diff --git a/pkg/oci/main_test.go b/pkg/oci/main_test.go index 4b47464c48..2b1be43a56 100644 --- a/pkg/oci/main_test.go +++ b/pkg/oci/main_test.go @@ -15,10 +15,12 @@ package oci import ( + "errors" "fmt" "log" "os" "path/filepath" + "strings" "testing" "time" @@ -33,10 +35,18 @@ const ( tag = "3.0.0" ) +var ( + registryHost string + registryResource *dockertest.Resource + registrySetupErr error +) + func TestMain(m *testing.M) { pool, err := dockertest.NewPool("") if err != nil { - log.Fatalf("Failed to connect to docker: %s", err) + registrySetupErr = err + code := m.Run() + os.Exit(code) } wd, err := os.Getwd() @@ -62,23 +72,68 @@ func TestMain(m *testing.M) { Name: "no", } } - res, err := pool.RunWithOptions(opts, hcOpts) + if err := pool.Client.Ping(); err != nil { + registrySetupErr = err + code := m.Run() + os.Exit(code) + } + + registryResource, err = pool.RunWithOptions(opts, hcOpts) if err != nil { - log.Fatalf("Failed to start resource: %s", err) + registrySetupErr = err + code := m.Run() + os.Exit(code) } portID := fmt.Sprintf("%s/tcp", port) - host := fmt.Sprintf("localhost:%s", res.GetPort(portID)) - os.Setenv(env, host) + registryHost = fmt.Sprintf("localhost:%s", registryResource.GetPort(portID)) + os.Setenv(env, registryHost) - log.Printf("Waiting for registry to be ready: %s", host) + log.Printf("Waiting for registry to be ready: %s", registryHost) time.Sleep(1 * time.Second) code := m.Run() - if err := res.Close(); err != nil { + if err := registryResource.Close(); err != nil { log.Fatalf("Failed to purge resource: %s", err) } os.Exit(code) } + +func isDockerUnavailable(err error) bool { + if err == nil { + return false + } + + if errors.Is(err, os.ErrNotExist) { + return true + } + + msg := strings.ToLower(err.Error()) + for _, marker := range []string{ + "cannot connect to the docker daemon", + "dial unix", + "is the docker daemon running", + "no such file or directory", + } { + if strings.Contains(msg, marker) { + return true + } + } + + return false +} + +func requireOCIRegistry(t *testing.T, repo string) string { + t.Helper() + + if registrySetupErr != nil { + if isDockerUnavailable(registrySetupErr) { + t.Skipf("Skipping Docker-dependent OCI test because Docker is unavailable: %v", registrySetupErr) + } + t.Fatalf("could not set up OCI registry: %v", registrySetupErr) + } + + return fmt.Sprintf("oci://%s/%s", registryHost, repo) +} diff --git a/pkg/oci/main_test_test.go b/pkg/oci/main_test_test.go new file mode 100644 index 0000000000..28593f16c7 --- /dev/null +++ b/pkg/oci/main_test_test.go @@ -0,0 +1,67 @@ +// Copyright 2025 The PipeCD Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package oci + +import ( + "errors" + "os" + "testing" +) + +func TestIsDockerUnavailable(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + err error + want bool + }{ + { + name: "missing socket path", + err: errors.New("dial unix /var/run/docker.sock: connect: no such file or directory"), + want: true, + }, + { + name: "daemon not running", + err: errors.New("Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?"), + want: true, + }, + { + name: "wrapped os err not exist", + err: errors.Join(errors.New("connect docker"), os.ErrNotExist), + want: true, + }, + { + name: "registry startup failure", + err: errors.New("failed to pull image"), + want: false, + }, + { + name: "nil error", + err: nil, + want: false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := isDockerUnavailable(tt.err); got != tt.want { + t.Fatalf("isDockerUnavailable(%v) = %v, want %v", tt.err, got, tt.want) + } + }) + } +} diff --git a/pkg/oci/pull_test.go b/pkg/oci/pull_test.go index 43c96e7bf7..7c7b89fa06 100644 --- a/pkg/oci/pull_test.go +++ b/pkg/oci/pull_test.go @@ -23,8 +23,7 @@ import ( func TestPullFileFromRegistry(t *testing.T) { t.Parallel() - // OCI_REGISTRY_HOST is set by TestMain in main_test.go - ociURL := fmt.Sprintf("oci://%s/test-pull", os.Getenv("OCI_REGISTRY_HOST")) + ociURL := requireOCIRegistry(t, "test-pull") testcases := pushTestFiles(t, t.TempDir(), ociURL) diff --git a/pkg/oci/push_test.go b/pkg/oci/push_test.go index cb526122b8..c2877c46ee 100644 --- a/pkg/oci/push_test.go +++ b/pkg/oci/push_test.go @@ -70,8 +70,7 @@ func pushTestFiles(t *testing.T, workDir, ociURL string) map[Platform]string { func TestPushFilesToRegistry(t *testing.T) { t.Parallel() - // OCI_REGISTRY_HOST is set by TestMain in main_test.go - ociURL := fmt.Sprintf("oci://%s/test-push", os.Getenv("OCI_REGISTRY_HOST")) + ociURL := requireOCIRegistry(t, "test-push") workDir := t.TempDir()