From 6a947dc7a93efd578331b30ba565e6665cb424b1 Mon Sep 17 00:00:00 2001 From: Steven Hartland Date: Mon, 9 Sep 2024 15:27:12 +0100 Subject: [PATCH 1/3] fix: docker config error handling when config file does not exist (#2772) Handle file not exist error in getDockerAuthConfigs, treating it as if no authentication was provided. Use config directly for cache instead of loading the file a second time which may be the wrong file if loaded from the environment. Correctly handle json decode errors in getDockerConfig instead of falling back to the default config, which would result in unexpected behaviour. Tests refactored to ensure all edge cases for getDockerConfig and getDockerAuthConfigs are handled. Fixes #2767 --- docker_auth.go | 49 ++--- docker_auth_test.go | 218 ++++++++++++-------- testdata/invalid-config/.docker/config.json | 3 + 3 files changed, 159 insertions(+), 111 deletions(-) create mode 100644 testdata/invalid-config/.docker/config.json diff --git a/docker_auth.go b/docker_auth.go index 99e2d2fdba1..af0d415de97 100644 --- a/docker_auth.go +++ b/docker_auth.go @@ -8,7 +8,6 @@ import ( "encoding/json" "errors" "fmt" - "io" "net/url" "os" "sync" @@ -137,24 +136,12 @@ func (c *credentialsCache) Get(hostname, configKey string) (string, string, erro return user, password, nil } -// configFileKey returns a key to use for caching credentials based on +// configKey returns a key to use for caching credentials based on // the contents of the currently active config. -func configFileKey() (string, error) { - configPath, err := dockercfg.ConfigPath() - if err != nil { - return "", err - } - - f, err := os.Open(configPath) - if err != nil { - return "", fmt.Errorf("open config file: %w", err) - } - - defer f.Close() - +func configKey(cfg *dockercfg.Config) (string, error) { h := md5.New() - if _, err := io.Copy(h, f); err != nil { - return "", fmt.Errorf("copying config file: %w", err) + if err := json.NewEncoder(h).Encode(cfg); err != nil { + return "", fmt.Errorf("encode config: %w", err) } return hex.EncodeToString(h.Sum(nil)), nil @@ -165,10 +152,14 @@ func configFileKey() (string, error) { func getDockerAuthConfigs() (map[string]registry.AuthConfig, error) { cfg, err := getDockerConfig() if err != nil { + if errors.Is(err, os.ErrNotExist) { + return map[string]registry.AuthConfig{}, nil + } + return nil, err } - configKey, err := configFileKey() + key, err := configKey(cfg) if err != nil { return nil, err } @@ -195,7 +186,7 @@ func getDockerAuthConfigs() (map[string]registry.AuthConfig, error) { switch { case ac.Username == "" && ac.Password == "": // Look up credentials from the credential store. - u, p, err := creds.Get(k, configKey) + u, p, err := creds.Get(k, key) if err != nil { results <- authConfigResult{err: err} return @@ -218,7 +209,7 @@ func getDockerAuthConfigs() (map[string]registry.AuthConfig, error) { go func(k string) { defer wg.Done() - u, p, err := creds.Get(k, configKey) + u, p, err := creds.Get(k, key) if err != nil { results <- authConfigResult{err: err} return @@ -260,20 +251,20 @@ func getDockerAuthConfigs() (map[string]registry.AuthConfig, error) { // 1. the DOCKER_AUTH_CONFIG environment variable, unmarshalling it into a dockercfg.Config // 2. the DOCKER_CONFIG environment variable, as the path to the config file // 3. else it will load the default config file, which is ~/.docker/config.json -func getDockerConfig() (dockercfg.Config, error) { - dockerAuthConfig := os.Getenv("DOCKER_AUTH_CONFIG") - if dockerAuthConfig != "" { - cfg := dockercfg.Config{} - err := json.Unmarshal([]byte(dockerAuthConfig), &cfg) - if err == nil { - return cfg, nil +func getDockerConfig() (*dockercfg.Config, error) { + if env := os.Getenv("DOCKER_AUTH_CONFIG"); env != "" { + var cfg dockercfg.Config + if err := json.Unmarshal([]byte(env), &cfg); err != nil { + return nil, fmt.Errorf("unmarshal DOCKER_AUTH_CONFIG: %w", err) } + + return &cfg, nil } cfg, err := dockercfg.LoadDefaultConfig() if err != nil { - return cfg, err + return nil, fmt.Errorf("load default config: %w", err) } - return cfg, nil + return &cfg, nil } diff --git a/docker_auth_test.go b/docker_auth_test.go index 4e55d2b9bff..7e42ff83b95 100644 --- a/docker_auth_test.go +++ b/docker_auth_test.go @@ -14,7 +14,6 @@ import ( "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/registry" "github.com/docker/docker/client" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go/internal/core" @@ -23,91 +22,87 @@ import ( const exampleAuth = "https://example-auth.com" -var testDockerConfigDirPath = filepath.Join("testdata", ".docker") - -var indexDockerIO = core.IndexDockerIO - -func TestGetDockerConfig(t *testing.T) { - const expectedErrorMessage = "Expected to find %s in auth configs" - - // Verify that the default docker config file exists before any test in this suite runs. - // Then, we can safely run the tests that rely on it. - defaultCfg, err := dockercfg.LoadDefaultConfig() - require.NoError(t, err) - require.NotEmpty(t, defaultCfg) - - t.Run("without DOCKER_CONFIG env var retrieves default", func(t *testing.T) { - t.Setenv("DOCKER_CONFIG", "") +func Test_getDockerConfig(t *testing.T) { + expectedConfig := &dockercfg.Config{ + AuthConfigs: map[string]dockercfg.AuthConfig{ + core.IndexDockerIO: {}, + "https://example.com": {}, + "https://my.private.registry": {}, + }, + CredentialsStore: "desktop", + } + t.Run("HOME/valid", func(t *testing.T) { + testDockerConfigHome(t, "testdata") cfg, err := getDockerConfig() require.NoError(t, err) - require.NotEmpty(t, cfg) + require.Equal(t, expectedConfig, cfg) + }) - assert.Equal(t, defaultCfg, cfg) + t.Run("HOME/not-found", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-found") + + cfg, err := getDockerConfig() + require.ErrorIs(t, err, os.ErrNotExist) + require.Nil(t, cfg) }) - t.Run("with DOCKER_CONFIG env var pointing to a non-existing file raises error", func(t *testing.T) { - t.Setenv("DOCKER_CONFIG", filepath.Join(testDockerConfigDirPath, "non-existing")) + t.Run("HOME/invalid-config", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "invalid-config") cfg, err := getDockerConfig() - require.Error(t, err) - require.Empty(t, cfg) + require.ErrorContains(t, err, "json: cannot unmarshal array") + require.Nil(t, cfg) }) - t.Run("with DOCKER_CONFIG env var", func(t *testing.T) { - t.Setenv("DOCKER_CONFIG", testDockerConfigDirPath) + t.Run("DOCKER_AUTH_CONFIG/valid", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-found") + t.Setenv("DOCKER_AUTH_CONFIG", dockerConfig) cfg, err := getDockerConfig() require.NoError(t, err) - require.NotEmpty(t, cfg) - - assert.Len(t, cfg.AuthConfigs, 3) + require.Equal(t, expectedConfig, cfg) + }) - authCfgs := cfg.AuthConfigs + t.Run("DOCKER_AUTH_CONFIG/invalid-config", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-found") + t.Setenv("DOCKER_AUTH_CONFIG", `{"auths": []}`) - if _, ok := authCfgs[indexDockerIO]; !ok { - t.Errorf(expectedErrorMessage, indexDockerIO) - } - if _, ok := authCfgs["https://example.com"]; !ok { - t.Errorf(expectedErrorMessage, "https://example.com") - } - if _, ok := authCfgs["https://my.private.registry"]; !ok { - t.Errorf(expectedErrorMessage, "https://my.private.registry") - } + cfg, err := getDockerConfig() + require.ErrorContains(t, err, "json: cannot unmarshal array") + require.Nil(t, cfg) }) - t.Run("DOCKER_AUTH_CONFIG env var takes precedence", func(t *testing.T) { - setAuthConfig(t, exampleAuth, "", "") - t.Setenv("DOCKER_CONFIG", testDockerConfigDirPath) + t.Run("DOCKER_CONFIG/valid", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-found") + t.Setenv("DOCKER_CONFIG", filepath.Join("testdata", ".docker")) cfg, err := getDockerConfig() require.NoError(t, err) - require.NotEmpty(t, cfg) - - assert.Len(t, cfg.AuthConfigs, 1) + require.Equal(t, expectedConfig, cfg) + }) - authCfgs := cfg.AuthConfigs + t.Run("DOCKER_CONFIG/invalid-config", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-found") + t.Setenv("DOCKER_CONFIG", filepath.Join("testdata", "invalid-config", ".docker")) - if _, ok := authCfgs[indexDockerIO]; ok { - t.Errorf("Not expected to find %s in auth configs", indexDockerIO) - } - if _, ok := authCfgs[exampleAuth]; !ok { - t.Errorf(expectedErrorMessage, exampleAuth) - } + cfg, err := getDockerConfig() + require.ErrorContains(t, err, "json: cannot unmarshal array") + require.Nil(t, cfg) }) +} +func TestDockerImageAuth(t *testing.T) { t.Run("retrieve auth with DOCKER_AUTH_CONFIG env var", func(t *testing.T) { username, password := "gopher", "secret" creds := setAuthConfig(t, exampleAuth, username, password) registry, cfg, err := DockerImageAuth(context.Background(), exampleAuth+"/my/image:latest") require.NoError(t, err) - require.NotEmpty(t, cfg) - - assert.Equal(t, exampleAuth, registry) - assert.Equal(t, username, cfg.Username) - assert.Equal(t, password, cfg.Password) - assert.Equal(t, creds, cfg.Auth) + require.Equal(t, exampleAuth, registry) + require.Equal(t, username, cfg.Username) + require.Equal(t, password, cfg.Password) + require.Equal(t, creds, cfg.Auth) }) t.Run("match registry authentication by host", func(t *testing.T) { @@ -117,12 +112,10 @@ func TestGetDockerConfig(t *testing.T) { registry, cfg, err := DockerImageAuth(context.Background(), imageReg+imagePath) require.NoError(t, err) - require.NotEmpty(t, cfg) - - assert.Equal(t, imageReg, registry) - assert.Equal(t, "gopher", cfg.Username) - assert.Equal(t, "secret", cfg.Password) - assert.Equal(t, base64, cfg.Auth) + require.Equal(t, imageReg, registry) + require.Equal(t, "gopher", cfg.Username) + require.Equal(t, "secret", cfg.Password) + require.Equal(t, base64, cfg.Auth) }) t.Run("fail to match registry authentication due to invalid host", func(t *testing.T) { @@ -135,8 +128,7 @@ func TestGetDockerConfig(t *testing.T) { registry, cfg, err := DockerImageAuth(context.Background(), imageReg+imagePath) require.ErrorIs(t, err, dockercfg.ErrCredentialsNotFound) require.Empty(t, cfg) - - assert.Equal(t, imageReg, registry) + require.Equal(t, imageReg, registry) }) t.Run("fail to match registry authentication by host with empty URL scheme creds and missing default", func(t *testing.T) { @@ -156,8 +148,7 @@ func TestGetDockerConfig(t *testing.T) { registry, cfg, err := DockerImageAuth(context.Background(), imageReg+imagePath) require.ErrorIs(t, err, dockercfg.ErrCredentialsNotFound) require.Empty(t, cfg) - - assert.Equal(t, imageReg, registry) + require.Equal(t, imageReg, registry) }) } @@ -391,27 +382,90 @@ func localAddress(t *testing.T) string { var dockerConfig string func Test_getDockerAuthConfigs(t *testing.T) { - t.Run("file", func(t *testing.T) { - got, err := getDockerAuthConfigs() + t.Run("HOME/valid", func(t *testing.T) { + testDockerConfigHome(t, "testdata") + + requireValidAuthConfig(t) + }) + + t.Run("HOME/not-found", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-exist") + + authConfigs, err := getDockerAuthConfigs() require.NoError(t, err) - require.NotNil(t, got) + require.NotNil(t, authConfigs) + require.Empty(t, authConfigs) + }) + + t.Run("HOME/invalid-config", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "invalid-config") + + authConfigs, err := getDockerAuthConfigs() + require.ErrorContains(t, err, "json: cannot unmarshal array") + require.Nil(t, authConfigs) }) - t.Run("env", func(t *testing.T) { + t.Run("DOCKER_AUTH_CONFIG/valid", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-exist") t.Setenv("DOCKER_AUTH_CONFIG", dockerConfig) - got, err := getDockerAuthConfigs() - require.NoError(t, err) + requireValidAuthConfig(t) + }) - // We can only check the keys as the values are not deterministic. - expected := map[string]registry.AuthConfig{ - "https://index.docker.io/v1/": {}, - "https://example.com": {}, - "https://my.private.registry": {}, - } - for k := range got { - got[k] = registry.AuthConfig{} - } - require.Equal(t, expected, got) + t.Run("DOCKER_AUTH_CONFIG/invalid-config", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-exist") + t.Setenv("DOCKER_AUTH_CONFIG", `{"auths": []}`) + + authConfigs, err := getDockerAuthConfigs() + require.ErrorContains(t, err, "json: cannot unmarshal array") + require.Nil(t, authConfigs) }) + + t.Run("DOCKER_CONFIG/valid", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-found") + t.Setenv("DOCKER_CONFIG", filepath.Join("testdata", ".docker")) + + requireValidAuthConfig(t) + }) + + t.Run("DOCKER_CONFIG/invalid-config", func(t *testing.T) { + testDockerConfigHome(t, "testdata", "not-found") + t.Setenv("DOCKER_CONFIG", filepath.Join("testdata", "invalid-config", ".docker")) + + cfg, err := getDockerConfig() + require.ErrorContains(t, err, "json: cannot unmarshal array") + require.Nil(t, cfg) + }) +} + +// requireValidAuthConfig checks that the given authConfigs map contains the expected keys. +func requireValidAuthConfig(t *testing.T) { + t.Helper() + + authConfigs, err := getDockerAuthConfigs() + require.NoError(t, err) + + // We can only check the keys as the values are not deterministic as they depend + // on users environment. + expected := map[string]registry.AuthConfig{ + "https://index.docker.io/v1/": {}, + "https://example.com": {}, + "https://my.private.registry": {}, + } + for k := range authConfigs { + authConfigs[k] = registry.AuthConfig{} + } + require.Equal(t, expected, authConfigs) +} + +// testDockerConfigHome sets the user's home directory to the given path +// and unsets the DOCKER_CONFIG and DOCKER_AUTH_CONFIG environment variables. +func testDockerConfigHome(t *testing.T, dirs ...string) { + t.Helper() + + dir := filepath.Join(dirs...) + t.Setenv("DOCKER_AUTH_CONFIG", "") + t.Setenv("DOCKER_CONFIG", "") + t.Setenv("HOME", dir) + t.Setenv("USERPROFILE", dir) // Windows } diff --git a/testdata/invalid-config/.docker/config.json b/testdata/invalid-config/.docker/config.json new file mode 100644 index 00000000000..f0f444f355e --- /dev/null +++ b/testdata/invalid-config/.docker/config.json @@ -0,0 +1,3 @@ +{ + "auths": [] +} From b4f82947ee7295898235ee64df290eebe6e49bea Mon Sep 17 00:00:00 2001 From: Steven Hartland Date: Mon, 9 Sep 2024 17:16:11 +0100 Subject: [PATCH 2/3] ci: add generate for mocks (#2774) Add generation for mocks, which can be run using make generate or go generate ./... Correct output-format for golangci-lint as githubs-actions has been replaced by colored-line-numbers. Add pre-commit target to help check code is valid before pushing. Ensure generate and mod tidy don't result in changes. Run go mod tidy for all modules, some were out of date. Implements: #2765 --- .github/workflows/ci-test-go.yml | 14 +++++++++++++- commons-test.mk | 15 +++++++++++++-- examples/nginx/go.sum | 2 ++ examples/toxiproxy/go.sum | 2 ++ generate.go | 3 +++ modules/artemis/go.sum | 2 ++ modules/azurite/go.sum | 2 ++ modules/cassandra/go.sum | 2 ++ modules/chroma/go.sum | 2 ++ modules/clickhouse/go.sum | 2 ++ modules/cockroachdb/go.sum | 2 ++ modules/compose/go.sum | 2 ++ modules/couchbase/go.mod | 2 -- modules/couchbase/go.sum | 3 ++- modules/dolt/go.sum | 2 ++ modules/elasticsearch/go.sum | 2 ++ modules/gcloud/go.sum | 3 ++- modules/grafana-lgtm/go.sum | 2 ++ modules/inbucket/go.sum | 2 ++ modules/influxdb/go.sum | 2 ++ modules/k3s/go.sum | 2 ++ modules/k6/go.sum | 2 ++ modules/kafka/go.sum | 2 ++ modules/localstack/go.sum | 2 ++ modules/mariadb/go.sum | 2 ++ modules/minio/go.sum | 2 ++ modules/mockserver/go.sum | 2 ++ modules/mongodb/go.sum | 2 ++ modules/mssql/go.sum | 2 ++ modules/mysql/go.sum | 2 ++ modules/nats/go.sum | 2 ++ modules/neo4j/go.sum | 2 ++ modules/ollama/go.sum | 2 ++ modules/openfga/go.sum | 2 ++ modules/openldap/go.sum | 2 ++ modules/opensearch/go.sum | 2 ++ modules/postgres/go.sum | 2 ++ modules/qdrant/go.sum | 2 ++ modules/rabbitmq/go.sum | 2 ++ modules/redis/go.sum | 2 ++ modules/redpanda/go.sum | 2 ++ modules/registry/go.sum | 2 ++ modules/surrealdb/go.sum | 2 ++ modules/valkey/go.sum | 2 ++ modules/vault/go.sum | 2 ++ modules/vearch/go.sum | 2 ++ modules/weaviate/go.sum | 2 ++ 47 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 generate.go diff --git a/.github/workflows/ci-test-go.yml b/.github/workflows/ci-test-go.yml index c2c0747278d..0a04327da14 100644 --- a/.github/workflows/ci-test-go.yml +++ b/.github/workflows/ci-test-go.yml @@ -85,13 +85,25 @@ jobs: # takes precedence over all other caching options. skip-cache: true + - name: generate + if: ${{ inputs.platform == 'ubuntu-latest' }} + working-directory: ./${{ inputs.project-directory }} + shell: bash + run: | + make generate + git --no-pager diff && [[ 0 -eq $(git status --porcelain | wc -l) ]] + - name: modVerify working-directory: ./${{ inputs.project-directory }} run: go mod verify - name: modTidy + if: ${{ inputs.platform == 'ubuntu-latest' }} working-directory: ./${{ inputs.project-directory }} - run: make tidy + shell: bash + run: | + make tidy + git --no-pager diff && [[ 0 -eq $(git status --porcelain | wc -l) ]] - name: ensure compilation working-directory: ./${{ inputs.project-directory }} diff --git a/commons-test.mk b/commons-test.mk index 04d0a6e70ca..08c9e613acd 100644 --- a/commons-test.mk +++ b/commons-test.mk @@ -11,13 +11,17 @@ $(GOBIN)/golangci-lint: $(GOBIN)/gotestsum: $(call go_install,gotest.tools/gotestsum@latest) +$(GOBIN)/mockery: + $(call go_install,github.com/vektra/mockery/v2@v2.45) + .PHONY: install -install: $(GOBIN)/golangci-lint $(GOBIN)/gotestsum +install: $(GOBIN)/golangci-lint $(GOBIN)/gotestsum $(GOBIN)/mockery .PHONY: clean clean: rm $(GOBIN)/golangci-lint rm $(GOBIN)/gotestsum + rm $(GOBIN)/mockery .PHONY: dependencies-scan dependencies-scan: @@ -26,7 +30,11 @@ dependencies-scan: .PHONY: lint lint: $(GOBIN)/golangci-lint - golangci-lint run --out-format=github-actions --path-prefix=. --verbose -c $(ROOT_DIR)/.golangci.yml --fix + golangci-lint run --out-format=colored-line-number --path-prefix=. --verbose -c $(ROOT_DIR)/.golangci.yml --fix + +.PHONY: generate +generate: $(GOBIN)/mockery + go generate ./... .PHONY: test-% test-%: $(GOBIN)/gotestsum @@ -51,3 +59,6 @@ test-tools: $(GOBIN)/gotestsum .PHONY: tidy tidy: go mod tidy + +.PHONY: pre-commit +pre-commit: generate tidy lint diff --git a/examples/nginx/go.sum b/examples/nginx/go.sum index 85338720c8a..ed514ea5ef8 100644 --- a/examples/nginx/go.sum +++ b/examples/nginx/go.sum @@ -89,6 +89,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/examples/toxiproxy/go.sum b/examples/toxiproxy/go.sum index c62c0ac532c..91a27d9fc86 100644 --- a/examples/toxiproxy/go.sum +++ b/examples/toxiproxy/go.sum @@ -105,6 +105,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/generate.go b/generate.go new file mode 100644 index 00000000000..19ae49695d9 --- /dev/null +++ b/generate.go @@ -0,0 +1,3 @@ +package testcontainers + +//go:generate mockery diff --git a/modules/artemis/go.sum b/modules/artemis/go.sum index 69f6d529977..db201d66723 100644 --- a/modules/artemis/go.sum +++ b/modules/artemis/go.sum @@ -101,6 +101,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/azurite/go.sum b/modules/azurite/go.sum index 90f85680886..0d2267c5a4c 100644 --- a/modules/azurite/go.sum +++ b/modules/azurite/go.sum @@ -114,6 +114,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/cassandra/go.sum b/modules/cassandra/go.sum index 775e48ecf59..4b9d427f273 100644 --- a/modules/cassandra/go.sum +++ b/modules/cassandra/go.sum @@ -107,6 +107,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/modules/chroma/go.sum b/modules/chroma/go.sum index e7d70f0539f..9e60fa93f1d 100644 --- a/modules/chroma/go.sum +++ b/modules/chroma/go.sum @@ -103,6 +103,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/clickhouse/go.sum b/modules/clickhouse/go.sum index 2f09c4daae3..cf050d17390 100644 --- a/modules/clickhouse/go.sum +++ b/modules/clickhouse/go.sum @@ -122,6 +122,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/modules/cockroachdb/go.sum b/modules/cockroachdb/go.sum index c051f94433c..19481c176e9 100644 --- a/modules/cockroachdb/go.sum +++ b/modules/cockroachdb/go.sum @@ -106,6 +106,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/modules/compose/go.sum b/modules/compose/go.sum index c05580192ee..7a9b815421d 100644 --- a/modules/compose/go.sum +++ b/modules/compose/go.sum @@ -459,6 +459,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= diff --git a/modules/couchbase/go.mod b/modules/couchbase/go.mod index 96365d44e7f..cc148407376 100644 --- a/modules/couchbase/go.mod +++ b/modules/couchbase/go.mod @@ -2,8 +2,6 @@ module github.com/testcontainers/testcontainers-go/modules/couchbase go 1.22 -toolchain go1.21.7 - require ( github.com/cenkalti/backoff/v4 v4.2.1 github.com/couchbase/gocb/v2 v2.7.2 diff --git a/modules/couchbase/go.sum b/modules/couchbase/go.sum index 737e1e52369..7f40c3b63f0 100644 --- a/modules/couchbase/go.sum +++ b/modules/couchbase/go.sum @@ -134,8 +134,9 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= diff --git a/modules/dolt/go.sum b/modules/dolt/go.sum index 7784d0b8332..e2bb93b49dc 100644 --- a/modules/dolt/go.sum +++ b/modules/dolt/go.sum @@ -91,6 +91,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/elasticsearch/go.sum b/modules/elasticsearch/go.sum index 59647a3793d..046620bb602 100644 --- a/modules/elasticsearch/go.sum +++ b/modules/elasticsearch/go.sum @@ -101,6 +101,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/gcloud/go.sum b/modules/gcloud/go.sum index 31d286e7d04..07f1f7f404a 100644 --- a/modules/gcloud/go.sum +++ b/modules/gcloud/go.sum @@ -190,8 +190,9 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/modules/grafana-lgtm/go.sum b/modules/grafana-lgtm/go.sum index 9eb14f31cb0..1eced8de79b 100644 --- a/modules/grafana-lgtm/go.sum +++ b/modules/grafana-lgtm/go.sum @@ -103,6 +103,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/inbucket/go.sum b/modules/inbucket/go.sum index 2e403c06ad6..62be48931c3 100644 --- a/modules/inbucket/go.sum +++ b/modules/inbucket/go.sum @@ -100,6 +100,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/influxdb/go.sum b/modules/influxdb/go.sum index ad81df94f6d..875bf9d6a13 100644 --- a/modules/influxdb/go.sum +++ b/modules/influxdb/go.sum @@ -99,6 +99,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/k3s/go.sum b/modules/k3s/go.sum index e08a07dd42b..86dfae5fc0f 100644 --- a/modules/k3s/go.sum +++ b/modules/k3s/go.sum @@ -143,6 +143,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/modules/k6/go.sum b/modules/k6/go.sum index 85338720c8a..ed514ea5ef8 100644 --- a/modules/k6/go.sum +++ b/modules/k6/go.sum @@ -89,6 +89,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/kafka/go.sum b/modules/kafka/go.sum index 778434567d4..9310807a500 100644 --- a/modules/kafka/go.sum +++ b/modules/kafka/go.sum @@ -127,6 +127,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/modules/localstack/go.sum b/modules/localstack/go.sum index 532bf9e05b1..93de5656150 100644 --- a/modules/localstack/go.sum +++ b/modules/localstack/go.sum @@ -141,6 +141,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/mariadb/go.sum b/modules/mariadb/go.sum index 7784d0b8332..e2bb93b49dc 100644 --- a/modules/mariadb/go.sum +++ b/modules/mariadb/go.sum @@ -91,6 +91,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/minio/go.sum b/modules/minio/go.sum index 55013e42212..e837e4f48e9 100644 --- a/modules/minio/go.sum +++ b/modules/minio/go.sum @@ -110,6 +110,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/modules/mockserver/go.sum b/modules/mockserver/go.sum index e752fd08122..acf88d134a7 100644 --- a/modules/mockserver/go.sum +++ b/modules/mockserver/go.sum @@ -93,6 +93,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/mongodb/go.sum b/modules/mongodb/go.sum index 0f2d5d53369..bc45e6d3335 100644 --- a/modules/mongodb/go.sum +++ b/modules/mongodb/go.sum @@ -95,6 +95,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/mssql/go.sum b/modules/mssql/go.sum index 4160a61ee2d..2f512564ff0 100644 --- a/modules/mssql/go.sum +++ b/modules/mssql/go.sum @@ -113,6 +113,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/mysql/go.sum b/modules/mysql/go.sum index 7784d0b8332..e2bb93b49dc 100644 --- a/modules/mysql/go.sum +++ b/modules/mysql/go.sum @@ -91,6 +91,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/nats/go.sum b/modules/nats/go.sum index 11a786d5d4a..61daa84ab42 100644 --- a/modules/nats/go.sum +++ b/modules/nats/go.sum @@ -95,6 +95,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/neo4j/go.sum b/modules/neo4j/go.sum index 2576f66232f..8cccf1cd871 100644 --- a/modules/neo4j/go.sum +++ b/modules/neo4j/go.sum @@ -91,6 +91,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/ollama/go.sum b/modules/ollama/go.sum index 07f9ef689fe..d2ff261f5b1 100644 --- a/modules/ollama/go.sum +++ b/modules/ollama/go.sum @@ -93,6 +93,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/openfga/go.sum b/modules/openfga/go.sum index a2fe09d6b47..1ab48ebfe26 100644 --- a/modules/openfga/go.sum +++ b/modules/openfga/go.sum @@ -93,6 +93,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/openldap/go.sum b/modules/openldap/go.sum index 57de4980776..eb7f8b79e36 100644 --- a/modules/openldap/go.sum +++ b/modules/openldap/go.sum @@ -98,6 +98,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/opensearch/go.sum b/modules/opensearch/go.sum index 85338720c8a..ed514ea5ef8 100644 --- a/modules/opensearch/go.sum +++ b/modules/opensearch/go.sum @@ -89,6 +89,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/postgres/go.sum b/modules/postgres/go.sum index 42933793538..1b1f37ba8de 100644 --- a/modules/postgres/go.sum +++ b/modules/postgres/go.sum @@ -106,6 +106,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/modules/qdrant/go.sum b/modules/qdrant/go.sum index c4425e81821..c38866d982d 100644 --- a/modules/qdrant/go.sum +++ b/modules/qdrant/go.sum @@ -91,6 +91,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/rabbitmq/go.sum b/modules/rabbitmq/go.sum index f57610f497b..bf809122dab 100644 --- a/modules/rabbitmq/go.sum +++ b/modules/rabbitmq/go.sum @@ -96,6 +96,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/redis/go.sum b/modules/redis/go.sum index 1698de2ecff..afc80497f0e 100644 --- a/modules/redis/go.sum +++ b/modules/redis/go.sum @@ -111,6 +111,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/redpanda/go.sum b/modules/redpanda/go.sum index cf26162896b..6b3772dc879 100644 --- a/modules/redpanda/go.sum +++ b/modules/redpanda/go.sum @@ -101,6 +101,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/registry/go.sum b/modules/registry/go.sum index 5583e9b0fc3..28367d00203 100644 --- a/modules/registry/go.sum +++ b/modules/registry/go.sum @@ -96,6 +96,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/surrealdb/go.sum b/modules/surrealdb/go.sum index 4a82bd32171..d39dac7fa49 100644 --- a/modules/surrealdb/go.sum +++ b/modules/surrealdb/go.sum @@ -91,6 +91,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/valkey/go.sum b/modules/valkey/go.sum index f92f0dbcc77..1256ebe1140 100644 --- a/modules/valkey/go.sum +++ b/modules/valkey/go.sum @@ -96,6 +96,8 @@ github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnj github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= diff --git a/modules/vault/go.sum b/modules/vault/go.sum index 5bed30736ed..8fa2d5b6df8 100644 --- a/modules/vault/go.sum +++ b/modules/vault/go.sum @@ -119,6 +119,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/vearch/go.sum b/modules/vearch/go.sum index 85338720c8a..ed514ea5ef8 100644 --- a/modules/vearch/go.sum +++ b/modules/vearch/go.sum @@ -89,6 +89,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= diff --git a/modules/weaviate/go.sum b/modules/weaviate/go.sum index 4d55fd59afb..b2be18dab84 100644 --- a/modules/weaviate/go.sum +++ b/modules/weaviate/go.sum @@ -203,6 +203,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= From b60497e9c7921c637aa2213bb9bffb486346c15d Mon Sep 17 00:00:00 2001 From: Steven Hartland Date: Thu, 12 Sep 2024 09:47:04 +0100 Subject: [PATCH 3/3] fix: resource clean up for tests and examples (#2738) Ensure that all resources are cleaned up for tests and examples even if they fail. This leverages new helpers in testcontainers: * TerminateContainer for examples * CleanupContainer and CleanupNetwork for tests These are required ensuring that containers that are created but fail in later actions are returned alongside the error so that clean up can be performed. Consistently clean up created networks using a new context to ensure that the removal gets run even if original context has timed out or been cancelled. Use fmt.Print instead of log.Fatal to ensure that defers are run in all examples again ensuring that clean up is processed. Call Stop from Terminate to ensure that child containers are shutdown correctly on clean up as the hard coded timeout using by ContainerRemove is too short to allow this to happen correctly. Clean up of test logic replacing manual checks and asserts with require to make them more concise and hence easier to understand. Quiet test output by either capturing or disabling output so it's easier to identify issues when tests are run in non verbose mode. Clarify source of errors with wrapping and update tests to handle. Ensure that port forwarding container is shutdown if an error occurs during setup so it isn't orphaned. Shutdown the port forwarding container on both stop and terminate to prevent it being orphaned when the Stop is used. Add missing error checks to tests. Remove unused nolint directives and enable the nolintlint to catch any regressions. Don't use container as a variable as its overused. --- .gitignore | 5 +- .golangci.yml | 1 + cleanup.go | 107 ++++++ container_test.go | 53 ++- docker.go | 31 +- docker_auth_test.go | 15 +- docker_exec_test.go | 21 +- docker_files_test.go | 67 ++-- docker_test.go | 321 ++++++++---------- docs/features/common_functional_options.md | 3 +- docs/features/creating_container.md | 101 ++++-- docs/quickstart.md | 17 +- examples/nginx/go.mod | 9 +- examples/nginx/go.sum | 9 + examples/nginx/nginx.go | 15 +- examples/nginx/nginx_test.go | 25 +- examples/toxiproxy/go.mod | 7 +- examples/toxiproxy/go.sum | 9 + examples/toxiproxy/redis.go | 8 +- examples/toxiproxy/toxiproxy.go | 14 +- examples/toxiproxy/toxiproxy_test.go | 70 +--- from_dockerfile_test.go | 50 +-- generic_test.go | 6 +- image_substitutors_test.go | 1 + image_test.go | 14 +- lifecycle.go | 4 +- lifecycle_test.go | 139 ++++---- logconsumer_test.go | 73 ++-- modulegen/_template/examples_test.go.tmpl | 16 +- modulegen/_template/module.go.tmpl | 9 +- modulegen/_template/module_test.go.tmpl | 16 +- modulegen/main_test.go | 24 +- modules/artemis/artemis.go | 12 +- modules/artemis/artemis_test.go | 14 +- modules/artemis/examples_test.go | 23 +- modules/azurite/azurite.go | 9 +- modules/azurite/azurite_test.go | 17 +- modules/azurite/examples_test.go | 110 +++--- modules/azurite/go.mod | 7 +- modules/azurite/go.sum | 9 + modules/cassandra/cassandra.go | 10 +- modules/cassandra/cassandra_test.go | 61 +--- modules/cassandra/examples_test.go | 26 +- modules/chroma/chroma.go | 9 +- modules/chroma/chroma_test.go | 21 +- modules/chroma/examples_test.go | 71 ++-- modules/clickhouse/clickhouse.go | 14 +- modules/clickhouse/clickhouse_test.go | 124 +++---- modules/clickhouse/examples_test.go | 21 +- modules/cockroachdb/cockroachdb.go | 10 +- modules/cockroachdb/cockroachdb_test.go | 72 ++-- modules/cockroachdb/examples_test.go | 23 +- modules/compose/compose_api.go | 4 + modules/consul/consul.go | 9 +- modules/consul/consul_test.go | 6 +- modules/consul/examples_test.go | 39 ++- modules/couchbase/couchbase.go | 14 +- modules/couchbase/couchbase_test.go | 73 ++-- modules/couchbase/examples_test.go | 24 +- modules/couchbase/go.mod | 6 +- modules/couchbase/go.sum | 8 + modules/dolt/dolt.go | 15 +- modules/dolt/dolt_test.go | 126 +++---- modules/dolt/examples_test.go | 41 ++- modules/dolt/go.mod | 7 +- modules/dolt/go.sum | 9 + modules/elasticsearch/elasticsearch.go | 25 +- modules/elasticsearch/elasticsearch_test.go | 54 +-- modules/elasticsearch/examples_test.go | 48 +-- modules/gcloud/bigquery.go | 15 +- modules/gcloud/bigquery_test.go | 26 +- modules/gcloud/bigtable.go | 10 +- modules/gcloud/bigtable_test.go | 32 +- modules/gcloud/datastore.go | 10 +- modules/gcloud/datastore_test.go | 23 +- modules/gcloud/firestore.go | 10 +- modules/gcloud/firestore_test.go | 29 +- modules/gcloud/gcloud.go | 25 +- modules/gcloud/go.mod | 4 + modules/gcloud/go.sum | 8 + modules/gcloud/pubsub.go | 10 +- modules/gcloud/pubsub_test.go | 32 +- modules/gcloud/spanner.go | 7 +- modules/gcloud/spanner_test.go | 44 ++- modules/grafana-lgtm/examples_test.go | 113 ++++--- modules/grafana-lgtm/go.mod | 5 + modules/grafana-lgtm/go.sum | 10 + modules/grafana-lgtm/grafana.go | 4 +- modules/grafana-lgtm/grafana_test.go | 37 +- modules/inbucket/examples_test.go | 17 +- modules/inbucket/inbucket.go | 9 +- modules/inbucket/inbucket_test.go | 15 +- modules/influxdb/examples_test.go | 17 +- modules/influxdb/influxdb.go | 9 +- modules/influxdb/influxdb_test.go | 21 +- modules/k3s/go.mod | 4 +- modules/k3s/k3s.go | 9 +- modules/k3s/k3s_example_test.go | 29 +- modules/k3s/k3s_test.go | 97 ++---- modules/k6/examples_test.go | 43 ++- modules/k6/go.mod | 7 +- modules/k6/go.sum | 9 + modules/k6/k6.go | 31 +- modules/k6/k6_test.go | 44 ++- modules/kafka/examples_test.go | 17 +- modules/kafka/go.mod | 6 +- modules/kafka/go.sum | 9 + modules/kafka/kafka.go | 11 +- modules/kafka/kafka_test.go | 58 +--- modules/localstack/examples_test.go | 96 +++--- modules/localstack/localstack.go | 10 +- modules/localstack/localstack_test.go | 34 +- modules/localstack/v1/s3_test.go | 5 +- modules/localstack/v2/s3_test.go | 5 +- modules/mariadb/examples_test.go | 17 +- modules/mariadb/go.mod | 7 +- modules/mariadb/go.sum | 9 + modules/mariadb/mariadb.go | 16 +- modules/mariadb/mariadb_test.go | 176 +++------- modules/milvus/examples_test.go | 42 +-- modules/milvus/milvus.go | 9 +- modules/milvus/milvus_test.go | 12 +- modules/minio/examples_test.go | 17 +- modules/minio/go.mod | 7 +- modules/minio/go.sum | 9 + modules/minio/minio.go | 9 +- modules/minio/minio_test.go | 50 +-- modules/mockserver/examples_test.go | 42 +-- modules/mockserver/go.mod | 5 + modules/mockserver/go.sum | 9 + modules/mongodb/examples_test.go | 64 ++-- modules/mongodb/go.mod | 7 +- modules/mongodb/go.sum | 9 + modules/mongodb/mongodb.go | 12 +- modules/mongodb/mongodb_test.go | 29 +- modules/mssql/examples_test.go | 17 +- modules/mssql/go.mod | 7 +- modules/mssql/go.sum | 9 + modules/mssql/mssql.go | 12 +- modules/mssql/mssql_test.go | 156 +++------ modules/mysql/examples_test.go | 44 +-- modules/mysql/go.mod | 7 +- modules/mysql/go.sum | 9 + modules/mysql/mysql.go | 16 +- modules/mysql/mysql_test.go | 119 +++---- modules/nats/examples_test.go | 128 ++++--- modules/nats/go.mod | 7 +- modules/nats/go.sum | 9 + modules/nats/nats.go | 17 +- modules/nats/nats_test.go | 62 ++-- modules/neo4j/examples_test.go | 17 +- modules/neo4j/go.mod | 7 +- modules/neo4j/go.sum | 9 + modules/neo4j/neo4j.go | 9 +- modules/neo4j/neo4j_test.go | 78 ++--- modules/ollama/examples_test.go | 69 ++-- modules/ollama/go.mod | 6 +- modules/ollama/go.sum | 8 + modules/ollama/ollama.go | 9 +- modules/ollama/ollama_test.go | 102 ++---- modules/openfga/examples_test.go | 94 +++--- modules/openfga/go.mod | 7 +- modules/openfga/go.sum | 9 + modules/openfga/openfga.go | 9 +- modules/openfga/openfga_test.go | 17 +- modules/openldap/examples_test.go | 45 +-- modules/openldap/go.mod | 7 +- modules/openldap/go.sum | 9 + modules/openldap/openldap.go | 19 +- modules/openldap/openldap_test.go | 176 +++------- modules/opensearch/examples_test.go | 17 +- modules/opensearch/go.mod | 7 +- modules/opensearch/go.sum | 9 + modules/opensearch/opensearch.go | 9 +- modules/opensearch/opensearch_test.go | 31 +- modules/postgres/examples_test.go | 16 +- modules/postgres/postgres.go | 19 +- modules/postgres/postgres_test.go | 274 +++++---------- modules/pulsar/examples_test.go | 17 +- modules/pulsar/pulsar.go | 13 +- modules/pulsar/pulsar_test.go | 31 +- modules/qdrant/examples_test.go | 59 ++-- modules/qdrant/go.mod | 7 +- modules/qdrant/go.sum | 9 + modules/qdrant/qdrant.go | 9 +- modules/qdrant/qdrant_test.go | 54 +-- modules/rabbitmq/examples_test.go | 109 +++--- modules/rabbitmq/go.mod | 19 +- modules/rabbitmq/go.sum | 7 + modules/rabbitmq/rabbitmq.go | 15 +- modules/rabbitmq/rabbitmq_test.go | 59 +--- modules/redis/examples_test.go | 17 +- modules/redis/redis.go | 9 +- modules/redis/redis_test.go | 31 +- modules/redpanda/examples_test.go | 17 +- modules/redpanda/options.go | 2 +- modules/redpanda/redpanda.go | 42 +-- modules/redpanda/redpanda_test.go | 128 +++---- modules/registry/examples_test.go | 103 +++--- modules/registry/registry.go | 10 +- modules/registry/registry_test.go | 31 +- modules/surrealdb/examples_test.go | 17 +- modules/surrealdb/go.mod | 5 + modules/surrealdb/go.sum | 9 + modules/surrealdb/surrealdb.go | 9 +- modules/surrealdb/surrealdb_test.go | 120 ++----- modules/valkey/examples_test.go | 17 +- modules/valkey/valkey.go | 9 +- modules/valkey/valkey_test.go | 31 +- modules/vault/examples_test.go | 52 +-- modules/vault/vault.go | 9 +- modules/vault/vault_test.go | 9 +- modules/vearch/examples_test.go | 17 +- modules/vearch/go.mod | 9 +- modules/vearch/go.sum | 9 + modules/vearch/vearch.go | 9 +- modules/vearch/vearch_test.go | 32 +- modules/weaviate/examples_test.go | 52 +-- modules/weaviate/go.mod | 5 +- modules/weaviate/weaviate.go | 9 +- modules/weaviate/weaviate_test.go | 36 +- mounts_test.go | 14 +- network/examples_test.go | 4 +- network/network_test.go | 224 ++++-------- options_test.go | 12 +- parallel.go | 9 +- parallel_test.go | 4 +- port_forwarding.go | 67 ++-- port_forwarding_test.go | 17 +- reaper_test.go | 40 +-- testcontainers_test.go | 3 +- testdata/echoserver.go | 3 +- testhelpers_test.go | 19 -- testing.go | 98 ++++++ testing_test.go | 81 ++++- wait/exec_test.go | 30 +- wait/file.go | 2 +- wait/host_port.go | 26 +- wait/host_port_test.go | 78 ++--- wait/http_test.go | 356 +++++++------------- wait/testdata/main.go | 2 +- 241 files changed, 3914 insertions(+), 4199 deletions(-) create mode 100644 cleanup.go diff --git a/.gitignore b/.gitignore index 4b420b86b49..e5293563592 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,7 @@ TEST-*.xml tcvenv -**/go.work \ No newline at end of file +**/go.work + +# VS Code settings +.vscode diff --git a/.golangci.yml b/.golangci.yml index 1791b9caac5..861e0cbc5c4 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -8,6 +8,7 @@ linters: - nonamedreturns - testifylint - errcheck + - nolintlint linters-settings: errorlint: diff --git a/cleanup.go b/cleanup.go new file mode 100644 index 00000000000..e2d52440b94 --- /dev/null +++ b/cleanup.go @@ -0,0 +1,107 @@ +package testcontainers + +import ( + "context" + "errors" + "fmt" + "reflect" + "time" +) + +// terminateOptions is a type that holds the options for terminating a container. +type terminateOptions struct { + ctx context.Context + timeout *time.Duration + volumes []string +} + +// TerminateOption is a type that represents an option for terminating a container. +type TerminateOption func(*terminateOptions) + +// StopContext returns a TerminateOption that sets the context. +// Default: context.Background(). +func StopContext(ctx context.Context) TerminateOption { + return func(c *terminateOptions) { + c.ctx = ctx + } +} + +// StopTimeout returns a TerminateOption that sets the timeout. +// Default: See [Container.Stop]. +func StopTimeout(timeout time.Duration) TerminateOption { + return func(c *terminateOptions) { + c.timeout = &timeout + } +} + +// RemoveVolumes returns a TerminateOption that sets additional volumes to remove. +// This is useful when the container creates named volumes that should be removed +// which are not removed by default. +// Default: nil. +func RemoveVolumes(volumes ...string) TerminateOption { + return func(c *terminateOptions) { + c.volumes = volumes + } +} + +// TerminateContainer calls [Container.Terminate] on the container if it is not nil. +// +// This should be called as a defer directly after [GenericContainer](...) +// or a modules Run(...) to ensure the container is terminated when the +// function ends. +func TerminateContainer(container Container, options ...TerminateOption) error { + if isNil(container) { + return nil + } + + c := &terminateOptions{ + ctx: context.Background(), + } + + for _, opt := range options { + opt(c) + } + + // TODO: Add a timeout when terminate supports it. + err := container.Terminate(c.ctx) + if !isCleanupSafe(err) { + return fmt.Errorf("terminate: %w", err) + } + + // Remove additional volumes if any. + if len(c.volumes) == 0 { + return nil + } + + client, err := NewDockerClientWithOpts(c.ctx) + if err != nil { + return fmt.Errorf("docker client: %w", err) + } + + defer client.Close() + + // Best effort to remove all volumes. + var errs []error + for _, volume := range c.volumes { + if errRemove := client.VolumeRemove(c.ctx, volume, true); errRemove != nil { + errs = append(errs, fmt.Errorf("volume remove %q: %w", volume, errRemove)) + } + } + + return errors.Join(errs...) +} + +// isNil returns true if val is nil or an nil instance false otherwise. +func isNil(val any) bool { + if val == nil { + return true + } + + valueOf := reflect.ValueOf(val) + switch valueOf.Kind() { + case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer, reflect.Interface, reflect.Slice: + return valueOf.IsNil() + default: + return false + } +} diff --git a/container_test.go b/container_test.go index 3cb14ac2962..074a8185694 100644 --- a/container_test.go +++ b/container_test.go @@ -290,8 +290,7 @@ func Test_BuildImageWithContexts(t *testing.T) { ContainerRequest: req, Started: true, }) - - defer terminateContainerOnEnd(t, ctx, c) + testcontainers.CleanupContainer(t, c) if testCase.ExpectedError != "" { require.EqualError(t, err, testCase.ExpectedError) @@ -317,7 +316,7 @@ func Test_GetLogsFromFailedContainer(t *testing.T) { ContainerRequest: req, Started: true, }) - terminateContainerOnEnd(t, ctx, c) + testcontainers.CleanupContainer(t, c) require.Error(t, err) require.Contains(t, err.Error(), "container exited with code 0") @@ -417,25 +416,21 @@ func TestImageSubstitutors(t *testing.T) { ImageSubstitutors: test.substitutors, } - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) + testcontainers.CleanupContainer(t, ctr) if test.expectedError != nil { require.ErrorIs(t, err, test.expectedError) return } - if err != nil { - t.Fatal(err) - } - defer func() { - terminateContainerOnEnd(t, ctx, container) - }() + require.NoError(t, err) // enforce the concrete type, as GenericContainer returns an interface, // which will be changed in future implementations of the library - dockerContainer := container.(*testcontainers.DockerContainer) + dockerContainer := ctr.(*testcontainers.DockerContainer) assert.Equal(t, test.expectedImage, dockerContainer.Image) }) } @@ -455,21 +450,17 @@ func TestShouldStartContainersInParallel(t *testing.T) { ExposedPorts: []string{nginxDefaultPort}, WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second), } - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) - if err != nil { - t.Fatalf("could not start container: %v", err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + // mappedPort { - port, err := container.MappedPort(ctx, nginxDefaultPort) + port, err := ctr.MappedPort(ctx, nginxDefaultPort) // } - if err != nil { - t.Fatalf("could not get mapped port: %v", err) - } - - terminateContainerOnEnd(t, ctx, container) + require.NoError(t, err) t.Logf("Parallel container [iteration_%d] listening on %d\n", i, port.Int()) }) @@ -480,28 +471,28 @@ func ExampleGenericContainer_withSubstitutors() { ctx := context.Background() // applyImageSubstitutors { - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ Image: "alpine:latest", ImageSubstitutors: []testcontainers.ImageSubstitutor{dockerImageSubstitutor{}}, }, Started: true, }) - // } - if err != nil { - log.Fatalf("could not start container: %v", err) - } - defer func() { - err := container.Terminate(ctx) - if err != nil { - log.Fatalf("could not terminate container: %v", err) + if err := testcontainers.TerminateContainer(ctr); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + // } + if err != nil { + log.Printf("could not start container: %v", err) + return + } + // enforce the concrete type, as GenericContainer returns an interface, // which will be changed in future implementations of the library - dockerContainer := container.(*testcontainers.DockerContainer) + dockerContainer := ctr.(*testcontainers.DockerContainer) fmt.Println(dockerContainer.Image) diff --git a/docker.go b/docker.go index 5f6c4156273..dcd962ffc85 100644 --- a/docker.go +++ b/docker.go @@ -259,9 +259,14 @@ func (c *DockerContainer) Start(ctx context.Context) error { // // If the container is already stopped, the method is a no-op. func (c *DockerContainer) Stop(ctx context.Context, timeout *time.Duration) error { + // Note we can't check isRunning here because we allow external creation + // without exposing the ability to fully initialize the container state. + // See: https://github.com/testcontainers/testcontainers-go/issues/2667 + // TODO: Add a check for isRunning when the above issue is resolved. + err := c.stoppingHook(ctx) if err != nil { - return err + return fmt.Errorf("stopping hook: %w", err) } var options container.StopOptions @@ -272,22 +277,38 @@ func (c *DockerContainer) Stop(ctx context.Context, timeout *time.Duration) erro } if err := c.provider.client.ContainerStop(ctx, c.ID, options); err != nil { - return err + return fmt.Errorf("container stop: %w", err) } + defer c.provider.Close() c.isRunning = false err = c.stoppedHook(ctx) if err != nil { - return err + return fmt.Errorf("stopped hook: %w", err) } return nil } -// Terminate is used to kill the container. It is usually triggered by as defer function. +// Terminate calls stops and then removes the container including its volumes. +// If its image was built it and all child images are also removed unless +// the [FromDockerfile.KeepImage] on the [ContainerRequest] was set to true. +// +// The following hooks are called in order: +// - [ContainerLifecycleHooks.PreTerminates] +// - [ContainerLifecycleHooks.PostTerminates] func (c *DockerContainer) Terminate(ctx context.Context) error { + // ContainerRemove hardcodes stop timeout to 3 seconds which is too short + // to ensure that child containers are stopped so we manually call stop. + // TODO: make this configurable via a functional option. + timeout := 10 * time.Second + err := c.Stop(ctx, &timeout) + if err != nil && !isCleanupSafe(err) { + return fmt.Errorf("stop: %w", err) + } + select { // close reaper if it was created case c.terminationSignal <- true: @@ -296,6 +317,8 @@ func (c *DockerContainer) Terminate(ctx context.Context) error { defer c.provider.client.Close() + // TODO: Handle errors from ContainerRemove more correctly, e.g. should we + // run the terminated hook? errs := []error{ c.terminatingHook(ctx), c.provider.client.ContainerRemove(ctx, c.GetContainerID(), container.RemoveOptions{ diff --git a/docker_auth_test.go b/docker_auth_test.go index 7e42ff83b95..d494d6d12ea 100644 --- a/docker_auth_test.go +++ b/docker_auth_test.go @@ -164,8 +164,8 @@ func TestBuildContainerFromDockerfile(t *testing.T) { } redisC, err := prepareRedisImage(ctx, req) + CleanupContainer(t, redisC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, redisC) } // removeImageFromLocalCache removes the image from the local cache @@ -202,8 +202,7 @@ func TestBuildContainerFromDockerfileWithDockerAuthConfig(t *testing.T) { BuildArgs: map[string]*string{ "REGISTRY_HOST": ®istryHost, }, - Repo: "localhost", - PrintBuildLog: true, + Repo: "localhost", }, AlwaysPullImage: true, // make sure the authentication takes place ExposedPorts: []string{"6379/tcp"}, @@ -211,7 +210,7 @@ func TestBuildContainerFromDockerfileWithDockerAuthConfig(t *testing.T) { } redisC, err := prepareRedisImage(ctx, req) - terminateContainerOnEnd(t, ctx, redisC) + CleanupContainer(t, redisC) require.NoError(t, err) } @@ -237,7 +236,7 @@ func TestBuildContainerFromDockerfileShouldFailWithWrongDockerAuthConfig(t *test } redisC, err := prepareRedisImage(ctx, req) - terminateContainerOnEnd(t, ctx, redisC) + CleanupContainer(t, redisC) require.Error(t, err) } @@ -259,7 +258,7 @@ func TestCreateContainerFromPrivateRegistry(t *testing.T) { ContainerRequest: req, Started: true, }) - terminateContainerOnEnd(t, ctx, redisContainer) + CleanupContainer(t, redisContainer) require.NoError(t, err) } @@ -298,6 +297,7 @@ func prepareLocalRegistryWithAuth(t *testing.T) string { } registryC, err := GenericContainer(ctx, genContainerReq) + CleanupContainer(t, registryC) require.NoError(t, err) mappedPort, err := registryC.MappedPort(ctx, "5000/tcp") @@ -310,9 +310,6 @@ func prepareLocalRegistryWithAuth(t *testing.T) string { t.Cleanup(func() { removeImageFromLocalCache(t, addr+"/redis:5.0-alpine") }) - t.Cleanup(func() { - require.NoError(t, registryC.Terminate(context.Background())) - }) _, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) diff --git a/docker_exec_test.go b/docker_exec_test.go index 11f187c226e..65f9e71e072 100644 --- a/docker_exec_test.go +++ b/docker_exec_test.go @@ -51,19 +51,18 @@ func TestExecWithOptions(t *testing.T) { Image: nginxAlpineImage, } - container, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: req, Started: true, }) - + CleanupContainer(t, ctr) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, container) // always append the multiplexed option for having the output // in a readable format tt.opts = append(tt.opts, tcexec.Multiplexed()) - code, reader, err := container.Exec(ctx, tt.cmds, tt.opts...) + code, reader, err := ctr.Exec(ctx, tt.cmds, tt.opts...) require.NoError(t, err) require.Zero(t, code) require.NotNil(t, reader) @@ -84,15 +83,14 @@ func TestExecWithMultiplexedResponse(t *testing.T) { Image: nginxAlpineImage, } - container, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: req, Started: true, }) - + CleanupContainer(t, ctr) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, container) - code, reader, err := container.Exec(ctx, []string{"sh", "-c", "echo stdout; echo stderr >&2"}, tcexec.Multiplexed()) + code, reader, err := ctr.Exec(ctx, []string{"sh", "-c", "echo stdout; echo stderr >&2"}, tcexec.Multiplexed()) require.NoError(t, err) require.Zero(t, code) require.NotNil(t, reader) @@ -112,15 +110,14 @@ func TestExecWithNonMultiplexedResponse(t *testing.T) { Image: nginxAlpineImage, } - container, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: req, Started: true, }) - + CleanupContainer(t, ctr) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, container) - code, reader, err := container.Exec(ctx, []string{"sh", "-c", "echo stdout; echo stderr >&2"}) + code, reader, err := ctr.Exec(ctx, []string{"sh", "-c", "echo stdout; echo stderr >&2"}) require.NoError(t, err) require.Zero(t, code) require.NotNil(t, reader) diff --git a/docker_files_test.go b/docker_files_test.go index 6fcfc92a0b0..6a767e61637 100644 --- a/docker_files_test.go +++ b/docker_files_test.go @@ -28,7 +28,7 @@ func TestCopyFileToContainer(t *testing.T) { t.Fatal(err) } - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ Image: "docker.io/bash", Files: []testcontainers.ContainerFile{ @@ -45,9 +45,8 @@ func TestCopyFileToContainer(t *testing.T) { Started: true, }) // } - + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - require.NoError(t, container.Terminate(ctx)) } func TestCopyFileToRunningContainer(t *testing.T) { @@ -65,7 +64,7 @@ func TestCopyFileToRunningContainer(t *testing.T) { t.Fatal(err) } - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ Image: "docker.io/bash:5.2.26", Files: []testcontainers.ContainerFile{ @@ -79,20 +78,17 @@ func TestCopyFileToRunningContainer(t *testing.T) { }, Started: true, }) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - err = container.CopyFileToContainer(ctx, helloPath, "/scripts/hello.sh", 0o700) + err = ctr.CopyFileToContainer(ctx, helloPath, "/scripts/hello.sh", 0o700) // } require.NoError(t, err) // Give some time to the wait script to catch the hello script being created - err = wait.ForLog("done").WithStartupTimeout(2*time.Second).WaitUntilReady(ctx, container) + err = wait.ForLog("done").WithStartupTimeout(2*time.Second).WaitUntilReady(ctx, ctr) require.NoError(t, err) - - require.NoError(t, container.Terminate(ctx)) } func TestCopyDirectoryToContainer(t *testing.T) { @@ -106,7 +102,7 @@ func TestCopyDirectoryToContainer(t *testing.T) { t.Fatal(err) } - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ Image: "docker.io/bash", Files: []testcontainers.ContainerFile{ @@ -125,9 +121,8 @@ func TestCopyDirectoryToContainer(t *testing.T) { Started: true, }) // } - + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - require.NoError(t, container.Terminate(ctx)) } func TestCopyDirectoryToRunningContainerAsFile(t *testing.T) { @@ -144,7 +139,7 @@ func TestCopyDirectoryToRunningContainerAsFile(t *testing.T) { t.Fatal(err) } - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ Image: "docker.io/bash", Files: []testcontainers.ContainerFile{ @@ -158,25 +153,17 @@ func TestCopyDirectoryToRunningContainerAsFile(t *testing.T) { }, Started: true, }) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // as the container is started, we can create the directory first - _, _, err = container.Exec(ctx, []string{"mkdir", "-p", "/scripts"}) - if err != nil { - t.Fatal(err) - } + _, _, err = ctr.Exec(ctx, []string{"mkdir", "-p", "/scripts"}) + require.NoError(t, err) // because the container path is a directory, it will use the copy dir method as fallback - err = container.CopyFileToContainer(ctx, dataDirectory, "/scripts", 0o700) - if err != nil { - t.Fatal(err) - } - // } - + err = ctr.CopyFileToContainer(ctx, dataDirectory, "/scripts", 0o700) require.NoError(t, err) - require.NoError(t, container.Terminate(ctx)) + // } } func TestCopyDirectoryToRunningContainerAsDir(t *testing.T) { @@ -194,7 +181,7 @@ func TestCopyDirectoryToRunningContainerAsDir(t *testing.T) { t.Fatal(err) } - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ Image: "docker.io/bash", Files: []testcontainers.ContainerFile{ @@ -208,22 +195,14 @@ func TestCopyDirectoryToRunningContainerAsDir(t *testing.T) { }, Started: true, }) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // as the container is started, we can create the directory first - _, _, err = container.Exec(ctx, []string{"mkdir", "-p", "/scripts"}) - if err != nil { - t.Fatal(err) - } - - err = container.CopyDirToContainer(ctx, dataDirectory, "/scripts", 0o700) - if err != nil { - t.Fatal(err) - } - // } + _, _, err = ctr.Exec(ctx, []string{"mkdir", "-p", "/scripts"}) + require.NoError(t, err) + err = ctr.CopyDirToContainer(ctx, dataDirectory, "/scripts", 0o700) require.NoError(t, err) - require.NoError(t, container.Terminate(ctx)) + // } } diff --git a/docker_test.go b/docker_test.go index 402d944dce2..fdc3196daec 100644 --- a/docker_test.go +++ b/docker_test.go @@ -82,8 +82,8 @@ func TestContainerWithHostNetworkOptions(t *testing.T) { } nginxC, err := GenericContainer(ctx, gcr) + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) // host, err := nginxC.Host(ctx) // if err != nil { @@ -113,12 +113,11 @@ func TestContainerWithHostNetworkOptions_UseExposePortsFromImageConfigs(t *testi } nginxC, err := GenericContainer(ctx, gcr) + CleanupContainer(t, nginxC) if err != nil { t.Fatal(err) } - terminateContainerOnEnd(t, ctx, nginxC) - endpoint, err := nginxC.Endpoint(ctx, "http") if err != nil { t.Errorf("Expected server endpoint. Got '%v'.", err) @@ -158,11 +157,11 @@ func TestContainerWithNetworkModeAndNetworkTogether(t *testing.T) { } nginx, err := GenericContainer(ctx, gcr) + CleanupContainer(t, nginx) if err != nil { // Error when NetworkMode = host and Network = []string{"bridge"} t.Logf("Can't use Network and NetworkMode together, %s\n", err) } - terminateContainerOnEnd(t, ctx, nginx) } func TestContainerWithHostNetwork(t *testing.T) { @@ -197,9 +196,8 @@ func TestContainerWithHostNetwork(t *testing.T) { } nginxC, err := GenericContainer(ctx, gcr) - + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) portEndpoint, err := nginxC.PortEndpoint(ctx, nginxHighPort, "http") if err != nil { @@ -234,9 +232,8 @@ func TestContainerReturnItsContainerID(t *testing.T) { }, }, }) - + CleanupContainer(t, nginxA) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxA) if nginxA.GetContainerID() == "" { t.Errorf("expected a containerID but we got an empty string.") @@ -256,21 +253,16 @@ func TestContainerTerminationResetsState(t *testing.T) { }, Started: true, }) - if err != nil { - t.Fatal(err) - } + CleanupContainer(t, nginxA) + require.NoError(t, err) err = nginxA.Terminate(ctx) - if err != nil { - t.Fatal(err) - } - if nginxA.SessionID() != "" { - t.Fatal("Internal state must be reset.") - } + require.NoError(t, err) + require.Empty(t, nginxA.SessionID()) + inspect, err := nginxA.Inspect(ctx) - if err == nil || inspect != nil { - t.Fatal("expected error from container inspect.") - } + require.Error(t, err) + require.Nil(t, inspect) } func TestContainerStateAfterTermination(t *testing.T) { @@ -290,15 +282,12 @@ func TestContainerStateAfterTermination(t *testing.T) { t.Run("Nil State after termination", func(t *testing.T) { ctx := context.Background() nginx, err := createContainerFn(ctx) - if err != nil { - t.Fatal(err) - } + CleanupContainer(t, nginx) + require.NoError(t, err) // terminate the container before the raw state is set err = nginx.Terminate(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) state, err := nginx.State(ctx) require.Error(t, err, "expected error from container inspect.") @@ -309,25 +298,20 @@ func TestContainerStateAfterTermination(t *testing.T) { t.Run("Nil State after termination if raw as already set", func(t *testing.T) { ctx := context.Background() nginx, err := createContainerFn(ctx) - if err != nil { - t.Fatal(err) - } + CleanupContainer(t, nginx) + require.NoError(t, err) state, err := nginx.State(ctx) require.NoError(t, err, "unexpected error from container inspect before container termination.") - - assert.NotNil(t, state, "unexpected nil container inspect before container termination.") + require.NotNil(t, state, "unexpected nil container inspect before container termination.") // terminate the container before the raw state is set err = nginx.Terminate(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) state, err = nginx.State(ctx) require.Error(t, err, "expected error from container inspect after container termination.") - - assert.Nil(t, state, "unexpected nil container inspect after container termination.") + require.Nil(t, state, "unexpected nil container inspect after container termination.") }) } @@ -350,13 +334,12 @@ func TestContainerTerminationRemovesDockerImage(t *testing.T) { }, Started: true, }) - if err != nil { - t.Fatal(err) - } + CleanupContainer(t, ctr) + require.NoError(t, err) + err = ctr.Terminate(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + _, _, err = dockerClient.ImageInspectWithRaw(ctx, nginxAlpineImage) if err != nil { t.Fatal("nginx image should not have been removed") @@ -383,6 +366,7 @@ func TestContainerTerminationRemovesDockerImage(t *testing.T) { ContainerRequest: req, Started: true, }) + CleanupContainer(t, ctr) if err != nil { t.Fatal(err) } @@ -418,9 +402,8 @@ func TestTwoContainersExposingTheSamePort(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxA) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxA) nginxB, err := GenericContainer(ctx, GenericContainerRequest{ ProviderType: providerType, @@ -433,9 +416,8 @@ func TestTwoContainersExposingTheSamePort(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxB) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxB) endpointA, err := nginxA.PortEndpoint(ctx, nginxDefaultPort, "http") require.NoError(t, err) @@ -480,9 +462,8 @@ func TestContainerCreation(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) endpoint, err := nginxC.PortEndpoint(ctx, nginxDefaultPort, "http") require.NoError(t, err) @@ -536,9 +517,8 @@ func TestContainerCreationWithName(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) inspect, err := nginxC.Inspect(ctx) if err != nil { @@ -597,9 +577,8 @@ func TestContainerCreationAndWaitForListeningPortLongEnough(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) origin, err := nginxC.PortEndpoint(ctx, nginxDefaultPort, "http") if err != nil { @@ -630,8 +609,7 @@ func TestContainerCreationTimesOut(t *testing.T) { }, Started: true, }) - - terminateContainerOnEnd(t, ctx, nginxC) + CleanupContainer(t, nginxC) if err == nil { t.Error("Expected timeout") @@ -652,9 +630,8 @@ func TestContainerRespondsWithHttp200ForIndex(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) origin, err := nginxC.PortEndpoint(ctx, nginxDefaultPort, "http") if err != nil { @@ -685,8 +662,7 @@ func TestContainerCreationTimesOutWithHttp(t *testing.T) { }, Started: true, }) - terminateContainerOnEnd(t, ctx, nginxC) - + CleanupContainer(t, nginxC) if err == nil { t.Error("Expected timeout") } @@ -708,11 +684,10 @@ func TestContainerCreationWaitsForLogContextTimeout(t *testing.T) { ContainerRequest: req, Started: true, }) + CleanupContainer(t, c) if err == nil { t.Error("Expected timeout") } - - terminateContainerOnEnd(t, ctx, c) } func TestContainerCreationWaitsForLog(t *testing.T) { @@ -731,9 +706,8 @@ func TestContainerCreationWaitsForLog(t *testing.T) { ContainerRequest: req, Started: true, }) - + CleanupContainer(t, mysqlC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, mysqlC) } func Test_BuildContainerFromDockerfileWithBuildArgs(t *testing.T) { @@ -761,9 +735,8 @@ func Test_BuildContainerFromDockerfileWithBuildArgs(t *testing.T) { } c, err := GenericContainer(ctx, genContainerReq) - + CleanupContainer(t, c) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c) ep, err := c.Endpoint(ctx, "http") require.NoError(t, err) @@ -779,13 +752,16 @@ func Test_BuildContainerFromDockerfileWithBuildArgs(t *testing.T) { } func Test_BuildContainerFromDockerfileWithBuildLog(t *testing.T) { - rescueStdout := os.Stderr - r, w, _ := os.Pipe() + r, w, err := os.Pipe() + require.NoError(t, err) + + oldStderr := os.Stderr os.Stderr = w + t.Cleanup(func() { + os.Stderr = oldStderr + }) - t.Log("getting ctx") ctx := context.Background() - t.Log("got ctx, creating container request") // fromDockerfile { req := ContainerRequest{ @@ -804,17 +780,19 @@ func Test_BuildContainerFromDockerfileWithBuildLog(t *testing.T) { } c, err := GenericContainer(ctx, genContainerReq) + CleanupContainer(t, c) + require.NoError(t, err) + + err = w.Close() + require.NoError(t, err) + out, err := io.ReadAll(r) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c) - _ = w.Close() - out, _ := io.ReadAll(r) - os.Stdout = rescueStdout temp := strings.Split(string(out), "\n") if !regexp.MustCompile(`^Step\s*1/\d+\s*:\s*FROM docker.io/alpine$`).MatchString(temp[0]) { - t.Errorf("Expected stdout firstline to be %s. Got '%s'.", "Step 1/* : FROM docker.io/alpine", temp[0]) + t.Errorf("Expected stdout first line to be %s. Got '%s'.", "Step 1/* : FROM docker.io/alpine", temp[0]) } } @@ -837,11 +815,10 @@ func TestContainerCreationWaitsForLogAndPortContextTimeout(t *testing.T) { ContainerRequest: req, Started: true, }) + CleanupContainer(t, c) if err == nil { t.Fatal("Expected timeout") } - - terminateContainerOnEnd(t, ctx, c) } func TestContainerCreationWaitingForHostPort(t *testing.T) { @@ -858,9 +835,8 @@ func TestContainerCreationWaitingForHostPort(t *testing.T) { ContainerRequest: req, Started: true, }) - + CleanupContainer(t, nginx) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginx) } func TestContainerCreationWaitingForHostPortWithoutBashThrowsAnError(t *testing.T) { @@ -875,9 +851,8 @@ func TestContainerCreationWaitingForHostPortWithoutBashThrowsAnError(t *testing. ContainerRequest: req, Started: true, }) - + CleanupContainer(t, nginx) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginx) } func TestCMD(t *testing.T) { @@ -902,9 +877,8 @@ func TestCMD(t *testing.T) { ContainerRequest: req, Started: true, }) - + CleanupContainer(t, c) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c) } func TestEntrypoint(t *testing.T) { @@ -929,9 +903,8 @@ func TestEntrypoint(t *testing.T) { ContainerRequest: req, Started: true, }) - + CleanupContainer(t, c) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c) } func TestWorkingDir(t *testing.T) { @@ -957,9 +930,8 @@ func TestWorkingDir(t *testing.T) { ContainerRequest: req, Started: true, }) - + CleanupContainer(t, c) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c) } func ExampleDockerProvider_CreateContainer() { @@ -969,19 +941,24 @@ func ExampleDockerProvider_CreateContainer() { ExposedPorts: []string{"80/tcp"}, WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second), } - nginxC, _ := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: req, Started: true, }) defer func() { - if err := nginxC.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := TerminateContainer(nginxC); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to create container: %s", err) + return + } state, err := nginxC.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -997,28 +974,38 @@ func ExampleContainer_Host() { ExposedPorts: []string{"80/tcp"}, WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second), } - nginxC, _ := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: req, Started: true, }) defer func() { - if err := nginxC.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := TerminateContainer(nginxC); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to create container: %s", err) + return + } // containerHost { - ip, _ := nginxC.Host(ctx) + ip, err := nginxC.Host(ctx) + if err != nil { + log.Printf("failed to create container: %s", err) + return + } // } - println(ip) + fmt.Println(ip) state, err := nginxC.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) // Output: + // localhost // true } @@ -1029,19 +1016,28 @@ func ExampleContainer_Start() { ExposedPorts: []string{"80/tcp"}, WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second), } - nginxC, _ := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: req, }) defer func() { - if err := nginxC.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := TerminateContainer(nginxC); err != nil { + log.Printf("failed to terminate container: %s", err) } }() - _ = nginxC.Start(ctx) + if err != nil { + log.Printf("failed to create container: %s", err) + return + } + + if err = nginxC.Start(ctx); err != nil { + log.Printf("failed to start container: %s", err) + return + } state, err := nginxC.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -1057,19 +1053,24 @@ func ExampleContainer_Stop() { ExposedPorts: []string{"80/tcp"}, WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second), } - nginxC, _ := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: req, }) defer func() { - if err := nginxC.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := TerminateContainer(nginxC); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to create and start container: %s", err) + return + } + fmt.Println("Container has been started") timeout := 10 * time.Second - err := nginxC.Stop(ctx, &timeout) - if err != nil { - log.Fatalf("failed to stop container: %s", err) // nolint:gocritic + if err = nginxC.Stop(ctx, &timeout); err != nil { + log.Printf("failed to terminate container: %s", err) + return } fmt.Println("Container has been stopped") @@ -1086,15 +1087,20 @@ func ExampleContainer_MappedPort() { ExposedPorts: []string{"80/tcp"}, WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second), } - nginxC, _ := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: req, Started: true, }) defer func() { - if err := nginxC.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := TerminateContainer(nginxC); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to create and start container: %s", err) + return + } + // buildingAddresses { ip, _ := nginxC.Host(ctx) port, _ := nginxC.MappedPort(ctx, "80") @@ -1103,7 +1109,8 @@ func ExampleContainer_MappedPort() { state, err := nginxC.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -1140,9 +1147,8 @@ func TestContainerCreationWithVolumeAndFileWritingToIt(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, bashC, RemoveVolumes(volumeName)) require.NoError(t, err) - require.NoError(t, bashC.Terminate(ctx)) } func TestContainerWithTmpFs(t *testing.T) { @@ -1158,9 +1164,8 @@ func TestContainerWithTmpFs(t *testing.T) { ContainerRequest: req, Started: true, }) - + CleanupContainer(t, ctr) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, ctr) path := "/testtmpfs/test.file" @@ -1204,12 +1209,13 @@ func TestContainerWithTmpFs(t *testing.T) { func TestContainerNonExistentImage(t *testing.T) { t.Run("if the image not found don't propagate the error", func(t *testing.T) { - _, err := GenericContainer(context.Background(), GenericContainerRequest{ + ctr, err := GenericContainer(context.Background(), GenericContainerRequest{ ContainerRequest: ContainerRequest{ Image: "postgres:nonexistent-version", }, Started: true, }) + CleanupContainer(t, ctr) var nf errdefs.ErrNotFound if !errors.As(err, &nf) { @@ -1228,11 +1234,10 @@ func TestContainerNonExistentImage(t *testing.T) { }, Started: true, }) + CleanupContainer(t, c) if !errors.Is(err, ctx.Err()) { t.Fatalf("err should be a ctx cancelled error %v", err) } - - terminateContainerOnEnd(t, context.Background(), c) // use non-cancelled context }) } @@ -1253,9 +1258,7 @@ func TestContainerCustomPlatformImage(t *testing.T) { }, Started: false, }) - - terminateContainerOnEnd(t, ctx, c) - + CleanupContainer(t, c) require.Error(t, err) }) @@ -1271,9 +1274,8 @@ func TestContainerCustomPlatformImage(t *testing.T) { }, Started: false, }) - + CleanupContainer(t, c) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c) dockerCli, err := NewDockerClientWithOpts(ctx) require.NoError(t, err) @@ -1303,9 +1305,8 @@ func TestContainerWithCustomHostname(t *testing.T) { ContainerRequest: req, Started: true, }) - + CleanupContainer(t, ctr) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, ctr) if actualHostname := readHostname(t, ctr.GetContainerID()); actualHostname != hostname { t.Fatalf("expected hostname %s, got %s", hostname, actualHostname) @@ -1319,8 +1320,8 @@ func TestContainerInspect_RawInspectIsCleanedOnStop(t *testing.T) { }, Started: true, }) + CleanupContainer(t, ctr) require.NoError(t, err) - terminateContainerOnEnd(t, context.Background(), ctr) inspect, err := ctr.Inspect(context.Background()) require.NoError(t, err) @@ -1373,9 +1374,8 @@ func TestDockerContainerCopyFileToContainer(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) _ = nginxC.CopyFileToContainer(ctx, filepath.Join(".", "testdata", "hello.sh"), tc.copiedFileName, 700) c, _, err := nginxC.Exec(ctx, []string{"bash", tc.copiedFileName}) @@ -1401,11 +1401,10 @@ func TestDockerContainerCopyDirToContainer(t *testing.T) { }, Started: true, }) - - p := filepath.Join(".", "testdata", "Dokerfile") + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) + p := filepath.Join(".", "testdata", "Dokerfile") err = nginxC.CopyDirToContainer(ctx, p, "/tmp/testdata/Dockerfile", 700) require.Error(t, err) // copying a file using the directory method will raise an error @@ -1462,7 +1461,7 @@ func TestDockerCreateContainerWithFiles(t *testing.T) { }, Started: false, }) - terminateContainerOnEnd(t, ctx, nginxC) + CleanupContainer(t, nginxC) if err != nil { require.Contains(t, err.Error(), tc.errMsg) @@ -1547,7 +1546,7 @@ func TestDockerCreateContainerWithDirs(t *testing.T) { }, Started: false, }) - terminateContainerOnEnd(t, ctx, nginxC) + CleanupContainer(t, nginxC) require.Equal(t, (err != nil), tc.hasError) if err == nil { @@ -1587,9 +1586,8 @@ func TestDockerContainerCopyToContainer(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) fileContent, err := os.ReadFile(filepath.Join(".", "testdata", "hello.sh")) if err != nil { @@ -1626,9 +1624,8 @@ func TestDockerContainerCopyFileFromContainer(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) copiedFileName := "hello_copy.sh" _ = nginxC.CopyFileToContainer(ctx, filepath.Join(".", "testdata", "hello.sh"), "/"+copiedFileName, 700) @@ -1665,9 +1662,8 @@ func TestDockerContainerCopyEmptyFileFromContainer(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) copiedFileName := "hello_copy.sh" _ = nginxC.CopyFileToContainer(ctx, filepath.Join(".", "testdata", "empty.sh"), "/"+copiedFileName, 700) @@ -1729,9 +1725,8 @@ func TestDockerContainerResources(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxC) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxC) c, err := NewDockerClientWithOpts(ctx) require.NoError(t, err) @@ -1766,8 +1761,8 @@ func TestContainerCapAdd(t *testing.T) { }, Started: true, }) + CleanupContainer(t, nginx) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginx) dockerClient, err := NewDockerClientWithOpts(ctx) require.NoError(t, err) @@ -1799,11 +1794,10 @@ func TestContainerRunningCheckingStatusCode(t *testing.T) { ContainerRequest: req, Started: true, }) + CleanupContainer(t, influx) if err != nil { t.Fatal(err) } - - terminateContainerOnEnd(t, ctx, influx) } func TestContainerWithUserID(t *testing.T) { @@ -1819,9 +1813,8 @@ func TestContainerWithUserID(t *testing.T) { ContainerRequest: req, Started: true, }) - + CleanupContainer(t, ctr) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, ctr) r, err := ctr.Logs(ctx) if err != nil { @@ -1848,9 +1841,8 @@ func TestContainerWithNoUserID(t *testing.T) { ContainerRequest: req, Started: true, }) - + CleanupContainer(t, ctr) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, ctr) r, err := ctr.Logs(ctx) if err != nil { @@ -1892,9 +1884,8 @@ func TestNetworkModeWithContainerReference(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxA) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxA) networkMode := fmt.Sprintf("container:%v", nginxA.GetContainerID()) nginxB, err := GenericContainer(ctx, GenericContainerRequest{ @@ -1907,9 +1898,8 @@ func TestNetworkModeWithContainerReference(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxB) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxB) } // creates a temporary dir in which the files will be extracted. Then it will compare the bytes of each file in the source with the bytes from the copied-from-container file @@ -1957,16 +1947,6 @@ func assertExtractedFiles(t *testing.T, ctx context.Context, container Container } } -func terminateContainerOnEnd(tb testing.TB, ctx context.Context, ctr Container) { - tb.Helper() - if ctr == nil { - return - } - tb.Cleanup(func() { - require.NoError(tb, ctr.Terminate(ctx)) - }) -} - func TestDockerProviderFindContainerByName(t *testing.T) { ctx := context.Background() provider, err := NewDockerProvider(WithLogger(TestLogger(t))) @@ -1982,11 +1962,12 @@ func TestDockerProviderFindContainerByName(t *testing.T) { }, Started: true, }) + CleanupContainer(t, c1) require.NoError(t, err) c1Inspect, err := c1.Inspect(ctx) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c1) + CleanupContainer(t, c1) c1Name := c1Inspect.Name @@ -1999,8 +1980,8 @@ func TestDockerProviderFindContainerByName(t *testing.T) { }, Started: true, }) + CleanupContainer(t, c2) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c2) c, err := provider.findContainerByName(ctx, "test") require.NoError(t, err) @@ -2035,8 +2016,8 @@ func TestImageBuiltFromDockerfile_KeepBuiltImage(t *testing.T) { }, }, }) + CleanupContainer(t, c) require.NoError(t, err, "create container should not fail") - defer func() { _ = c.Terminate(context.Background()) }() // Get the image ID. containerInspect, err := c.Inspect(ctx) require.NoError(t, err, "container inspect should not fail") @@ -2058,7 +2039,7 @@ func TestImageBuiltFromDockerfile_KeepBuiltImage(t *testing.T) { if tt.keepBuiltImage { require.NoError(t, err, "image should still exist") } else { - require.Error(t, err, "image should not exist anymore") + require.Error(t, err, "image should not exist any more") } }) } @@ -2295,15 +2276,11 @@ func TestCustomPrefixTrailingSlashIsProperlyRemovedIfPresent(t *testing.T) { ContainerRequest: req, Started: true, }) - if err != nil { - t.Fatal(err) - } - defer func() { - terminateContainerOnEnd(t, ctx, c) - }() + CleanupContainer(t, c) + require.NoError(t, err) // enforce the concrete type, as GenericContainer returns an interface, // which will be changed in future implementations of the library dockerContainer := c.(*DockerContainer) - assert.Equal(t, fmt.Sprintf("%s%s", hubPrefixWithTrailingSlash, dockerImage), dockerContainer.Image) + require.Equal(t, fmt.Sprintf("%s%s", hubPrefixWithTrailingSlash, dockerImage), dockerContainer.Image) } diff --git a/docs/features/common_functional_options.md b/docs/features/common_functional_options.md index d559a3ee7ff..18d0e4b0070 100644 --- a/docs/features/common_functional_options.md +++ b/docs/features/common_functional_options.md @@ -70,7 +70,8 @@ useful context instead of appearing out of band. ```golang func TestHandler(t *testing.T) { logger := TestLogger(t) - _, err := postgresModule.Run(ctx, "postgres:15-alpine", testcontainers.WithLogger(logger)) + ctr, err := postgresModule.Run(ctx, "postgres:15-alpine", testcontainers.WithLogger(logger)) + CleanupContainer(t, ctr) require.NoError(t, err) // Do something with container. } diff --git a/docs/features/creating_container.md b/docs/features/creating_container.md index 30264a05da6..ec33bdb0143 100644 --- a/docs/features/creating_container.md +++ b/docs/features/creating_container.md @@ -11,7 +11,15 @@ up with Testcontainers and integrate into your tests: `testcontainers.GenericContainer` defines the container that should be run, similar to the `docker run` command. -The following test creates an NGINX container and validates that it returns 200 for the status code: +The following test creates an NGINX container on both the `bridge` (docker default +network) and the `foo` network and validates that it returns 200 for the status code. + +It also demonstrates how to use `CleanupContainer` ensures that nginx container +is removed when the test ends even if the underlying `GenericContainer` errored +as well as the `CleanupNetwork` which does the same for networks. + +The alternatives for these outside of tests as a `defer` are `TerminateContainer` +and `Network.Remove` which can be seen in the examples. ```go package main @@ -32,33 +40,38 @@ type nginxContainer struct { } -func setupNginx(ctx context.Context) (*nginxContainer, error) { +func setupNginx(ctx context.Context, networkName string) (*nginxContainer, error) { req := testcontainers.ContainerRequest{ Image: "nginx", ExposedPorts: []string{"80/tcp"}, + Networks: []string{"bridge", networkName}, WaitingFor: wait.ForHTTP("/"), } container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) + var nginxC *nginxContainer + if container != nil { + nginxC = &nginxContainer{Container: c} + } if err != nil { - return nil, err + return nginxC, err } ip, err := container.Host(ctx) if err != nil { - return nil, err + return nginxC, err } mappedPort, err := container.MappedPort(ctx, "80") if err != nil { - return nil, err + return nginxC, err } - uri := fmt.Sprintf("http://%s:%s", ip, mappedPort.Port()) + nginxC.URI = fmt.Sprintf("http://%s:%s", ip, mappedPort.Port()) - return &nginxContainer{Container: container, URI: uri}, nil + return nginxC, nil } func TestIntegrationNginxLatestReturn(t *testing.T) { @@ -68,25 +81,25 @@ func TestIntegrationNginxLatestReturn(t *testing.T) { ctx := context.Background() - nginxC, err := setupNginx(ctx) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := nginxC.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } + networkName := "foo" + net, err := provider.CreateNetwork(ctx, NetworkRequest{ + Name: networkName, }) + require.NoError(t, err) + CleanupNetwork(t, net) + + nginxC, err := setupNginx(ctx, networkName) + testcontainers.CleanupContainer(t, nginxC) + require.NoError(t, err) resp, err := http.Get(nginxC.URI) - if resp.StatusCode != http.StatusOK { - t.Fatalf("Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) - } + require.Equal(t, http.StatusOK, resp.StatusCode) } ``` + + + ### Lifecycle hooks _Testcontainers for Go_ allows you to define your own lifecycle hooks for better control over your containers. You just need to define functions that return an error and receive the Go context as first argument, and a `ContainerRequest` for the `Creating` hook, and a `Container` for the rest of them as second argument. @@ -145,8 +158,8 @@ The aforementioned `GenericContainer` function and the `ContainerRequest` struct ## Reusable container -With `Reuse` option you can reuse an existing container. Reusing will work only if you pass an -existing container name via 'req.Name' field. If the name is not in a list of existing containers, +With `Reuse` option you can reuse an existing container. Reusing will work only if you pass an +existing container name via 'req.Name' field. If the name is not in a list of existing containers, the function will create a new generic container. If `Reuse` is true and `Name` is empty, you will get error. The following test creates an NGINX container, adds a file into it and then reuses the container again for checking the file: @@ -178,16 +191,22 @@ func main() { }, Started: true, }) + defer func() { + if err := testcontainers.TerminateContainer(n1); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() if err != nil { - log.Fatal(err) + log.Print(err) + return } - defer n1.Terminate(ctx) copiedFileName := "hello_copy.sh" err = n1.CopyFileToContainer(ctx, "./testdata/hello.sh", "/"+copiedFileName, 700) if err != nil { - log.Fatal(err) + log.Print(err) + return } n2, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ @@ -200,13 +219,20 @@ func main() { Started: true, Reuse: true, }) + defer func() { + if err := testcontainers.TerminateContainer(n2); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() if err != nil { - log.Fatal(err) + log.Print(err) + return } c, _, err := n2.Exec(ctx, []string{"bash", copiedFileName}) if err != nil { - log.Fatal(err) + log.Print(err) + return } fmt.Println(c) } @@ -256,10 +282,20 @@ func main() { } res, err := testcontainers.ParallelContainers(ctx, requests, testcontainers.ParallelContainersOptions{}) + for _, c := range res { + c := c + defer func() { + if err := testcontainers.TerminateContainer(c); err != nil { + log.Printf("failed to terminate container: %s", c) + } + }() + } + if err != nil { e, ok := err.(testcontainers.ParallelContainersError) if !ok { - log.Fatalf("unknown error: %v", err) + log.Printf("unknown error: %v", err) + return } for _, pe := range e.Errors { @@ -267,14 +303,5 @@ func main() { } return } - - for _, c := range res { - c := c - defer func() { - if err := c.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", c) - } - }() - } } ``` diff --git a/docs/quickstart.md b/docs/quickstart.md index 5660e357577..ed6bbfcd4a3 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -9,7 +9,7 @@ Please read the [system requirements](../system_requirements/) page before you s ## 2. Install _Testcontainers for Go_ -We use [gomod](https://blog.golang.org/using-go-modules) and you can get it installed via: +We use [go mod](https://blog.golang.org/using-go-modules) and you can get it installed via: ``` go get github.com/testcontainers/testcontainers-go @@ -22,6 +22,8 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) @@ -37,14 +39,8 @@ func TestWithRedis(t *testing.T) { ContainerRequest: req, Started: true, }) - if err != nil { - t.Fatalf("Could not start redis: %s", err) - } - defer func() { - if err := redisC.Terminate(ctx); err != nil { - t.Fatalf("Could not stop redis: %s", err) - } - }() + testcontainers.CleanupContainer(t, redisC) + require.NoError(t, err) } ``` @@ -75,7 +71,8 @@ start, leaving to you the decision about when to start it. All the containers must be removed at some point, otherwise they will run until the host is overloaded. One of the ways we have to clean up is by deferring the -terminated function: `defer redisC.Terminate(ctx)`. +terminated function: `defer testcontainers.TerminateContainer(redisC)` which +automatically handles nil container so is safe to use even in the error case. !!!tip diff --git a/examples/nginx/go.mod b/examples/nginx/go.mod index a6f67e793c9..4b4830eaa39 100644 --- a/examples/nginx/go.mod +++ b/examples/nginx/go.mod @@ -2,7 +2,10 @@ module github.com/testcontainers/testcontainers-go/examples/nginx go 1.22 -require github.com/testcontainers/testcontainers-go v0.33.0 +require ( + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 +) replace github.com/testcontainers/testcontainers-go => ../.. @@ -15,6 +18,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect @@ -26,6 +30,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.4 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -37,6 +42,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -53,4 +59,5 @@ require ( golang.org/x/sys v0.21.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/examples/nginx/go.sum b/examples/nginx/go.sum index ed514ea5ef8..28367d00203 100644 --- a/examples/nginx/go.sum +++ b/examples/nginx/go.sum @@ -16,6 +16,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -52,6 +53,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -78,6 +83,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -174,6 +181,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/nginx/nginx.go b/examples/nginx/nginx.go index fd0e530ea67..6a5718c3e0a 100644 --- a/examples/nginx/nginx.go +++ b/examples/nginx/nginx.go @@ -24,21 +24,24 @@ func startContainer(ctx context.Context) (*nginxContainer, error) { ContainerRequest: req, Started: true, }) + var nginxC *nginxContainer + if container != nil { + nginxC = &nginxContainer{Container: container} + } if err != nil { - return nil, err + return nginxC, err } ip, err := container.Host(ctx) if err != nil { - return nil, err + return nginxC, err } mappedPort, err := container.MappedPort(ctx, "80") if err != nil { - return nil, err + return nginxC, err } - uri := fmt.Sprintf("http://%s:%s", ip, mappedPort.Port()) - - return &nginxContainer{Container: container, URI: uri}, nil + nginxC.URI = fmt.Sprintf("http://%s:%s", ip, mappedPort.Port()) + return nginxC, nil } diff --git a/examples/nginx/nginx_test.go b/examples/nginx/nginx_test.go index 3d7b8ada48c..fe662daf07e 100644 --- a/examples/nginx/nginx_test.go +++ b/examples/nginx/nginx_test.go @@ -4,6 +4,10 @@ import ( "context" "net/http" "testing" + + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" ) func TestIntegrationNginxLatestReturn(t *testing.T) { @@ -14,23 +18,10 @@ func TestIntegrationNginxLatestReturn(t *testing.T) { ctx := context.Background() nginxC, err := startContainer(ctx) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := nginxC.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, nginxC) + require.NoError(t, err) resp, err := http.Get(nginxC.URI) - if err != nil { - t.Fatal(err) - } - - if resp.StatusCode != http.StatusOK { - t.Fatalf("Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) - } + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) } diff --git a/examples/toxiproxy/go.mod b/examples/toxiproxy/go.mod index 04be7036c13..7cdb477e6c6 100644 --- a/examples/toxiproxy/go.mod +++ b/examples/toxiproxy/go.mod @@ -6,7 +6,8 @@ require ( github.com/Shopify/toxiproxy/v2 v2.8.0 github.com/go-redis/redis/v8 v8.11.5 github.com/google/uuid v1.6.0 - github.com/testcontainers/testcontainers-go v0.33.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 ) require ( @@ -19,6 +20,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect @@ -30,6 +32,7 @@ require ( github.com/go-ole/go-ole v1.2.6 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/klauspost/compress v1.17.4 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -41,6 +44,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -57,6 +61,7 @@ require ( golang.org/x/sys v0.21.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/examples/toxiproxy/go.sum b/examples/toxiproxy/go.sum index 91a27d9fc86..8173198d1b8 100644 --- a/examples/toxiproxy/go.sum +++ b/examples/toxiproxy/go.sum @@ -20,6 +20,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -62,6 +63,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -94,6 +99,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -190,6 +197,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/examples/toxiproxy/redis.go b/examples/toxiproxy/redis.go index ead526773df..c66e52550f3 100644 --- a/examples/toxiproxy/redis.go +++ b/examples/toxiproxy/redis.go @@ -27,9 +27,9 @@ func setupRedis(ctx context.Context, network string, networkAlias []string) (*re ContainerRequest: req, Started: true, }) - if err != nil { - return nil, err + var nginxC *redisContainer + if container != nil { + nginxC = &redisContainer{Container: container} } - - return &redisContainer{Container: container}, nil + return nginxC, err } diff --git a/examples/toxiproxy/toxiproxy.go b/examples/toxiproxy/toxiproxy.go index e7903a9f998..1a226e8c61a 100644 --- a/examples/toxiproxy/toxiproxy.go +++ b/examples/toxiproxy/toxiproxy.go @@ -31,21 +31,25 @@ func startContainer(ctx context.Context, network string, networkAlias []string) ContainerRequest: req, Started: true, }) + var toxiC *toxiproxyContainer + if container != nil { + toxiC = &toxiproxyContainer{Container: container} + } if err != nil { - return nil, err + return toxiC, err } mappedPort, err := container.MappedPort(ctx, "8474") if err != nil { - return nil, err + return toxiC, err } hostIP, err := container.Host(ctx) if err != nil { - return nil, err + return toxiC, err } - uri := fmt.Sprintf("%s:%s", hostIP, mappedPort.Port()) + toxiC.URI = fmt.Sprintf("%s:%s", hostIP, mappedPort.Port()) - return &toxiproxyContainer{Container: container, URI: uri}, nil + return toxiC, nil } diff --git a/examples/toxiproxy/toxiproxy_test.go b/examples/toxiproxy/toxiproxy_test.go index 0cb3f053205..c372d739b81 100644 --- a/examples/toxiproxy/toxiproxy_test.go +++ b/examples/toxiproxy/toxiproxy_test.go @@ -9,7 +9,9 @@ import ( toxiproxy "github.com/Shopify/toxiproxy/v2/client" "github.com/go-redis/redis/v8" "github.com/google/uuid" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/network" ) @@ -17,63 +19,37 @@ func TestToxiproxy(t *testing.T) { ctx := context.Background() newNetwork, err := network.New(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + testcontainers.CleanupNetwork(t, newNetwork) networkName := newNetwork.Name toxiproxyContainer, err := startContainer(ctx, networkName, []string{"toxiproxy"}) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, toxiproxyContainer) + require.NoError(t, err) redisContainer, err := setupRedis(ctx, networkName, []string{"redis"}) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := toxiproxyContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - if err := redisContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - if err := newNetwork.Remove(ctx); err != nil { - t.Fatalf("failed to terminate network: %s", err) - } - }) + testcontainers.CleanupContainer(t, redisContainer) + require.NoError(t, err) toxiproxyClient := toxiproxy.NewClient(toxiproxyContainer.URI) proxy, err := toxiproxyClient.CreateProxy("redis", "0.0.0.0:8666", "redis:6379") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) toxiproxyProxyPort, err := toxiproxyContainer.MappedPort(ctx, "8666") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) toxiproxyProxyHostIP, err := toxiproxyContainer.Host(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) redisUri := fmt.Sprintf("redis://%s:%s?read_timeout=2s", toxiproxyProxyHostIP, toxiproxyProxyPort.Port()) options, err := redis.ParseURL(redisUri) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) redisClient := redis.NewClient(options) + defer func() { - err := flushRedis(ctx, *redisClient) - if err != nil { - t.Fatal(err) - } + require.NoError(t, flushRedis(ctx, *redisClient)) }() // Set data @@ -81,28 +57,18 @@ func TestToxiproxy(t *testing.T) { value := "Cabbage Biscuits" ttl, _ := time.ParseDuration("2h") err = redisClient.Set(ctx, key, value, ttl).Err() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) _, err = proxy.AddToxic("latency_down", "latency", "downstream", 1.0, toxiproxy.Attributes{ "latency": 1000, "jitter": 100, }) - if err != nil { - return - } + require.NoError(t, err) // Get data savedValue, err := redisClient.Get(ctx, key).Result() - if err != nil { - t.Fatal(err) - } - - // perform assertions - if savedValue != value { - t.Fatalf("Expected value %s. Got %s.", savedValue, value) - } + require.NoError(t, err) + require.Equal(t, value, savedValue) } func flushRedis(ctx context.Context, client redis.Client) error { diff --git a/from_dockerfile_test.go b/from_dockerfile_test.go index fc1d4052eae..f6f7512ae3e 100644 --- a/from_dockerfile_test.go +++ b/from_dockerfile_test.go @@ -102,12 +102,12 @@ func TestBuildImageFromDockerfile_BuildError(t *testing.T) { Context: filepath.Join(".", "testdata"), }, } - _, err = GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, }) - + CleanupContainer(t, ctr) require.EqualError(t, err, `create container: build image: The command '/bin/sh -c exit 1' returned a non-zero code: 1`) } @@ -153,10 +153,9 @@ func TestBuildImageFromDockerfile_Target(t *testing.T) { c, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: ContainerRequest{ FromDockerfile: FromDockerfile{ - Context: "testdata", - Dockerfile: "target.Dockerfile", - PrintBuildLog: true, - KeepImage: false, + Context: "testdata", + Dockerfile: "target.Dockerfile", + KeepImage: false, BuildOptionsModifier: func(buildOptions *types.ImageBuildOptions) { buildOptions.Target = fmt.Sprintf("target%d", i) }, @@ -164,6 +163,7 @@ func TestBuildImageFromDockerfile_Target(t *testing.T) { }, Started: true, }) + CleanupContainer(t, c) require.NoError(t, err) r, err := c.Logs(ctx) @@ -171,12 +171,7 @@ func TestBuildImageFromDockerfile_Target(t *testing.T) { logs, err := io.ReadAll(r) require.NoError(t, err) - - assert.Equal(t, fmt.Sprintf("target%d\n\n", i), string(logs)) - - t.Cleanup(func() { - require.NoError(t, c.Terminate(ctx)) - }) + require.Equal(t, fmt.Sprintf("target%d\n\n", i), string(logs)) } } @@ -187,10 +182,9 @@ func ExampleGenericContainer_buildFromDockerfile() { c, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: ContainerRequest{ FromDockerfile: FromDockerfile{ - Context: "testdata", - Dockerfile: "target.Dockerfile", - PrintBuildLog: true, - KeepImage: false, + Context: "testdata", + Dockerfile: "target.Dockerfile", + KeepImage: false, BuildOptionsModifier: func(buildOptions *types.ImageBuildOptions) { buildOptions.Target = "target2" }, @@ -199,18 +193,26 @@ func ExampleGenericContainer_buildFromDockerfile() { Started: true, }) // } + defer func() { + if err := TerminateContainer(c); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() if err != nil { - log.Fatalf("failed to start container: %v", err) + log.Printf("failed to start container: %v", err) + return } r, err := c.Logs(ctx) if err != nil { - log.Fatalf("failed to get logs: %v", err) + log.Printf("failed to get logs: %v", err) + return } logs, err := io.ReadAll(r) if err != nil { - log.Fatalf("failed to read logs: %v", err) + log.Printf("failed to read logs: %v", err) + return } fmt.Println(string(logs)) @@ -223,13 +225,12 @@ func TestBuildImageFromDockerfile_TargetDoesNotExist(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() - _, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: ContainerRequest{ FromDockerfile: FromDockerfile{ - Context: "testdata", - Dockerfile: "target.Dockerfile", - PrintBuildLog: true, - KeepImage: false, + Context: "testdata", + Dockerfile: "target.Dockerfile", + KeepImage: false, BuildOptionsModifier: func(buildOptions *types.ImageBuildOptions) { buildOptions.Target = "target-foo" }, @@ -237,5 +238,6 @@ func TestBuildImageFromDockerfile_TargetDoesNotExist(t *testing.T) { }, Started: true, }) + CleanupContainer(t, ctr) require.Error(t, err) } diff --git a/generic_test.go b/generic_test.go index 9116fd0f65a..99c13f5bfaa 100644 --- a/generic_test.go +++ b/generic_test.go @@ -38,7 +38,7 @@ func TestGenericReusableContainer(t *testing.T) { }) require.NoError(t, err) require.True(t, n1.IsRunning()) - terminateContainerOnEnd(t, ctx, n1) + CleanupContainer(t, n1) copiedFileName := "hello_copy.sh" err = n1.CopyFileToContainer(ctx, "./testdata/hello.sh", "/"+copiedFileName, 700) @@ -123,7 +123,7 @@ func TestGenericContainerShouldReturnRefOnError(t *testing.T) { }) require.Error(t, err) require.NotNil(t, c) - terminateContainerOnEnd(t, context.Background(), c) + CleanupContainer(t, c) } func TestGenericReusableContainerInSubprocess(t *testing.T) { @@ -160,7 +160,7 @@ func TestGenericReusableContainerInSubprocess(t *testing.T) { nginxC, err := containerFromDockerResponse(context.Background(), ctrs[0]) require.NoError(t, err) - terminateContainerOnEnd(t, context.Background(), nginxC) + CleanupContainer(t, nginxC) } func createReuseContainerInSubprocess(t *testing.T) string { diff --git a/image_substitutors_test.go b/image_substitutors_test.go index 8054ebf96cf..4a41f31ce67 100644 --- a/image_substitutors_test.go +++ b/image_substitutors_test.go @@ -149,6 +149,7 @@ func TestSubstituteBuiltImage(t *testing.T) { t.Run("should not use the properties prefix on built images", func(t *testing.T) { config.Reset() c, err := GenericContainer(context.Background(), req) + CleanupContainer(t, c) if err != nil { t.Fatal(err) } diff --git a/image_test.go b/image_test.go index 795a521b295..2344c5063d0 100644 --- a/image_test.go +++ b/image_test.go @@ -25,15 +25,12 @@ func TestImageList(t *testing.T) { Image: "redis:latest", } - container, err := provider.CreateContainer(context.Background(), req) + ctr, err := provider.CreateContainer(context.Background(), req) + CleanupContainer(t, ctr) if err != nil { t.Fatalf("creating test container %v", err) } - defer func() { - _ = container.Terminate(context.Background()) - }() - images, err := provider.ListImages(context.Background()) if err != nil { t.Fatalf("listing images %v", err) @@ -69,15 +66,12 @@ func TestSaveImages(t *testing.T) { Image: "redis:latest", } - container, err := provider.CreateContainer(context.Background(), req) + ctr, err := provider.CreateContainer(context.Background(), req) + CleanupContainer(t, ctr) if err != nil { t.Fatalf("creating test container %v", err) } - defer func() { - _ = container.Terminate(context.Background()) - }() - output := filepath.Join(t.TempDir(), "images.tar") err = provider.SaveImages(context.Background(), output, req.Image) if err != nil { diff --git a/lifecycle.go b/lifecycle.go index 40360a4c0b9..c38e60240da 100644 --- a/lifecycle.go +++ b/lifecycle.go @@ -411,10 +411,10 @@ func (c ContainerLifecycleHooks) Creating(ctx context.Context) func(req Containe // containerHookFn is a helper function that will create a function to be returned by all the different // container lifecycle hooks. The created function will iterate over all the hooks and call them one by one. func containerHookFn(ctx context.Context, containerHook []ContainerHook) func(container Container) error { - return func(container Container) error { + return func(ctr Container) error { errs := make([]error, len(containerHook)) for i, hook := range containerHook { - errs[i] = hook(ctx, container) + errs[i] = hook(ctx, ctr) } return errors.Join(errs...) diff --git a/lifecycle_test.go b/lifecycle_test.go index f4b0a2ae37e..66c4ad8a8ae 100644 --- a/lifecycle_test.go +++ b/lifecycle_test.go @@ -210,12 +210,7 @@ func TestPreCreateModifierHook(t *testing.T) { Name: networkName, }) require.NoError(t, err) - defer func() { - err := net.Remove(ctx) - if err != nil { - t.Logf("failed to remove network %s: %s\n", networkName, err) - } - }() + CleanupNetwork(t, net) dockerNetwork, err := provider.GetNetwork(ctx, NetworkRequest{ Name: networkName, @@ -262,12 +257,7 @@ func TestPreCreateModifierHook(t *testing.T) { Name: networkName, }) require.NoError(t, err) - defer func() { - err := net.Remove(ctx) - if err != nil { - t.Logf("failed to remove network %s: %s\n", networkName, err) - } - }() + CleanupNetwork(t, net) dockerNetwork, err := provider.GetNetwork(ctx, NetworkRequest{ Name: networkName, @@ -549,91 +539,91 @@ func TestLifecycleHooks(t *testing.T) { { PreCreates: []ContainerRequestHook{ func(ctx context.Context, req ContainerRequest) error { - prints = append(prints, fmt.Sprintf("pre-create hook 1: %#v", req)) + prints = append(prints, "pre-create hook 1") return nil }, func(ctx context.Context, req ContainerRequest) error { - prints = append(prints, fmt.Sprintf("pre-create hook 2: %#v", req)) + prints = append(prints, "pre-create hook 2") return nil }, }, PostCreates: []ContainerHook{ func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("post-create hook 1: %#v", c)) + prints = append(prints, "post-create hook 1") return nil }, func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("post-create hook 2: %#v", c)) + prints = append(prints, "post-create hook 2") return nil }, }, PreStarts: []ContainerHook{ func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("pre-start hook 1: %#v", c)) + prints = append(prints, "pre-start hook 1") return nil }, func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("pre-start hook 2: %#v", c)) + prints = append(prints, "pre-start hook 2") return nil }, }, PostStarts: []ContainerHook{ func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("post-start hook 1: %#v", c)) + prints = append(prints, "post-start hook 1") return nil }, func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("post-start hook 2: %#v", c)) + prints = append(prints, "post-start hook 2") return nil }, }, PostReadies: []ContainerHook{ func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("post-ready hook 1: %#v", c)) + prints = append(prints, "post-ready hook 1") return nil }, func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("post-ready hook 2: %#v", c)) + prints = append(prints, "post-ready hook 2") return nil }, }, PreStops: []ContainerHook{ func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("pre-stop hook 1: %#v", c)) + prints = append(prints, "pre-stop hook 1") return nil }, func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("pre-stop hook 2: %#v", c)) + prints = append(prints, "pre-stop hook 2") return nil }, }, PostStops: []ContainerHook{ func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("post-stop hook 1: %#v", c)) + prints = append(prints, "post-stop hook 1") return nil }, func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("post-stop hook 2: %#v", c)) + prints = append(prints, "post-stop hook 2") return nil }, }, PreTerminates: []ContainerHook{ func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("pre-terminate hook 1: %#v", c)) + prints = append(prints, "pre-terminate hook 1") return nil }, func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("pre-terminate hook 2: %#v", c)) + prints = append(prints, "pre-terminate hook 2") return nil }, }, PostTerminates: []ContainerHook{ func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("post-terminate hook 1: %#v", c)) + prints = append(prints, "post-terminate hook 1") return nil }, func(ctx context.Context, c Container) error { - prints = append(prints, fmt.Sprintf("post-terminate hook 2: %#v", c)) + prints = append(prints, "post-terminate hook 2") return nil }, }, @@ -651,6 +641,7 @@ func TestLifecycleHooks(t *testing.T) { Reuse: tt.reuse, Started: true, }) + CleanupContainer(t, c) require.NoError(t, err) require.NotNil(t, c) @@ -664,7 +655,7 @@ func TestLifecycleHooks(t *testing.T) { err = c.Terminate(ctx) require.NoError(t, err) - lifecycleHooksIsHonouredFn(t, ctx, prints) + lifecycleHooksIsHonouredFn(t, prints) }) } } @@ -698,6 +689,7 @@ func TestLifecycleHooks_WithDefaultLogger(t *testing.T) { ContainerRequest: req, Started: true, }) + CleanupContainer(t, c) require.NoError(t, err) require.NotNil(t, c) @@ -711,7 +703,8 @@ func TestLifecycleHooks_WithDefaultLogger(t *testing.T) { err = c.Terminate(ctx) require.NoError(t, err) - require.Len(t, dl.data, 12) + // Includes two additional entries for stop when terminate is called. + require.Len(t, dl.data, 14) } func TestCombineLifecycleHooks(t *testing.T) { @@ -864,6 +857,7 @@ func TestLifecycleHooks_WithMultipleHooks(t *testing.T) { ContainerRequest: req, Started: true, }) + CleanupContainer(t, c) require.NoError(t, err) require.NotNil(t, c) @@ -877,7 +871,8 @@ func TestLifecycleHooks_WithMultipleHooks(t *testing.T) { err = c.Terminate(ctx) require.NoError(t, err) - require.Len(t, dl.data, 24) + // Includes four additional entries for stop (twice) when terminate is called. + require.Len(t, dl.data, 28) } type linesTestLogger struct { @@ -901,19 +896,19 @@ func TestPrintContainerLogsOnError(t *testing.T) { data: []string{}, } - container, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Logger: &arrayOfLinesLogger, Started: true, }) + CleanupContainer(t, ctr) // it should fail because the waiting for condition is not met if err == nil { t.Fatal(err) } - terminateContainerOnEnd(t, ctx, container) - containerLogs, err := container.Logs(ctx) + containerLogs, err := ctr.Logs(ctx) if err != nil { t.Fatal(err) } @@ -944,42 +939,40 @@ func TestPrintContainerLogsOnError(t *testing.T) { } } -func lifecycleHooksIsHonouredFn(t *testing.T, ctx context.Context, prints []string) { - require.Len(t, prints, 24) - - assert.True(t, strings.HasPrefix(prints[0], "pre-create hook 1: ")) - assert.True(t, strings.HasPrefix(prints[1], "pre-create hook 2: ")) - - assert.True(t, strings.HasPrefix(prints[2], "post-create hook 1: ")) - assert.True(t, strings.HasPrefix(prints[3], "post-create hook 2: ")) - - assert.True(t, strings.HasPrefix(prints[4], "pre-start hook 1: ")) - assert.True(t, strings.HasPrefix(prints[5], "pre-start hook 2: ")) - - assert.True(t, strings.HasPrefix(prints[6], "post-start hook 1: ")) - assert.True(t, strings.HasPrefix(prints[7], "post-start hook 2: ")) - - assert.True(t, strings.HasPrefix(prints[8], "post-ready hook 1: ")) - assert.True(t, strings.HasPrefix(prints[9], "post-ready hook 2: ")) - - assert.True(t, strings.HasPrefix(prints[10], "pre-stop hook 1: ")) - assert.True(t, strings.HasPrefix(prints[11], "pre-stop hook 2: ")) - - assert.True(t, strings.HasPrefix(prints[12], "post-stop hook 1: ")) - assert.True(t, strings.HasPrefix(prints[13], "post-stop hook 2: ")) - - assert.True(t, strings.HasPrefix(prints[14], "pre-start hook 1: ")) - assert.True(t, strings.HasPrefix(prints[15], "pre-start hook 2: ")) - - assert.True(t, strings.HasPrefix(prints[16], "post-start hook 1: ")) - assert.True(t, strings.HasPrefix(prints[17], "post-start hook 2: ")) - - assert.True(t, strings.HasPrefix(prints[18], "post-ready hook 1: ")) - assert.True(t, strings.HasPrefix(prints[19], "post-ready hook 2: ")) - - assert.True(t, strings.HasPrefix(prints[20], "pre-terminate hook 1: ")) - assert.True(t, strings.HasPrefix(prints[21], "pre-terminate hook 2: ")) +func lifecycleHooksIsHonouredFn(t *testing.T, prints []string) { + t.Helper() + + expects := []string{ + "pre-create hook 1", + "pre-create hook 2", + "post-create hook 1", + "post-create hook 2", + "pre-start hook 1", + "pre-start hook 2", + "post-start hook 1", + "post-start hook 2", + "post-ready hook 1", + "post-ready hook 2", + "pre-stop hook 1", + "pre-stop hook 2", + "post-stop hook 1", + "post-stop hook 2", + "pre-start hook 1", + "pre-start hook 2", + "post-start hook 1", + "post-start hook 2", + "post-ready hook 1", + "post-ready hook 2", + // Terminate currently calls stop to ensure that child containers are stopped. + "pre-stop hook 1", + "pre-stop hook 2", + "post-stop hook 1", + "post-stop hook 2", + "pre-terminate hook 1", + "pre-terminate hook 2", + "post-terminate hook 1", + "post-terminate hook 2", + } - assert.True(t, strings.HasPrefix(prints[22], "post-terminate hook 1: ")) - assert.True(t, strings.HasPrefix(prints[23], "post-terminate hook 2: ")) + require.Equal(t, expects, prints) } diff --git a/logconsumer_test.go b/logconsumer_test.go index 6265f0a578b..855a849914e 100644 --- a/logconsumer_test.go +++ b/logconsumer_test.go @@ -92,6 +92,7 @@ func Test_LogConsumerGetsCalled(t *testing.T) { } c, err := GenericContainer(ctx, gReq) + CleanupContainer(t, c) require.NoError(t, err) ep, err := c.Endpoint(ctx, "http") @@ -112,9 +113,7 @@ func Test_LogConsumerGetsCalled(t *testing.T) { t.Fatal("never received final log message") } - assert.Equal(t, []string{"ready\n", "echo hello\n", "echo there\n"}, g.Msgs()) - - terminateContainerOnEnd(t, ctx, c) + require.Equal(t, []string{"ready\n", "echo hello\n", "echo there\n"}, g.Msgs()) } type TestLogTypeConsumer struct { @@ -157,8 +156,8 @@ func Test_ShouldRecognizeLogTypes(t *testing.T) { } c, err := GenericContainer(ctx, gReq) + CleanupContainer(t, c) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c) ep, err := c.Endpoint(ctx, "http") require.NoError(t, err) @@ -212,6 +211,7 @@ func Test_MultipleLogConsumers(t *testing.T) { } c, err := GenericContainer(ctx, gReq) + CleanupContainer(t, c) require.NoError(t, err) ep, err := c.Endpoint(ctx, "http") @@ -226,9 +226,9 @@ func Test_MultipleLogConsumers(t *testing.T) { <-first.Done <-second.Done - assert.Equal(t, []string{"ready\n", "echo mlem\n"}, first.Msgs()) - assert.Equal(t, []string{"ready\n", "echo mlem\n"}, second.Msgs()) - require.NoError(t, c.Terminate(ctx)) + expected := []string{"ready\n", "echo mlem\n"} + require.Equal(t, expected, first.Msgs()) + require.Equal(t, expected, second.Msgs()) } func TestContainerLogWithErrClosed(t *testing.T) { @@ -258,9 +258,8 @@ func TestContainerLogWithErrClosed(t *testing.T) { Privileged: true, }, }) - + CleanupContainer(t, dind) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, dind) var remoteDocker string @@ -320,7 +319,7 @@ func TestContainerLogWithErrClosed(t *testing.T) { if err := nginx.Start(ctx); err != nil { t.Fatal(err) } - terminateContainerOnEnd(t, ctx, nginx) + CleanupContainer(t, nginx) port, err := nginx.MappedPort(ctx, "80/tcp") if err != nil { @@ -380,15 +379,16 @@ func TestContainerLogsShouldBeWithoutStreamHeader(t *testing.T) { Cmd: []string{"sh", "-c", "id -u"}, WaitingFor: wait.ForExit(), } - container, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: req, Started: true, }) + CleanupContainer(t, ctr) if err != nil { t.Fatal(err) } - terminateContainerOnEnd(t, ctx, container) - r, err := container.Logs(ctx) + + r, err := ctr.Logs(ctx) if err != nil { t.Fatal(err) } @@ -429,6 +429,7 @@ func TestContainerLogsEnableAtStart(t *testing.T) { } c, err := GenericContainer(ctx, gReq) + CleanupContainer(t, c) require.NoError(t, err) ep, err := c.Endpoint(ctx, "http") @@ -448,9 +449,7 @@ func TestContainerLogsEnableAtStart(t *testing.T) { case <-time.After(10 * time.Second): t.Fatal("never received final log message") } - assert.Equal(t, []string{"ready\n", "echo hello\n", "echo there\n"}, g.Msgs()) - - terminateContainerOnEnd(t, ctx, c) + require.Equal(t, []string{"ready\n", "echo hello\n", "echo there\n"}, g.Msgs()) } func Test_StartLogProductionStillStartsWithTooLowTimeout(t *testing.T) { @@ -481,8 +480,8 @@ func Test_StartLogProductionStillStartsWithTooLowTimeout(t *testing.T) { } c, err := GenericContainer(ctx, gReq) + CleanupContainer(t, c) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c) } func Test_StartLogProductionStillStartsWithTooHighTimeout(t *testing.T) { @@ -513,22 +512,25 @@ func Test_StartLogProductionStillStartsWithTooHighTimeout(t *testing.T) { } c, err := GenericContainer(ctx, gReq) + CleanupContainer(t, c) require.NoError(t, err) require.NotNil(t, c) - // because the log production timeout is too high, the container should have already been terminated - // so no need to terminate it again with "terminateContainerOnEnd(t, ctx, c)" dc := c.(*DockerContainer) require.NoError(t, dc.stopLogProduction()) - - terminateContainerOnEnd(t, ctx, c) } func Test_MultiContainerLogConsumer_CancelledContext(t *testing.T) { // Redirect stderr to a buffer + r, w, err := os.Pipe() + require.NoError(t, err) oldStderr := os.Stderr - r, w, _ := os.Pipe() os.Stderr = w + defer func() { + // Restore stderr + os.Stderr = oldStderr + w.Close() + }() // Context with cancellation functionality for simulating user interruption ctx, cancel := context.WithCancel(context.Background()) @@ -558,6 +560,7 @@ func Test_MultiContainerLogConsumer_CancelledContext(t *testing.T) { } c, err := GenericContainer(ctx, genericReq1) + CleanupContainer(t, c) require.NoError(t, err) ep1, err := c.Endpoint(ctx, "http") @@ -593,6 +596,7 @@ func Test_MultiContainerLogConsumer_CancelledContext(t *testing.T) { } c2, err := GenericContainer(ctx, genericReq2) + CleanupContainer(t, c2) require.NoError(t, err) ep2, err := c2.Endpoint(ctx, "http") @@ -604,16 +608,6 @@ func Test_MultiContainerLogConsumer_CancelledContext(t *testing.T) { _, err = http.Get(ep2 + "/stdout?echo=there2") require.NoError(t, err) - // Handling the termination of the containers - defer func() { - shutdownCtx, shutdownCancel := context.WithTimeout( - context.Background(), 10*time.Second, - ) - defer shutdownCancel() - _ = c.Terminate(shutdownCtx) - _ = c2.Terminate(shutdownCtx) - }() - // Deliberately calling context cancel cancel() @@ -622,9 +616,8 @@ func Test_MultiContainerLogConsumer_CancelledContext(t *testing.T) { assert.GreaterOrEqual(t, len(first.Msgs()), 2) assert.GreaterOrEqual(t, len(second.Msgs()), 2) - // Restore stderr + // Close the pipe so as not to block on empty. w.Close() - os.Stderr = oldStderr // Read the stderr output from the buffer var buf bytes.Buffer @@ -636,7 +629,7 @@ func Test_MultiContainerLogConsumer_CancelledContext(t *testing.T) { // The context cancel shouldn't cause the system to throw a // logStoppedForOutOfSyncMessage, as it hangs the system with // the multiple containers. - assert.False(t, strings.Contains(actual, logStoppedForOutOfSyncMessage)) + require.NotContains(t, actual, logStoppedForOutOfSyncMessage) } // FooLogConsumer is a test log consumer that accepts logs from the @@ -689,7 +682,7 @@ func TestRestartContainerWithLogConsumer(t *testing.T) { logConsumer := NewFooLogConsumer(t) ctx := context.Background() - container, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ContainerRequest: ContainerRequest{ Image: "hello-world", AlwaysPullImage: true, @@ -699,24 +692,24 @@ func TestRestartContainerWithLogConsumer(t *testing.T) { }, Started: false, }) - terminateContainerOnEnd(t, ctx, container) + CleanupContainer(t, ctr) require.NoError(t, err) // Start and confirm that the log consumer receives the log message. - err = container.Start(ctx) + err = ctr.Start(ctx) require.NoError(t, err) logConsumer.AssertRead() // Stop the container and clear any pending message. d := 5 * time.Second - err = container.Stop(ctx, &d) + err = ctr.Stop(ctx, &d) require.NoError(t, err) logConsumer.SlurpOne() // Restart the container and confirm that the log consumer receives new log messages. - err = container.Start(ctx) + err = ctr.Start(ctx) require.NoError(t, err) // First message is from the first start. diff --git a/modulegen/_template/examples_test.go.tmpl b/modulegen/_template/examples_test.go.tmpl index b81cf22c589..f02ab36021e 100644 --- a/modulegen/_template/examples_test.go.tmpl +++ b/modulegen/_template/examples_test.go.tmpl @@ -13,21 +13,21 @@ func Example{{ $entrypoint }}() { ctx := context.Background() {{ $lower }}Container, err := {{ $lower }}.{{ $entrypoint }}(ctx, "{{ $image }}") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := {{ $lower }}Container.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer({{ $lower }}Container); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := {{ $lower }}Container.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modulegen/_template/module.go.tmpl b/modulegen/_template/module.go.tmpl index fe988afada4..31e50981d08 100644 --- a/modulegen/_template/module.go.tmpl +++ b/modulegen/_template/module.go.tmpl @@ -30,9 +30,14 @@ func {{ $entrypoint }}(ctx context.Context, img string, opts ...testcontainers.C } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *{{ $containerName }} + if container != nil { + c = &{{ $containerName }}{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &{{ $containerName }}{Container: container}, nil + return c, nil } diff --git a/modulegen/_template/module_test.go.tmpl b/modulegen/_template/module_test.go.tmpl index 2f7774ad7a6..351ba5c8d5f 100644 --- a/modulegen/_template/module_test.go.tmpl +++ b/modulegen/_template/module_test.go.tmpl @@ -4,23 +4,17 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go/{{ ParentDir }}/{{ $lower }}" ) func Test{{ $title }}(t *testing.T) { ctx := context.Background() - container, err := {{ $lower }}.{{ $entrypoint }}(ctx, "{{ $image }}") - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := {{ $lower }}.{{ $entrypoint }}(ctx, "{{ $image }}") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions } diff --git a/modulegen/main_test.go b/modulegen/main_test.go index 2c1ddbd8e9e..322cb25920b 100644 --- a/modulegen/main_test.go +++ b/modulegen/main_test.go @@ -407,8 +407,8 @@ func assertModuleTestContent(t *testing.T, module context.TestcontainersModule, data := sanitiseContent(content) assert.Equal(t, "package "+module.Lower()+"_test", data[0]) - assert.Equal(t, "func Test"+module.Title()+"(t *testing.T) {", data[9]) - assert.Equal(t, "\tcontainer, err := "+module.Lower()+"."+module.Entrypoint()+"(ctx, \""+module.Image+"\")", data[12]) + assert.Equal(t, "func Test"+module.Title()+"(t *testing.T) {", data[11]) + assert.Equal(t, "\tctr, err := "+module.Lower()+"."+module.Entrypoint()+"(ctx, \""+module.Image+"\")", data[14]) } // assert content module @@ -422,15 +422,17 @@ func assertModuleContent(t *testing.T, module context.TestcontainersModule, exam entrypoint := module.Entrypoint() data := sanitiseContent(content) - assert.Equal(t, "package "+lower, data[0]) - assert.Equal(t, "// "+containerName+" represents the "+exampleName+" container type used in the module", data[9]) - assert.Equal(t, "type "+containerName+" struct {", data[10]) - assert.Equal(t, "// "+entrypoint+" creates an instance of the "+exampleName+" container type", data[14]) - assert.Equal(t, "func "+entrypoint+"(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*"+containerName+", error) {", data[15]) - assert.Equal(t, "\t\tImage: img,", data[17]) - assert.Equal(t, "\t\tif err := opt.Customize(&genericContainerReq); err != nil {", data[26]) - assert.Equal(t, "\t\t\treturn nil, fmt.Errorf(\"customize: %w\", err)", data[27]) - assert.Equal(t, "\treturn &"+containerName+"{Container: container}, nil", data[36]) + require.Equal(t, "package "+lower, data[0]) + require.Equal(t, "// "+containerName+" represents the "+exampleName+" container type used in the module", data[9]) + require.Equal(t, "type "+containerName+" struct {", data[10]) + require.Equal(t, "// "+entrypoint+" creates an instance of the "+exampleName+" container type", data[14]) + require.Equal(t, "func "+entrypoint+"(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*"+containerName+", error) {", data[15]) + require.Equal(t, "\t\tImage: img,", data[17]) + require.Equal(t, "\t\tif err := opt.Customize(&genericContainerReq); err != nil {", data[26]) + require.Equal(t, "\t\t\treturn nil, fmt.Errorf(\"customize: %w\", err)", data[27]) + require.Equal(t, "\tvar c *"+containerName, data[32]) + require.Equal(t, "\t\tc = &"+containerName+"{Container: container}", data[34]) + require.Equal(t, "\treturn c, nil", data[41]) } // assert content GitHub workflow for the module diff --git a/modules/artemis/artemis.go b/modules/artemis/artemis.go index 9edfcaa5279..a8721ce1893 100644 --- a/modules/artemis/artemis.go +++ b/modules/artemis/artemis.go @@ -109,12 +109,16 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, req) + var c *Container + if container != nil { + c = &Container{Container: container} + } if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - user := req.Env["ARTEMIS_USER"] - password := req.Env["ARTEMIS_PASSWORD"] + c.user = req.Env["ARTEMIS_USER"] + c.password = req.Env["ARTEMIS_PASSWORD"] - return &Container{Container: container, user: user, password: password}, nil + return c, nil } diff --git a/modules/artemis/artemis_test.go b/modules/artemis/artemis_test.go index c2767790add..01097463b2c 100644 --- a/modules/artemis/artemis_test.go +++ b/modules/artemis/artemis_test.go @@ -64,12 +64,12 @@ func TestArtemis(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - container, err := artemis.Run(ctx, "docker.io/apache/activemq-artemis:2.30.0-alpine", test.opts...) + ctr, err := artemis.Run(ctx, "docker.io/apache/activemq-artemis:2.30.0-alpine", test.opts...) + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, container.Terminate(ctx), "failed to terminate container") }) // consoleURL { - u, err := container.ConsoleURL(ctx) + u, err := ctr.ConsoleURL(ctx) // } require.NoError(t, err) @@ -79,15 +79,15 @@ func TestArtemis(t *testing.T) { assert.Equal(t, http.StatusOK, res.StatusCode, "failed to access console") if test.user != "" { - assert.Equal(t, test.user, container.User(), "unexpected user") + assert.Equal(t, test.user, ctr.User(), "unexpected user") } if test.pass != "" { - assert.Equal(t, test.pass, container.Password(), "unexpected password") + assert.Equal(t, test.pass, ctr.Password(), "unexpected password") } // brokerEndpoint { - host, err := container.BrokerEndpoint(ctx) + host, err := ctr.BrokerEndpoint(ctx) // } require.NoError(t, err) @@ -116,7 +116,7 @@ func TestArtemis(t *testing.T) { } if test.hook != nil { - test.hook(t, container) + test.hook(t, ctr) } }) } diff --git a/modules/artemis/examples_test.go b/modules/artemis/examples_test.go index e1c21f3afc0..78f8c2d13d3 100644 --- a/modules/artemis/examples_test.go +++ b/modules/artemis/examples_test.go @@ -7,6 +7,7 @@ import ( "github.com/go-stomp/stomp/v3" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/artemis" ) @@ -18,19 +19,21 @@ func ExampleRun() { "docker.io/apache/activemq-artemis:2.30.0", artemis.WithCredentials("test", "test"), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } defer func() { - if err := artemisContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(artemisContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := artemisContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -39,7 +42,8 @@ func ExampleRun() { // Get broker endpoint. host, err := artemisContainer.BrokerEndpoint(ctx) if err != nil { - log.Fatalf("failed to get broker endpoint: %s", err) + log.Printf("failed to get broker endpoint: %s", err) + return } // containerUser { @@ -52,11 +56,12 @@ func ExampleRun() { // Connect to Artemis via STOMP. conn, err := stomp.Dial("tcp", host, stomp.ConnOpt.Login(user, pass)) if err != nil { - log.Fatalf("failed to connect to Artemis: %s", err) + log.Printf("failed to connect to Artemis: %s", err) + return } defer func() { if err := conn.Disconnect(); err != nil { - log.Fatalf("failed to disconnect from Artemis: %s", err) + log.Printf("failed to disconnect from Artemis: %s", err) } }() // } diff --git a/modules/azurite/azurite.go b/modules/azurite/azurite.go index 1dcdae4709d..c3172fd58a6 100644 --- a/modules/azurite/azurite.go +++ b/modules/azurite/azurite.go @@ -121,9 +121,14 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *AzuriteContainer + if container != nil { + c = &AzuriteContainer{Container: container, Settings: settings} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &AzuriteContainer{Container: container, Settings: settings}, nil + return c, nil } diff --git a/modules/azurite/azurite_test.go b/modules/azurite/azurite_test.go index 8fe5946e2fb..618fc28b0b9 100644 --- a/modules/azurite/azurite_test.go +++ b/modules/azurite/azurite_test.go @@ -4,23 +4,18 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/azurite" ) func TestAzurite(t *testing.T) { ctx := context.Background() - container, err := azurite.Run(ctx, "mcr.microsoft.com/azure-storage/azurite:3.23.0") - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := azurite.Run(ctx, "mcr.microsoft.com/azure-storage/azurite:3.23.0") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions } diff --git a/modules/azurite/examples_test.go b/modules/azurite/examples_test.go index d2fc9965f03..567685891f2 100644 --- a/modules/azurite/examples_test.go +++ b/modules/azurite/examples_test.go @@ -12,6 +12,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/azurite" ) @@ -23,21 +24,21 @@ func ExampleRun() { ctx, "mcr.microsoft.com/azure-storage/azurite:3.28.0", ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := azuriteContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(azuriteContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := azuriteContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -57,20 +58,21 @@ func ExampleRun_blobOperations() { "mcr.microsoft.com/azure-storage/azurite:3.28.0", azurite.WithInMemoryPersistence(64), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := azuriteContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(azuriteContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // using the built-in shared key credential type cred, err := azblob.NewSharedKeyCredential(azurite.AccountName, azurite.AccountKey) if err != nil { - log.Fatalf("failed to create shared key credential: %s", err) // nolint:gocritic + log.Printf("failed to create shared key credential: %s", err) + return } // create an azblob.Client for the specified storage account that uses the above credentials @@ -78,14 +80,16 @@ func ExampleRun_blobOperations() { client, err := azblob.NewClientWithSharedKeyCredential(blobServiceURL, cred, nil) if err != nil { - log.Fatalf("failed to create client: %s", err) // nolint:gocritic + log.Printf("failed to create client: %s", err) + return } // ===== 1. Create a container ===== containerName := "testcontainer" _, err = client.CreateContainer(context.TODO(), containerName, nil) if err != nil { - log.Fatalf("failed to create container: %s", err) + log.Printf("failed to create container: %s", err) + return } // ===== 2. Upload and Download a block blob ===== @@ -101,13 +105,15 @@ func ExampleRun_blobOperations() { Tags: map[string]string{"Year": "2022"}, }) if err != nil { - log.Fatalf("failed to upload blob: %s", err) + log.Printf("failed to upload blob: %s", err) + return } // Download the blob's contents and ensure that the download worked properly blobDownloadResponse, err := client.DownloadStream(context.TODO(), containerName, blobName, nil) if err != nil { - log.Fatalf("failed to download blob: %s", err) // nolint:gocritic + log.Printf("failed to download blob: %s", err) + return } // Use the bytes.Buffer object to read the downloaded data. @@ -115,7 +121,8 @@ func ExampleRun_blobOperations() { reader := blobDownloadResponse.Body downloadData, err := io.ReadAll(reader) if err != nil { - log.Fatalf("failed to read downloaded data: %s", err) // nolint:gocritic + log.Printf("failed to read downloaded data: %s", err) + return } fmt.Println(string(downloadData)) @@ -133,7 +140,8 @@ func ExampleRun_blobOperations() { for pager.More() { resp, err := pager.NextPage(context.TODO()) if err != nil { - log.Fatalf("failed to list blobs: %s", err) + log.Printf("failed to list blobs: %s", err) + return } fmt.Println(len(resp.Segment.BlobItems)) @@ -142,13 +150,15 @@ func ExampleRun_blobOperations() { // Delete the blob. _, err = client.DeleteBlob(context.TODO(), containerName, blobName, nil) if err != nil { - log.Fatalf("failed to delete blob: %s", err) + log.Printf("failed to delete blob: %s", err) + return } // Delete the container. _, err = client.DeleteContainer(context.TODO(), containerName, nil) if err != nil { - log.Fatalf("failed to delete container: %s", err) + log.Printf("failed to delete container: %s", err) + return } // } @@ -169,20 +179,21 @@ func ExampleRun_queueOperations() { "mcr.microsoft.com/azure-storage/azurite:3.28.0", azurite.WithInMemoryPersistence(64), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := azuriteContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(azuriteContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // using the built-in shared key credential type cred, err := azqueue.NewSharedKeyCredential(azurite.AccountName, azurite.AccountKey) if err != nil { - log.Fatalf("failed to create shared key credential: %s", err) // nolint:gocritic + log.Printf("failed to create shared key credential: %s", err) + return } // create an azqueue.Client for the specified storage account that uses the above credentials @@ -190,7 +201,8 @@ func ExampleRun_queueOperations() { client, err := azqueue.NewServiceClientWithSharedKeyCredential(queueServiceURL, cred, nil) if err != nil { - log.Fatalf("failed to create client: %s", err) + log.Printf("failed to create client: %s", err) + return } queueName := "testqueue" @@ -199,7 +211,8 @@ func ExampleRun_queueOperations() { Metadata: map[string]*string{"hello": to.Ptr("world")}, }) if err != nil { - log.Fatalf("failed to create queue: %s", err) + log.Printf("failed to create queue: %s", err) + return } pager := client.NewListQueuesPager(&azqueue.ListQueuesOptions{ @@ -210,7 +223,8 @@ func ExampleRun_queueOperations() { for pager.More() { resp, err := pager.NextPage(context.Background()) if err != nil { - log.Fatalf("failed to list queues: %s", err) + log.Printf("failed to list queues: %s", err) + return } fmt.Println(len(resp.Queues)) @@ -220,7 +234,8 @@ func ExampleRun_queueOperations() { // delete the queue _, err = client.DeleteQueue(context.TODO(), queueName, &azqueue.DeleteOptions{}) if err != nil { - log.Fatalf("failed to delete queue: %s", err) + log.Printf("failed to delete queue: %s", err) + return } // } @@ -241,20 +256,21 @@ func ExampleRun_tableOperations() { "mcr.microsoft.com/azure-storage/azurite:3.28.0", azurite.WithInMemoryPersistence(64), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := azuriteContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(azuriteContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // using the built-in shared key credential type cred, err := aztables.NewSharedKeyCredential(azurite.AccountName, azurite.AccountKey) if err != nil { - log.Fatalf("failed to create shared key credential: %s", err) // nolint:gocritic + log.Printf("failed to create shared key credential: %s", err) + return } // create an aztables.Client for the specified storage account that uses the above credentials @@ -262,14 +278,16 @@ func ExampleRun_tableOperations() { client, err := aztables.NewServiceClientWithSharedKey(tablesServiceURL, cred, nil) if err != nil { - log.Fatalf("failed to create client: %s", err) + log.Printf("failed to create client: %s", err) + return } tableName := "fromServiceClient" // Create a table _, err = client.CreateTable(context.TODO(), tableName, nil) if err != nil { - log.Fatalf("failed to create table: %s", err) + log.Printf("failed to create table: %s", err) + return } // List tables @@ -277,7 +295,8 @@ func ExampleRun_tableOperations() { for pager.More() { resp, err := pager.NextPage(context.Background()) if err != nil { - log.Fatalf("failed to list tables: %s", err) + log.Printf("failed to list tables: %s", err) + return } fmt.Println(len(resp.Tables)) @@ -287,7 +306,8 @@ func ExampleRun_tableOperations() { // Delete a table _, err = client.DeleteTable(context.TODO(), tableName, nil) if err != nil { - panic(err) + fmt.Println(err) + return } // } diff --git a/modules/azurite/go.mod b/modules/azurite/go.mod index 826a457a9ac..99eaff2d54e 100644 --- a/modules/azurite/go.mod +++ b/modules/azurite/go.mod @@ -8,7 +8,8 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue v1.0.0 github.com/docker/go-connections v0.5.0 - github.com/testcontainers/testcontainers-go v0.33.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 ) require ( @@ -21,6 +22,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect @@ -31,6 +33,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.4 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -42,6 +45,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -59,6 +63,7 @@ require ( golang.org/x/text v0.16.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/azurite/go.sum b/modules/azurite/go.sum index 0d2267c5a4c..4dd10ea1191 100644 --- a/modules/azurite/go.sum +++ b/modules/azurite/go.sum @@ -32,6 +32,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -73,6 +74,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= @@ -103,6 +108,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -199,6 +206,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/cassandra/cassandra.go b/modules/cassandra/cassandra.go index c5dbfc61d6b..e63d1c7e97d 100644 --- a/modules/cassandra/cassandra.go +++ b/modules/cassandra/cassandra.go @@ -2,6 +2,7 @@ package cassandra import ( "context" + "fmt" "io" "path/filepath" "strings" @@ -114,9 +115,14 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *CassandraContainer + if container != nil { + c = &CassandraContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &CassandraContainer{Container: container}, nil + return c, nil } diff --git a/modules/cassandra/cassandra_test.go b/modules/cassandra/cassandra_test.go index a878db4f6f0..3c8cd1e9184 100644 --- a/modules/cassandra/cassandra_test.go +++ b/modules/cassandra/cassandra_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/cassandra" ) @@ -20,26 +21,18 @@ type Test struct { func TestCassandra(t *testing.T) { ctx := context.Background() - container, err := cassandra.Run(ctx, "cassandra:4.1.3") - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - require.NoError(t, container.Terminate(ctx)) - }) + ctr, err := cassandra.Run(ctx, "cassandra:4.1.3") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // connectionString { - connectionHost, err := container.ConnectionHost(ctx) + connectionHost, err := ctr.ConnectionHost(ctx) // } require.NoError(t, err) cluster := gocql.NewCluster(connectionHost) session, err := cluster.CreateSession() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer session.Close() // perform assertions @@ -60,17 +53,11 @@ func TestCassandra(t *testing.T) { func TestCassandraWithConfigFile(t *testing.T) { ctx := context.Background() - container, err := cassandra.Run(ctx, "cassandra:4.1.3", cassandra.WithConfigFile(filepath.Join("testdata", "config.yaml"))) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - require.NoError(t, container.Terminate(ctx)) - }) + ctr, err := cassandra.Run(ctx, "cassandra:4.1.3", cassandra.WithConfigFile(filepath.Join("testdata", "config.yaml"))) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - connectionHost, err := container.ConnectionHost(ctx) + connectionHost, err := ctr.ConnectionHost(ctx) require.NoError(t, err) cluster := gocql.NewCluster(connectionHost) @@ -91,19 +78,13 @@ func TestCassandraWithInitScripts(t *testing.T) { ctx := context.Background() // withInitScripts { - container, err := cassandra.Run(ctx, "cassandra:4.1.3", cassandra.WithInitScripts(filepath.Join("testdata", "init.cql"))) + ctr, err := cassandra.Run(ctx, "cassandra:4.1.3", cassandra.WithInitScripts(filepath.Join("testdata", "init.cql"))) // } - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - require.NoError(t, container.Terminate(ctx)) - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // connectionHost { - connectionHost, err := container.ConnectionHost(ctx) + connectionHost, err := ctr.ConnectionHost(ctx) // } require.NoError(t, err) @@ -123,17 +104,11 @@ func TestCassandraWithInitScripts(t *testing.T) { t.Run("with init bash script", func(t *testing.T) { ctx := context.Background() - container, err := cassandra.Run(ctx, "cassandra:4.1.3", cassandra.WithInitScripts(filepath.Join("testdata", "init.sh"))) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - require.NoError(t, container.Terminate(ctx)) - }) + ctr, err := cassandra.Run(ctx, "cassandra:4.1.3", cassandra.WithInitScripts(filepath.Join("testdata", "init.sh"))) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - connectionHost, err := container.ConnectionHost(ctx) + connectionHost, err := ctr.ConnectionHost(ctx) require.NoError(t, err) cluster := gocql.NewCluster(connectionHost) diff --git a/modules/cassandra/examples_test.go b/modules/cassandra/examples_test.go index f80cb3f666e..68a80589ea1 100644 --- a/modules/cassandra/examples_test.go +++ b/modules/cassandra/examples_test.go @@ -8,6 +8,7 @@ import ( "github.com/gocql/gocql" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/cassandra" ) @@ -20,41 +21,44 @@ func ExampleRun() { cassandra.WithInitScripts(filepath.Join("testdata", "init.cql")), cassandra.WithConfigFile(filepath.Join("testdata", "config.yaml")), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := cassandraContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(cassandraContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := cassandraContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) connectionHost, err := cassandraContainer.ConnectionHost(ctx) if err != nil { - log.Fatalf("failed to get connection host: %s", err) + log.Printf("failed to get connection host: %s", err) + return } cluster := gocql.NewCluster(connectionHost) session, err := cluster.CreateSession() if err != nil { - log.Fatalf("failed to create session: %s", err) + log.Printf("failed to create session: %s", err) + return } defer session.Close() var version string err = session.Query("SELECT release_version FROM system.local").Scan(&version) if err != nil { - log.Fatalf("failed to query: %s", err) + log.Printf("failed to query: %s", err) + return } fmt.Println(version) diff --git a/modules/chroma/chroma.go b/modules/chroma/chroma.go index d0d633f3901..d0919e899f1 100644 --- a/modules/chroma/chroma.go +++ b/modules/chroma/chroma.go @@ -45,11 +45,16 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *ChromaContainer + if container != nil { + c = &ChromaContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &ChromaContainer{Container: container}, nil + return c, nil } // RESTEndpoint returns the REST endpoint of the Chroma container diff --git a/modules/chroma/chroma_test.go b/modules/chroma/chroma_test.go index 0e33d059f76..5bf44b3282b 100644 --- a/modules/chroma/chroma_test.go +++ b/modules/chroma/chroma_test.go @@ -8,27 +8,20 @@ import ( chromago "github.com/amikos-tech/chroma-go" "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/chroma" ) func TestChroma(t *testing.T) { ctx := context.Background() - container, err := chroma.Run(ctx, "chromadb/chroma:0.4.24") - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := chroma.Run(ctx, "chromadb/chroma:0.4.24") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) t.Run("REST Endpoint retrieve docs site", func(tt *testing.T) { // restEndpoint { - restEndpoint, err := container.RESTEndpoint(ctx) + restEndpoint, err := ctr.RESTEndpoint(ctx) // } if err != nil { tt.Fatalf("failed to get REST endpoint: %s", err) @@ -48,9 +41,9 @@ func TestChroma(t *testing.T) { t.Run("GetClient", func(tt *testing.T) { // restEndpoint { - endpoint, err := container.RESTEndpoint(context.Background()) + endpoint, err := ctr.RESTEndpoint(context.Background()) if err != nil { - tt.Fatalf("failed to get REST endpoint: %s", err) // nolint:gocritic + tt.Fatalf("failed to get REST endpoint: %s", err) } chromaClient, err := chromago.NewClient(endpoint) // } diff --git a/modules/chroma/examples_test.go b/modules/chroma/examples_test.go index 1828c1eef4b..a44125b2422 100644 --- a/modules/chroma/examples_test.go +++ b/modules/chroma/examples_test.go @@ -17,21 +17,21 @@ func ExampleRun() { ctx := context.Background() chromaContainer, err := chroma.Run(ctx, "chromadb/chroma:0.4.24") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := chromaContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(chromaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := chromaContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -45,24 +45,25 @@ func ExampleChromaContainer_connectWithClient() { ctx := context.Background() chromaContainer, err := chroma.Run(ctx, "chromadb/chroma:0.4.24") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := chromaContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(chromaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } endpoint, err := chromaContainer.RESTEndpoint(context.Background()) if err != nil { - log.Fatalf("failed to get REST endpoint: %s", err) // nolint:gocritic + log.Printf("failed to get REST endpoint: %s", err) + return } chromaClient, err := chromago.NewClient(endpoint) if err != nil { - log.Fatalf("failed to get client: %s", err) // nolint:gocritic + log.Printf("failed to get client: %s", err) + return } hbs, errHb := chromaClient.Heartbeat(context.Background()) @@ -82,32 +83,35 @@ func ExampleChromaContainer_collections() { ctx := context.Background() chromaContainer, err := chroma.Run(ctx, "chromadb/chroma:0.4.24", testcontainers.WithEnv(map[string]string{"ALLOW_RESET": "true"})) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := chromaContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(chromaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // getClient { // create the client connection and confirm that we can access the server with it endpoint, err := chromaContainer.RESTEndpoint(context.Background()) if err != nil { - log.Fatalf("failed to get REST endpoint: %s", err) // nolint:gocritic + log.Printf("failed to get REST endpoint: %s", err) + return } chromaClient, err := chromago.NewClient(endpoint) // } if err != nil { - log.Fatalf("failed to get client: %s", err) // nolint:gocritic + log.Printf("failed to get client: %s", err) + return } // reset { reset, err := chromaClient.Reset(context.Background()) // } if err != nil { - log.Fatalf("failed to reset: %s", err) // nolint:gocritic + log.Printf("failed to reset: %s", err) + return } fmt.Printf("Reset successful: %v\n", reset) @@ -116,7 +120,8 @@ func ExampleChromaContainer_collections() { col, err := chromaClient.CreateCollection(context.Background(), "test-collection", map[string]any{}, true, types.NewConsistentHashEmbeddingFunction(), types.L2) // } if err != nil { - log.Fatalf("failed to create collection: %s", err) // nolint:gocritic + log.Printf("failed to create collection: %s", err) + return } fmt.Println("Collection created:", col.Name) @@ -132,7 +137,8 @@ func ExampleChromaContainer_collections() { ) // } if err != nil { - log.Fatalf("failed to add data to collection: %s", err) // nolint:gocritic + log.Printf("failed to add data to collection: %s", err) + return } fmt.Println(col1.Count(context.Background())) @@ -147,7 +153,8 @@ func ExampleChromaContainer_collections() { ) // } if err != nil { - log.Fatalf("failed to query collection: %s", err) // nolint:gocritic + log.Printf("failed to query collection: %s", err) + return } fmt.Printf("Result of query: %v\n", queryResults) @@ -156,7 +163,8 @@ func ExampleChromaContainer_collections() { cols, err := chromaClient.ListCollections(context.Background()) // } if err != nil { - log.Fatalf("failed to list collections: %s", err) // nolint:gocritic + log.Printf("failed to list collections: %s", err) + return } fmt.Println(len(cols)) @@ -165,7 +173,8 @@ func ExampleChromaContainer_collections() { _, err = chromaClient.DeleteCollection(context.Background(), "test-collection") // } if err != nil { - log.Fatalf("failed to delete collection: %s", err) // nolint:gocritic + log.Printf("failed to delete collection: %s", err) + return } fmt.Println(err) diff --git a/modules/clickhouse/clickhouse.go b/modules/clickhouse/clickhouse.go index 88b8b82d4b1..43b41110b84 100644 --- a/modules/clickhouse/clickhouse.go +++ b/modules/clickhouse/clickhouse.go @@ -249,13 +249,17 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *ClickHouseContainer + if container != nil { + c = &ClickHouseContainer{Container: container} + } if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - user := req.Env["CLICKHOUSE_USER"] - password := req.Env["CLICKHOUSE_PASSWORD"] - dbName := req.Env["CLICKHOUSE_DB"] + c.User = req.Env["CLICKHOUSE_USER"] + c.Password = req.Env["CLICKHOUSE_PASSWORD"] + c.DbName = req.Env["CLICKHOUSE_DB"] - return &ClickHouseContainer{Container: container, DbName: dbName, Password: password, User: user}, nil + return c, nil } diff --git a/modules/clickhouse/clickhouse_test.go b/modules/clickhouse/clickhouse_test.go index a581e8d4c5a..c14814d3feb 100644 --- a/modules/clickhouse/clickhouse_test.go +++ b/modules/clickhouse/clickhouse_test.go @@ -10,7 +10,6 @@ import ( "github.com/ClickHouse/clickhouse-go/v2/lib/driver" "github.com/cenkalti/backoff/v4" "github.com/docker/go-connections/nat" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" @@ -31,29 +30,23 @@ type Test struct { func TestClickHouseDefaultConfig(t *testing.T) { ctx := context.Background() - container, err := clickhouse.Run(ctx, "clickhouse/clickhouse-server:23.3.8.21-alpine") - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - require.NoError(t, container.Terminate(ctx)) - }) + ctr, err := clickhouse.Run(ctx, "clickhouse/clickhouse-server:23.3.8.21-alpine") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - connectionHost, err := container.ConnectionHost(ctx) + connectionHost, err := ctr.ConnectionHost(ctx) require.NoError(t, err) conn, err := ch.Open(&ch.Options{ Addr: []string{connectionHost}, Auth: ch.Auth{ - Database: container.DbName, - Username: container.User, - Password: container.Password, + Database: ctr.DbName, + Username: ctr.User, + Password: ctr.Password, }, }) require.NoError(t, err) - assert.NotNil(t, conn) + require.NotNil(t, conn) defer conn.Close() err = conn.Ping(context.Background()) @@ -63,23 +56,17 @@ func TestClickHouseDefaultConfig(t *testing.T) { func TestClickHouseConnectionHost(t *testing.T) { ctx := context.Background() - container, err := clickhouse.Run(ctx, + ctr, err := clickhouse.Run(ctx, "clickhouse/clickhouse-server:23.3.8.21-alpine", clickhouse.WithUsername(user), clickhouse.WithPassword(password), clickhouse.WithDatabase(dbname), ) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - require.NoError(t, container.Terminate(ctx)) - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // connectionHost { - connectionHost, err := container.ConnectionHost(ctx) + connectionHost, err := ctr.ConnectionHost(ctx) // } require.NoError(t, err) @@ -92,74 +79,63 @@ func TestClickHouseConnectionHost(t *testing.T) { }, }) require.NoError(t, err) - assert.NotNil(t, conn) + require.NotNil(t, conn) defer conn.Close() // perform assertions data, err := performCRUD(t, conn) require.NoError(t, err) - assert.Len(t, data, 1) + require.Len(t, data, 1) } func TestClickHouseDSN(t *testing.T) { ctx := context.Background() - container, err := clickhouse.Run(ctx, + ctr, err := clickhouse.Run(ctx, "clickhouse/clickhouse-server:23.3.8.21-alpine", clickhouse.WithUsername(user), clickhouse.WithPassword(password), clickhouse.WithDatabase(dbname), ) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - require.NoError(t, container.Terminate(ctx)) - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // connectionString { - connectionString, err := container.ConnectionString(ctx, "debug=true") + connectionString, err := ctr.ConnectionString(ctx, "debug=true") // } require.NoError(t, err) opts, err := ch.ParseDSN(connectionString) require.NoError(t, err) + opts.Debugf = t.Logf conn, err := ch.Open(opts) require.NoError(t, err) - assert.NotNil(t, conn) + require.NotNil(t, conn) defer conn.Close() // perform assertions data, err := performCRUD(t, conn) require.NoError(t, err) - assert.Len(t, data, 1) + require.Len(t, data, 1) } func TestClickHouseWithInitScripts(t *testing.T) { ctx := context.Background() // withInitScripts { - container, err := clickhouse.Run(ctx, + ctr, err := clickhouse.Run(ctx, "clickhouse/clickhouse-server:23.3.8.21-alpine", clickhouse.WithUsername(user), clickhouse.WithPassword(password), clickhouse.WithDatabase(dbname), clickhouse.WithInitScripts(filepath.Join("testdata", "init-db.sh")), ) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // } - // Clean up the container after the test is complete - t.Cleanup(func() { - require.NoError(t, container.Terminate(ctx)) - }) - - connectionHost, err := container.ConnectionHost(ctx) + connectionHost, err := ctr.ConnectionHost(ctx) require.NoError(t, err) conn, err := ch.Open(&ch.Options{ @@ -171,13 +147,13 @@ func TestClickHouseWithInitScripts(t *testing.T) { }, }) require.NoError(t, err) - assert.NotNil(t, conn) + require.NotNil(t, conn) defer conn.Close() // perform assertions data, err := getAllRows(conn) require.NoError(t, err) - assert.Len(t, data, 1) + require.Len(t, data, 1) } func TestClickHouseWithConfigFile(t *testing.T) { @@ -192,23 +168,17 @@ func TestClickHouseWithConfigFile(t *testing.T) { } for _, tC := range testCases { t.Run(tC.desc, func(t *testing.T) { - container, err := clickhouse.Run(ctx, + ctr, err := clickhouse.Run(ctx, "clickhouse/clickhouse-server:23.3.8.21-alpine", clickhouse.WithUsername(user), clickhouse.WithPassword(""), clickhouse.WithDatabase(dbname), tC.configOption, ) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - require.NoError(t, container.Terminate(ctx)) - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - connectionHost, err := container.ConnectionHost(ctx) + connectionHost, err := ctr.ConnectionHost(ctx) require.NoError(t, err) conn, err := ch.Open(&ch.Options{ @@ -220,13 +190,13 @@ func TestClickHouseWithConfigFile(t *testing.T) { }, }) require.NoError(t, err) - assert.NotNil(t, conn) + require.NotNil(t, conn) defer conn.Close() // perform assertions data, err := performCRUD(t, conn) require.NoError(t, err) - assert.Len(t, data, 1) + require.Len(t, data, 1) }) } } @@ -245,34 +215,24 @@ func TestClickHouseWithZookeeper(t *testing.T) { }, Started: true, }) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, zkcontainer) + require.NoError(t, err) ipaddr, err := zkcontainer.ContainerIP(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - container, err := clickhouse.Run(ctx, + ctr, err := clickhouse.Run(ctx, "clickhouse/clickhouse-server:23.3.8.21-alpine", clickhouse.WithUsername(user), clickhouse.WithPassword(password), clickhouse.WithDatabase(dbname), clickhouse.WithZookeeper(ipaddr, zkPort.Port()), ) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // } - // Clean up the container after the test is complete - t.Cleanup(func() { - require.NoError(t, container.Terminate(ctx)) - require.NoError(t, zkcontainer.Terminate(ctx)) - }) - - connectionHost, err := container.ConnectionHost(ctx) + connectionHost, err := ctr.ConnectionHost(ctx) require.NoError(t, err) conn, err := ch.Open(&ch.Options{ @@ -284,13 +244,13 @@ func TestClickHouseWithZookeeper(t *testing.T) { }, }) require.NoError(t, err) - assert.NotNil(t, conn) + require.NotNil(t, conn) defer conn.Close() // perform assertions data, err := performReplicatedCRUD(t, conn) require.NoError(t, err) - assert.Len(t, data, 1) + require.Len(t, data, 1) } func performReplicatedCRUD(t *testing.T, conn driver.Conn) ([]Test, error) { diff --git a/modules/clickhouse/examples_test.go b/modules/clickhouse/examples_test.go index d63c4405416..bc031f134d3 100644 --- a/modules/clickhouse/examples_test.go +++ b/modules/clickhouse/examples_test.go @@ -9,6 +9,7 @@ import ( ch "github.com/ClickHouse/clickhouse-go/v2" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/clickhouse" ) @@ -28,31 +29,35 @@ func ExampleRun() { clickhouse.WithInitScripts(filepath.Join("testdata", "init-db.sh")), clickhouse.WithConfigFile(filepath.Join("testdata", "config.xml")), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } defer func() { - if err := clickHouseContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(clickHouseContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := clickHouseContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) connectionString, err := clickHouseContainer.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) + log.Printf("failed to get connection string: %s", err) + return } opts, err := ch.ParseDSN(connectionString) if err != nil { - log.Fatalf("failed to parse DSN: %s", err) + log.Printf("failed to parse DSN: %s", err) + return } fmt.Println(strings.HasPrefix(opts.ClientInfo.String(), "clickhouse-go/")) diff --git a/modules/cockroachdb/cockroachdb.go b/modules/cockroachdb/cockroachdb.go index e53ef08c6a3..4d412f04d3b 100644 --- a/modules/cockroachdb/cockroachdb.go +++ b/modules/cockroachdb/cockroachdb.go @@ -118,10 +118,16 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, req) + var c *CockroachDBContainer + if container != nil { + c = &CockroachDBContainer{Container: container, opts: o} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &CockroachDBContainer{Container: container, opts: o}, nil + + return c, nil } type modiferFunc func(*testcontainers.GenericContainerRequest, options) error diff --git a/modules/cockroachdb/cockroachdb_test.go b/modules/cockroachdb/cockroachdb_test.go index 1f5a6df0ad5..cc355e9168c 100644 --- a/modules/cockroachdb/cockroachdb_test.go +++ b/modules/cockroachdb/cockroachdb_test.go @@ -63,15 +63,11 @@ type AuthNSuite struct { func (suite *AuthNSuite) TestConnectionString() { ctx := context.Background() - container, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", suite.opts...) + ctr, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", suite.opts...) + testcontainers.CleanupContainer(suite.T(), ctr) suite.Require().NoError(err) - suite.T().Cleanup(func() { - err := container.Terminate(ctx) - suite.Require().NoError(err) - }) - - connStr, err := removePort(container.MustConnectionString(ctx)) + connStr, err := removePort(ctr.MustConnectionString(ctx)) suite.Require().NoError(err) suite.Equal(suite.url, connStr) @@ -101,15 +97,11 @@ func (suite *AuthNSuite) TestPing() { opts := suite.opts opts = append(opts, input.opts...) - container, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", opts...) + ctr, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", opts...) + testcontainers.CleanupContainer(suite.T(), ctr) suite.Require().NoError(err) - suite.T().Cleanup(func() { - err := container.Terminate(ctx) - suite.Require().NoError(err) - }) - - conn, err := conn(ctx, container) + conn, err := conn(ctx, ctr) suite.Require().NoError(err) defer conn.Close(ctx) @@ -122,15 +114,11 @@ func (suite *AuthNSuite) TestPing() { func (suite *AuthNSuite) TestQuery() { ctx := context.Background() - container, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", suite.opts...) + ctr, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", suite.opts...) + testcontainers.CleanupContainer(suite.T(), ctr) suite.Require().NoError(err) - suite.T().Cleanup(func() { - err := container.Terminate(ctx) - suite.Require().NoError(err) - }) - - conn, err := conn(ctx, container) + conn, err := conn(ctx, ctr) suite.Require().NoError(err) defer conn.Close(ctx) @@ -155,15 +143,9 @@ func (suite *AuthNSuite) TestWithWaitStrategyAndDeadline() { // This will never match a log statement suite.opts = append(suite.opts, testcontainers.WithWaitStrategyAndDeadline(time.Millisecond*250, wait.ForLog("Won't Exist In Logs"))) - container, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", suite.opts...) - + ctr, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", suite.opts...) + testcontainers.CleanupContainer(suite.T(), ctr) suite.Require().ErrorIs(err, context.DeadlineExceeded) - suite.T().Cleanup(func() { - if container != nil { - err := container.Terminate(ctx) - suite.Require().NoError(err) - } - }) }) suite.Run("Expected Failure To Run But Would Succeed ", func() { @@ -171,15 +153,9 @@ func (suite *AuthNSuite) TestWithWaitStrategyAndDeadline() { // This will timeout as we didn't give enough time for intialization, but would have succeeded otherwise suite.opts = append(suite.opts, testcontainers.WithWaitStrategyAndDeadline(time.Millisecond*20, wait.ForLog(nodeStartUpCompleted))) - container, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", suite.opts...) - + ctr, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", suite.opts...) + testcontainers.CleanupContainer(suite.T(), ctr) suite.Require().ErrorIs(err, context.DeadlineExceeded) - suite.T().Cleanup(func() { - if container != nil { - err := container.Terminate(ctx) - suite.Require().NoError(err) - } - }) }) suite.Run("Succeeds And Executes Commands", func() { @@ -187,21 +163,16 @@ func (suite *AuthNSuite) TestWithWaitStrategyAndDeadline() { // This will succeed suite.opts = append(suite.opts, testcontainers.WithWaitStrategyAndDeadline(time.Second*60, wait.ForLog(nodeStartUpCompleted))) - container, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", suite.opts...) + ctr, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", suite.opts...) + testcontainers.CleanupContainer(suite.T(), ctr) suite.Require().NoError(err) - conn, err := conn(ctx, container) + conn, err := conn(ctx, ctr) suite.Require().NoError(err) defer conn.Close(ctx) _, err = conn.Exec(ctx, "CREATE TABLE test (id INT PRIMARY KEY)") suite.Require().NoError(err) - suite.T().Cleanup(func() { - if container != nil { - err := container.Terminate(ctx) - suite.Require().NoError(err) - } - }) }) suite.Run("Succeeds And Executes Commands Waiting on HTTP Endpoint", func() { @@ -209,21 +180,16 @@ func (suite *AuthNSuite) TestWithWaitStrategyAndDeadline() { // This will succeed suite.opts = append(suite.opts, testcontainers.WithWaitStrategyAndDeadline(time.Second*60, wait.ForHTTP("/health").WithPort("8080/tcp"))) - container, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", suite.opts...) + ctr, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1", suite.opts...) + testcontainers.CleanupContainer(suite.T(), ctr) suite.Require().NoError(err) - conn, err := conn(ctx, container) + conn, err := conn(ctx, ctr) suite.Require().NoError(err) defer conn.Close(ctx) _, err = conn.Exec(ctx, "CREATE TABLE test (id INT PRIMARY KEY)") suite.Require().NoError(err) - suite.T().Cleanup(func() { - if container != nil { - err := container.Terminate(ctx) - suite.Require().NoError(err) - } - }) }) } diff --git a/modules/cockroachdb/examples_test.go b/modules/cockroachdb/examples_test.go index b427846d4b7..c06c97596b7 100644 --- a/modules/cockroachdb/examples_test.go +++ b/modules/cockroachdb/examples_test.go @@ -6,6 +6,7 @@ import ( "log" "net/url" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/cockroachdb" ) @@ -14,31 +15,33 @@ func ExampleRun() { ctx := context.Background() cockroachdbContainer, err := cockroachdb.Run(ctx, "cockroachdb/cockroach:latest-v23.1") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := cockroachdbContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(cockroachdbContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := cockroachdbContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) addr, err := cockroachdbContainer.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) + log.Printf("failed to get connection string: %s", err) + return } u, err := url.Parse(addr) if err != nil { - log.Fatalf("failed to parse connection string: %s", err) + log.Printf("failed to parse connection string: %s", err) + return } u.Host = fmt.Sprintf("%s:%s", u.Hostname(), "xxx") fmt.Println(u.String()) diff --git a/modules/compose/compose_api.go b/modules/compose/compose_api.go index d1b18ec3b6e..9f21d09e875 100644 --- a/modules/compose/compose_api.go +++ b/modules/compose/compose_api.go @@ -460,6 +460,10 @@ func (d *dockerCompose) lookupContainer(ctx context.Context, svcName string) (*t } containerInstance := containers[0] + // TODO: Fix as this is only setting a subset of the fields + // and the container is not fully initialized, for example + // the isRunning flag is not set. + // See: https://github.com/testcontainers/testcontainers-go/issues/2667 ctr := &testcontainers.DockerContainer{ ID: containerInstance.ID, Image: containerInstance.Image, diff --git a/modules/consul/consul.go b/modules/consul/consul.go index fab3c5b29dd..c374938c32d 100644 --- a/modules/consul/consul.go +++ b/modules/consul/consul.go @@ -94,9 +94,14 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, containerReq) + var c *ConsulContainer + if container != nil { + c = &ConsulContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &ConsulContainer{Container: container}, nil + return c, nil } diff --git a/modules/consul/consul_test.go b/modules/consul/consul_test.go index 2b244577852..f89c7858747 100644 --- a/modules/consul/consul_test.go +++ b/modules/consul/consul_test.go @@ -40,12 +40,12 @@ func TestConsul(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - container, err := consul.Run(ctx, "docker.io/hashicorp/consul:1.15", test.opts...) + ctr, err := consul.Run(ctx, "docker.io/hashicorp/consul:1.15", test.opts...) + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, container.Terminate(ctx), "failed to terminate container") }) // Check if API is up - host, err := container.ApiEndpoint(ctx) + host, err := ctr.ApiEndpoint(ctx) require.NoError(t, err) assert.NotEmpty(t, len(host)) diff --git a/modules/consul/examples_test.go b/modules/consul/examples_test.go index a65a30c0667..32d323fe3db 100644 --- a/modules/consul/examples_test.go +++ b/modules/consul/examples_test.go @@ -7,6 +7,7 @@ import ( capi "github.com/hashicorp/consul/api" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/consul" ) @@ -15,21 +16,21 @@ func ExampleRun() { ctx := context.Background() consulContainer, err := consul.Run(ctx, "docker.io/hashicorp/consul:1.15") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := consulContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(consulContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := consulContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -43,33 +44,35 @@ func ExampleRun_connect() { ctx := context.Background() consulContainer, err := consul.Run(ctx, "docker.io/hashicorp/consul:1.15") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := consulContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(consulContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } endpoint, err := consulContainer.ApiEndpoint(ctx) if err != nil { - log.Fatalf("failed to get endpoint: %s", err) // nolint:gocritic + log.Printf("failed to get endpoint: %s", err) + return } config := capi.DefaultConfig() config.Address = endpoint client, err := capi.NewClient(config) if err != nil { - log.Fatalf("failed to connect to Consul: %s", err) + log.Printf("failed to connect to Consul: %s", err) + return } // } node_name, err := client.Agent().NodeName() if err != nil { - log.Fatalf("failed to get node name: %s", err) // nolint:gocritic + log.Printf("failed to get node name: %s", err) + return } fmt.Println(len(node_name) > 0) diff --git a/modules/couchbase/couchbase.go b/modules/couchbase/couchbase.go index 5bd73a4eabd..e061ecf3a44 100644 --- a/modules/couchbase/couchbase.go +++ b/modules/couchbase/couchbase.go @@ -113,21 +113,23 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var couchbaseContainer *CouchbaseContainer + if container != nil { + couchbaseContainer = &CouchbaseContainer{container, config} + } if err != nil { - return nil, err + return couchbaseContainer, err } - couchbaseContainer := CouchbaseContainer{container, config} - if err = couchbaseContainer.initCluster(ctx); err != nil { - return nil, err + return couchbaseContainer, fmt.Errorf("init cluster: %w", err) } if err = couchbaseContainer.createBuckets(ctx); err != nil { - return nil, err + return couchbaseContainer, fmt.Errorf("create buckets: %w", err) } - return &couchbaseContainer, nil + return couchbaseContainer, nil } // StartContainer creates an instance of the Couchbase container type diff --git a/modules/couchbase/couchbase_test.go b/modules/couchbase/couchbase_test.go index 9fee51317ef..c8731158985 100644 --- a/modules/couchbase/couchbase_test.go +++ b/modules/couchbase/couchbase_test.go @@ -6,7 +6,9 @@ import ( "time" "github.com/couchbase/gocb/v2" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" tccouchbase "github.com/testcontainers/testcontainers-go/modules/couchbase" ) @@ -29,23 +31,13 @@ func TestCouchbaseWithCommunityContainer(t *testing.T) { WithFlushEnabled(false). WithPrimaryIndex(true) - container, err := tccouchbase.Run(ctx, communityEdition, tccouchbase.WithBuckets(bucket)) - if err != nil { - t.Fatal(err) - } + ctr, err := tccouchbase.Run(ctx, communityEdition, tccouchbase.WithBuckets(bucket)) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // } - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - - cluster, err := connectCluster(ctx, container) - if err != nil { - t.Fatalf("could not connect couchbase: %s", err) - } + cluster, err := connectCluster(ctx, ctr) + require.NoError(t, err) testBucketUsage(t, cluster.Bucket(bucketName)) } @@ -59,25 +51,15 @@ func TestCouchbaseWithEnterpriseContainer(t *testing.T) { WithReplicas(0). WithFlushEnabled(true). WithPrimaryIndex(true) - container, err := tccouchbase.Run(ctx, + ctr, err := tccouchbase.Run(ctx, enterpriseEdition, tccouchbase.WithBuckets(bucket), ) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - cluster, err := connectCluster(ctx, container) - if err != nil { - t.Fatalf("could not connect couchbase: %s", err) - } + cluster, err := connectCluster(ctx, ctr) + require.NoError(t, err) testBucketUsage(t, cluster.Bucket(bucketName)) } @@ -86,55 +68,48 @@ func TestWithCredentials(t *testing.T) { ctx := context.Background() bucketName := "testBucket" - _, err := tccouchbase.Run(ctx, + ctr, err := tccouchbase.Run(ctx, communityEdition, tccouchbase.WithAdminCredentials("testcontainers", "testcontainers.IS.cool!"), tccouchbase.WithBuckets(tccouchbase.NewBucket(bucketName))) - if err != nil { - t.Errorf("Expected error to be [%v] , got nil", err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) } func TestWithCredentials_Password_LessThan_6(t *testing.T) { ctx := context.Background() bucketName := "testBucket" - _, err := tccouchbase.Run(ctx, + ctr, err := tccouchbase.Run(ctx, communityEdition, tccouchbase.WithAdminCredentials("testcontainers", "12345"), tccouchbase.WithBuckets(tccouchbase.NewBucket(bucketName))) - - if err == nil { - t.Errorf("Expected error to be [%v] , got nil", err) - } + testcontainers.CleanupContainer(t, ctr) + require.Error(t, err) } func TestAnalyticsServiceWithCommunityContainer(t *testing.T) { ctx := context.Background() bucketName := "testBucket" - _, err := tccouchbase.Run(ctx, + ctr, err := tccouchbase.Run(ctx, communityEdition, tccouchbase.WithServiceAnalytics(), tccouchbase.WithBuckets(tccouchbase.NewBucket(bucketName))) - - if err == nil { - t.Errorf("Expected error to be [%v] , got nil", err) - } + testcontainers.CleanupContainer(t, ctr) + require.Error(t, err) } func TestEventingServiceWithCommunityContainer(t *testing.T) { ctx := context.Background() bucketName := "testBucket" - _, err := tccouchbase.Run(ctx, + ctr, err := tccouchbase.Run(ctx, communityEdition, tccouchbase.WithServiceEventing(), tccouchbase.WithBuckets(tccouchbase.NewBucket(bucketName))) - - if err == nil { - t.Errorf("Expected error to be [%v] , got nil", err) - } + testcontainers.CleanupContainer(t, ctr) + require.Error(t, err) } func testBucketUsage(t *testing.T, bucket *gocb.Bucket) { diff --git a/modules/couchbase/examples_test.go b/modules/couchbase/examples_test.go index 518b270613b..cc1a09a9dbd 100644 --- a/modules/couchbase/examples_test.go +++ b/modules/couchbase/examples_test.go @@ -7,6 +7,7 @@ import ( "github.com/couchbase/gocb/v2" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/couchbase" ) @@ -27,26 +28,29 @@ func ExampleRun() { couchbase.WithAdminCredentials("testcontainers", "testcontainers.IS.cool!"), couchbase.WithBuckets(bucket), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } defer func() { - if err := couchbaseContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(couchbaseContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := couchbaseContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) connectionString, err := couchbaseContainer.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) + log.Printf("failed to get connection string: %s", err) + return } cluster, err := gocb.Connect(connectionString, gocb.ClusterOptions{ @@ -54,12 +58,14 @@ func ExampleRun() { Password: couchbaseContainer.Password(), }) if err != nil { - log.Fatalf("failed to connect to cluster: %s", err) + log.Printf("failed to connect to cluster: %s", err) + return } buckets, err := cluster.Buckets().GetAllBuckets(nil) if err != nil { - log.Fatalf("failed to get buckets: %s", err) + log.Printf("failed to get buckets: %s", err) + return } fmt.Println(len(buckets)) diff --git a/modules/couchbase/go.mod b/modules/couchbase/go.mod index cc148407376..d07defe4ae1 100644 --- a/modules/couchbase/go.mod +++ b/modules/couchbase/go.mod @@ -6,7 +6,8 @@ require ( github.com/cenkalti/backoff/v4 v4.2.1 github.com/couchbase/gocb/v2 v2.7.2 github.com/docker/go-connections v0.5.0 - github.com/testcontainers/testcontainers-go v0.33.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 github.com/tidwall/gjson v1.17.1 ) @@ -22,6 +23,7 @@ require ( github.com/couchbase/goprotostellar v1.0.2 // indirect github.com/couchbaselabs/gocbconnstr/v2 v2.0.0-20230515165046-68b522a21131 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect @@ -45,6 +47,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -67,6 +70,7 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/grpc v1.64.1 // indirect google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/couchbase/go.sum b/modules/couchbase/go.sum index 7f40c3b63f0..f7b1f393fd0 100644 --- a/modules/couchbase/go.sum +++ b/modules/couchbase/go.sum @@ -91,8 +91,12 @@ github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -122,6 +126,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -272,6 +278,8 @@ google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGm google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/dolt/dolt.go b/modules/dolt/dolt.go index d819e18f5c1..87713233d8c 100644 --- a/modules/dolt/dolt.go +++ b/modules/dolt/dolt.go @@ -88,15 +88,20 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var dc *DoltContainer + if container != nil { + dc = &DoltContainer{Container: container, username: username, password: password, database: database} + } if err != nil { - return nil, err + return dc, err } - dc := &DoltContainer{container, username, password, database} - // dolthub/dolt-sql-server does not create user or database, so we do so here - err = dc.initialize(ctx, createUser) - return dc, err + if err = dc.initialize(ctx, createUser); err != nil { + return dc, fmt.Errorf("initialize: %w", err) + } + + return dc, nil } func (c *DoltContainer) initialize(ctx context.Context, createUser bool) error { diff --git a/modules/dolt/dolt_test.go b/modules/dolt/dolt_test.go index a511549c78f..61787e237b6 100644 --- a/modules/dolt/dolt_test.go +++ b/modules/dolt/dolt_test.go @@ -9,78 +9,64 @@ import ( // Import mysql into the scope of this package (required) _ "github.com/go-sql-driver/mysql" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/dolt" ) func TestDolt(t *testing.T) { ctx := context.Background() - container, err := dolt.Run(ctx, "dolthub/dolt-sql-server:1.32.4") - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := dolt.Run(ctx, "dolthub/dolt-sql-server:1.32.4") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions // connectionString { - connectionString, err := container.ConnectionString(ctx) + connectionString, err := ctr.ConnectionString(ctx) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) db, err := sql.Open("mysql", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) + _, err = db.Exec("CREATE TABLE IF NOT EXISTS a_table ( \n" + " `col_1` VARCHAR(128) NOT NULL, \n" + " `col_2` VARCHAR(128) NOT NULL, \n" + " PRIMARY KEY (`col_1`, `col_2`) \n" + ")") - if err != nil { - t.Errorf("error creating table: %+v\n", err) - } + require.NoError(t, err) } func TestDoltWithNonRootUserAndEmptyPassword(t *testing.T) { ctx := context.Background() - _, err := dolt.Run(ctx, + ctr, err := dolt.Run(ctx, "dolthub/dolt-sql-server:1.32.4", dolt.WithDatabase("foo"), dolt.WithUsername("test"), dolt.WithPassword("")) - if err.Error() != "empty password can be used only with the root user" { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.EqualError(t, err, "empty password can be used only with the root user") } func TestDoltWithPublicRemoteCloneUrl(t *testing.T) { ctx := context.Background() - _, err := dolt.Run(ctx, + ctr, err := dolt.Run(ctx, "dolthub/dolt-sql-server:1.32.4", dolt.WithDatabase("foo"), dolt.WithUsername("test"), dolt.WithPassword("test"), dolt.WithScripts(filepath.Join("testdata", "check_clone_public.sh")), dolt.WithDoltCloneRemoteUrl("fake-remote-url")) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) } func createTestCredsFile(t *testing.T) string { @@ -100,7 +86,7 @@ func TestDoltWithPrivateRemoteCloneUrl(t *testing.T) { ctx := context.Background() filename := createTestCredsFile(t) - _, err := dolt.Run(ctx, + ctr, err := dolt.Run(ctx, "dolthub/dolt-sql-server:1.32.4", dolt.WithDatabase("foo"), dolt.WithUsername("test"), @@ -109,93 +95,65 @@ func TestDoltWithPrivateRemoteCloneUrl(t *testing.T) { dolt.WithDoltCloneRemoteUrl("fake-remote-url"), dolt.WithDoltCredsPublicKey("fake-public-key"), dolt.WithCredsFile(filename)) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) } func TestDoltWithRootUserAndEmptyPassword(t *testing.T) { ctx := context.Background() - container, err := dolt.Run(ctx, + ctr, err := dolt.Run(ctx, "dolthub/dolt-sql-server:1.32.4", dolt.WithDatabase("foo"), dolt.WithUsername("root"), dolt.WithPassword("")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions - connectionString := container.MustConnectionString(ctx) + connectionString := ctr.MustConnectionString(ctx) db, err := sql.Open("mysql", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) + _, err = db.Exec("CREATE TABLE IF NOT EXISTS a_table ( \n" + " `col_1` VARCHAR(128) NOT NULL, \n" + " `col_2` VARCHAR(128) NOT NULL, \n" + " PRIMARY KEY (`col_1`, `col_2`) \n" + ")") - if err != nil { - t.Errorf("error creating table: %+v\n", err) - } + require.NoError(t, err) } func TestDoltWithScripts(t *testing.T) { ctx := context.Background() - container, err := dolt.Run(ctx, + ctr, err := dolt.Run(ctx, "dolthub/dolt-sql-server:1.32.4", dolt.WithScripts(filepath.Join("testdata", "schema.sql"))) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions - connectionString := container.MustConnectionString(ctx) + connectionString := ctr.MustConnectionString(ctx) db, err := sql.Open("mysql", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) + stmt, err := db.Prepare("SELECT name from profile") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + defer stmt.Close() row := stmt.QueryRow() var name string err = row.Scan(&name) - if err != nil { - t.Errorf("error fetching data") - } - if name != "profile 1" { - t.Fatal("The expected record was not found in the database.") - } + require.NoError(t, err) + require.Equal(t, "profile 1", name) } diff --git a/modules/dolt/examples_test.go b/modules/dolt/examples_test.go index 73e430d8719..ddbf81b079b 100644 --- a/modules/dolt/examples_test.go +++ b/modules/dolt/examples_test.go @@ -7,6 +7,7 @@ import ( "log" "path/filepath" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/dolt" ) @@ -22,21 +23,21 @@ func ExampleRun() { dolt.WithPassword("password"), dolt.WithScripts(filepath.Join("testdata", "schema.sql")), ) - if err != nil { - log.Fatalf("failed to run dolt container: %s", err) // nolint:gocritic - } - - // Clean up the container defer func() { - if err := doltContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate dolt container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(doltContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to run dolt container: %s", err) + return + } // } state, err := doltContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -56,37 +57,41 @@ func ExampleRun_connect() { dolt.WithPassword("password"), dolt.WithScripts(filepath.Join("testdata", "schema.sql")), ) - if err != nil { - log.Fatalf("failed to run dolt container: %s", err) // nolint:gocritic - } - defer func() { - if err := doltContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate dolt container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(doltContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to run dolt container: %s", err) + return + } connectionString := doltContainer.MustConnectionString(ctx) db, err := sql.Open("mysql", connectionString) if err != nil { - log.Fatalf("failed to open database connection: %s", err) // nolint:gocritic + log.Printf("failed to open database connection: %s", err) + return } defer db.Close() if err = db.Ping(); err != nil { - log.Fatalf("failed to ping database: %s", err) // nolint:gocritic + log.Printf("failed to ping database: %s", err) + return } stmt, err := db.Prepare("SELECT dolt_version();") if err != nil { - log.Fatalf("failed to prepate sql statement: %s", err) // nolint:gocritic + log.Printf("failed to prepate sql statement: %s", err) + return } defer stmt.Close() row := stmt.QueryRow() version := "" err = row.Scan(&version) if err != nil { - log.Fatalf("failed to scan row: %s", err) // nolint:gocritic + log.Printf("failed to scan row: %s", err) + return } fmt.Println(version) diff --git a/modules/dolt/go.mod b/modules/dolt/go.mod index 804efb84834..a4c2376a9b5 100644 --- a/modules/dolt/go.mod +++ b/modules/dolt/go.mod @@ -4,7 +4,8 @@ go 1.22 require ( github.com/go-sql-driver/mysql v1.7.1 - github.com/testcontainers/testcontainers-go v0.33.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 ) require ( @@ -16,6 +17,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect @@ -27,6 +29,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.4 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -38,6 +41,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -54,6 +58,7 @@ require ( golang.org/x/sys v0.21.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/dolt/go.sum b/modules/dolt/go.sum index e2bb93b49dc..dcc1a8d165a 100644 --- a/modules/dolt/go.sum +++ b/modules/dolt/go.sum @@ -16,6 +16,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -54,6 +55,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -80,6 +85,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -176,6 +183,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/elasticsearch/elasticsearch.go b/modules/elasticsearch/elasticsearch.go index 10a863c589c..5fab503b368 100644 --- a/modules/elasticsearch/elasticsearch.go +++ b/modules/elasticsearch/elasticsearch.go @@ -95,33 +95,32 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, req) - if err != nil { - return nil, err + var esContainer *ElasticsearchContainer + if container != nil { + esContainer = &ElasticsearchContainer{Container: container, Settings: *settings} } - - esContainer := &ElasticsearchContainer{Container: container, Settings: *settings} - - address, err := configureAddress(ctx, esContainer) if err != nil { - return nil, err + return esContainer, fmt.Errorf("generic container: %w", err) } - esContainer.Settings.Address = address + if err := esContainer.configureAddress(ctx); err != nil { + return esContainer, fmt.Errorf("configure address: %w", err) + } return esContainer, nil } // configureAddress sets the address of the Elasticsearch container. // If the certificate is set, it will use https as protocol, otherwise http. -func configureAddress(ctx context.Context, c *ElasticsearchContainer) (string, error) { +func (c *ElasticsearchContainer) configureAddress(ctx context.Context) error { containerPort, err := c.MappedPort(ctx, defaultHTTPPort+"/tcp") if err != nil { - return "", err + return fmt.Errorf("mapped port: %w", err) } host, err := c.Host(ctx) if err != nil { - return "", err + return fmt.Errorf("host: %w", err) } proto := "http" @@ -129,7 +128,9 @@ func configureAddress(ctx context.Context, c *ElasticsearchContainer) (string, e proto = "https" } - return fmt.Sprintf("%s://%s:%s", proto, host, containerPort.Port()), nil + c.Settings.Address = fmt.Sprintf("%s://%s:%s", proto, host, containerPort.Port()) + + return nil } // configureCertificate transfers the certificate settings to the container request. diff --git a/modules/elasticsearch/elasticsearch_test.go b/modules/elasticsearch/elasticsearch_test.go index 1bc7d794565..69c4149fc4c 100644 --- a/modules/elasticsearch/elasticsearch_test.go +++ b/modules/elasticsearch/elasticsearch_test.go @@ -8,6 +8,8 @@ import ( "net/http" "testing" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/elasticsearch" ) @@ -80,22 +82,13 @@ func TestElasticsearch(t *testing.T) { } esContainer, err := elasticsearch.Run(ctx, tt.image, opts...) - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - if err := esContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, esContainer) + require.NoError(t, err) httpClient := configureHTTPClient(esContainer) req, err := http.NewRequest("GET", esContainer.Settings.Address, nil) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // set the password for the request using the Authentication header if tt.passwordCustomiser != nil { @@ -184,23 +177,16 @@ func TestElasticsearch8WithoutSSL(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { ctx := context.Background() - container, err := elasticsearch.Run( + ctr, err := elasticsearch.Run( ctx, baseImage8, testcontainers.WithEnv(map[string]string{ test.configKey: "false", })) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - - if len(container.Settings.CACert) > 0 { + if len(ctr.Settings.CACert) > 0 { t.Fatal("expected CA cert to be empty") } }) @@ -210,26 +196,19 @@ func TestElasticsearch8WithoutSSL(t *testing.T) { func TestElasticsearch8WithoutCredentials(t *testing.T) { ctx := context.Background() - container, err := elasticsearch.Run(ctx, baseImage8) - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := elasticsearch.Run(ctx, baseImage8) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - httpClient := configureHTTPClient(container) + httpClient := configureHTTPClient(ctr) - req, err := http.NewRequest("GET", container.Settings.Address, nil) + req, err := http.NewRequest("GET", ctr.Settings.Address, nil) if err != nil { t.Fatal(err) } // elastic:changeme are the default credentials for Elasticsearch 8 - req.SetBasicAuth(container.Settings.Username, container.Settings.Password) + req.SetBasicAuth(ctr.Settings.Username, ctr.Settings.Password) resp, err := httpClient.Do(req) if err != nil { @@ -253,7 +232,8 @@ func TestElasticsearchOSSCannotuseWithPassword(t *testing.T) { ossImage := elasticsearch.DefaultBaseImageOSS + ":7.9.2" - _, err := elasticsearch.Run(ctx, ossImage, elasticsearch.WithPassword("foo")) + ctr, err := elasticsearch.Run(ctx, ossImage, elasticsearch.WithPassword("foo")) + testcontainers.CleanupContainer(t, ctr) if err == nil { t.Fatal(err, "Should not be able to use WithPassword with OSS image.") } diff --git a/modules/elasticsearch/examples_test.go b/modules/elasticsearch/examples_test.go index db578a46ee7..f4ada5df600 100644 --- a/modules/elasticsearch/examples_test.go +++ b/modules/elasticsearch/examples_test.go @@ -9,6 +9,7 @@ import ( es "github.com/elastic/go-elasticsearch/v8" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/elasticsearch" ) @@ -16,19 +17,21 @@ func ExampleRun() { // runElasticsearchContainer { ctx := context.Background() elasticsearchContainer, err := elasticsearch.Run(ctx, "docker.elastic.co/elasticsearch/elasticsearch:8.9.0") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } defer func() { - if err := elasticsearchContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(elasticsearchContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := elasticsearchContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -45,15 +48,15 @@ func ExampleRun_withUsingPassword() { "docker.elastic.co/elasticsearch/elasticsearch:7.9.2", elasticsearch.WithPassword("foo"), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } defer func() { - err := elasticsearchContainer.Terminate(ctx) - if err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(elasticsearchContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } fmt.Println(strings.HasPrefix(elasticsearchContainer.Settings.Address, "http://")) @@ -72,15 +75,15 @@ func ExampleRun_connectUsingElasticsearchClient() { "docker.elastic.co/elasticsearch/elasticsearch:8.9.0", elasticsearch.WithPassword("foo"), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } defer func() { - err := elasticsearchContainer.Terminate(ctx) - if err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(elasticsearchContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } cfg := es.Config{ Addresses: []string{ @@ -93,19 +96,22 @@ func ExampleRun_connectUsingElasticsearchClient() { esClient, err := es.NewClient(cfg) if err != nil { - log.Fatalf("error creating the client: %s", err) // nolint:gocritic + log.Printf("error creating the client: %s", err) + return } resp, err := esClient.Info() if err != nil { - log.Fatalf("error getting response: %s", err) + log.Printf("error getting response: %s", err) + return } defer resp.Body.Close() // } var esResp ElasticsearchResponse if err := json.NewDecoder(resp.Body).Decode(&esResp); err != nil { - log.Fatalf("error decoding response: %s", err) + log.Printf("error decoding response: %s", err) + return } fmt.Println(esResp.Tagline) diff --git a/modules/gcloud/bigquery.go b/modules/gcloud/bigquery.go index 54363dc2f22..ce8ff1fcbd6 100644 --- a/modules/gcloud/bigquery.go +++ b/modules/gcloud/bigquery.go @@ -33,18 +33,5 @@ func RunBigQuery(ctx context.Context, img string, opts ...testcontainers.Contain req.Cmd = []string{"--project", settings.ProjectID} - container, err := testcontainers.GenericContainer(ctx, req) - if err != nil { - return nil, err - } - - bigQueryContainer, err := newGCloudContainer(ctx, 9050, container, settings) - if err != nil { - return nil, err - } - - // always prepend http:// to the URI - bigQueryContainer.URI = "http://" + bigQueryContainer.URI - - return bigQueryContainer, nil + return newGCloudContainer(ctx, req, 9050, settings, "http://") } diff --git a/modules/gcloud/bigquery_test.go b/modules/gcloud/bigquery_test.go index a39347a23f1..957b17e3e55 100644 --- a/modules/gcloud/bigquery_test.go +++ b/modules/gcloud/bigquery_test.go @@ -13,6 +13,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/gcloud" ) @@ -25,16 +26,15 @@ func ExampleRunBigQueryContainer() { "ghcr.io/goccy/bigquery-emulator:0.4.3", gcloud.WithProjectID("bigquery-project"), ) - if err != nil { - log.Fatalf("failed to run container: %v", err) - } - - // Clean up the container defer func() { - if err := bigQueryContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %v", err) + if err := testcontainers.TerminateContainer(bigQueryContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to run container: %v", err) + return + } // } // bigQueryClient { @@ -49,7 +49,8 @@ func ExampleRunBigQueryContainer() { client, err := bigquery.NewClient(ctx, projectID, opts...) if err != nil { - log.Fatalf("failed to create bigquery client: %v", err) // nolint:gocritic + log.Printf("failed to create bigquery client: %v", err) + return } defer client.Close() // } @@ -57,13 +58,15 @@ func ExampleRunBigQueryContainer() { createFnQuery := client.Query("CREATE FUNCTION testr(arr ARRAY>) AS ((SELECT SUM(IF(elem.name = \"foo\",elem.val,null)) FROM UNNEST(arr) AS elem))") _, err = createFnQuery.Read(ctx) if err != nil { - log.Fatalf("failed to create function: %v", err) + log.Printf("failed to create function: %v", err) + return } selectQuery := client.Query("SELECT testr([STRUCT(\"foo\", 10), STRUCT(\"bar\", 40), STRUCT(\"foo\", 20)])") it, err := selectQuery.Read(ctx) if err != nil { - log.Fatalf("failed to read query: %v", err) + log.Printf("failed to read query: %v", err) + return } var val []bigquery.Value @@ -73,7 +76,8 @@ func ExampleRunBigQueryContainer() { break } if err != nil { - log.Fatalf("failed to iterate: %v", err) + log.Printf("failed to iterate: %v", err) + return } } diff --git a/modules/gcloud/bigtable.go b/modules/gcloud/bigtable.go index 4bea521ff16..134f14d1d60 100644 --- a/modules/gcloud/bigtable.go +++ b/modules/gcloud/bigtable.go @@ -2,7 +2,6 @@ package gcloud import ( "context" - "fmt" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" @@ -33,13 +32,8 @@ func RunBigTable(ctx context.Context, img string, opts ...testcontainers.Contain req.Cmd = []string{ "/bin/sh", "-c", - "gcloud beta emulators bigtable start --host-port 0.0.0.0:9000 " + fmt.Sprintf("--project=%s", settings.ProjectID), + "gcloud beta emulators bigtable start --host-port 0.0.0.0:9000 --project=" + settings.ProjectID, } - container, err := testcontainers.GenericContainer(ctx, req) - if err != nil { - return nil, err - } - - return newGCloudContainer(ctx, 9000, container, settings) + return newGCloudContainer(ctx, req, 9000, settings, "") } diff --git a/modules/gcloud/bigtable_test.go b/modules/gcloud/bigtable_test.go index 15409e2b0e1..553581bcc4e 100644 --- a/modules/gcloud/bigtable_test.go +++ b/modules/gcloud/bigtable_test.go @@ -10,6 +10,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/gcloud" ) @@ -22,16 +23,15 @@ func ExampleRunBigTableContainer() { "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators", gcloud.WithProjectID("bigtable-project"), ) - if err != nil { - log.Fatalf("failed to run container: %v", err) - } - - // Clean up the container defer func() { - if err := bigTableContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %v", err) + if err := testcontainers.TerminateContainer(bigTableContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to run container: %v", err) + return + } // } // bigTableAdminClient { @@ -49,24 +49,28 @@ func ExampleRunBigTableContainer() { } adminClient, err := bigtable.NewAdminClient(ctx, projectId, instanceId, options...) if err != nil { - log.Fatalf("failed to create admin client: %v", err) // nolint:gocritic + log.Printf("failed to create admin client: %v", err) + return } defer adminClient.Close() // } err = adminClient.CreateTable(ctx, tableName) if err != nil { - log.Fatalf("failed to create table: %v", err) + log.Printf("failed to create table: %v", err) + return } err = adminClient.CreateColumnFamily(ctx, tableName, "name") if err != nil { - log.Fatalf("failed to create column family: %v", err) + log.Printf("failed to create column family: %v", err) + return } // bigTableClient { client, err := bigtable.NewClient(ctx, projectId, instanceId, options...) if err != nil { - log.Fatalf("failed to create client: %v", err) + log.Printf("failed to create client: %v", err) + return } defer client.Close() // } @@ -77,12 +81,14 @@ func ExampleRunBigTableContainer() { mut.Set("name", "firstName", bigtable.Now(), []byte("Gopher")) err = tbl.Apply(ctx, "1", mut) if err != nil { - log.Fatalf("failed to apply mutation: %v", err) + log.Printf("failed to apply mutation: %v", err) + return } row, err := tbl.ReadRow(ctx, "1", bigtable.RowFilter(bigtable.FamilyFilter("name"))) if err != nil { - log.Fatalf("failed to read row: %v", err) + log.Printf("failed to read row: %v", err) + return } fmt.Println(string(row["name"][0].Value)) diff --git a/modules/gcloud/datastore.go b/modules/gcloud/datastore.go index 92ab6718421..caf53e98799 100644 --- a/modules/gcloud/datastore.go +++ b/modules/gcloud/datastore.go @@ -2,7 +2,6 @@ package gcloud import ( "context" - "fmt" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" @@ -33,13 +32,8 @@ func RunDatastore(ctx context.Context, img string, opts ...testcontainers.Contai req.Cmd = []string{ "/bin/sh", "-c", - "gcloud beta emulators datastore start --host-port 0.0.0.0:8081 " + fmt.Sprintf("--project=%s", settings.ProjectID), + "gcloud beta emulators datastore start --host-port 0.0.0.0:8081 --project=" + settings.ProjectID, } - container, err := testcontainers.GenericContainer(ctx, req) - if err != nil { - return nil, err - } - - return newGCloudContainer(ctx, 8081, container, settings) + return newGCloudContainer(ctx, req, 8081, settings, "") } diff --git a/modules/gcloud/datastore_test.go b/modules/gcloud/datastore_test.go index 0cf04780ef7..fa056bbf634 100644 --- a/modules/gcloud/datastore_test.go +++ b/modules/gcloud/datastore_test.go @@ -10,6 +10,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/gcloud" ) @@ -22,16 +23,15 @@ func ExampleRunDatastoreContainer() { "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators", gcloud.WithProjectID("datastore-project"), ) - if err != nil { - log.Fatalf("failed to run container: %v", err) - } - - // Clean up the container defer func() { - if err := datastoreContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %v", err) + if err := testcontainers.TerminateContainer(datastoreContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to run container: %v", err) + return + } // } // datastoreClient { @@ -45,7 +45,8 @@ func ExampleRunDatastoreContainer() { dsClient, err := datastore.NewClient(ctx, projectID, options...) if err != nil { - log.Fatalf("failed to create client: %v", err) // nolint:gocritic + log.Printf("failed to create client: %v", err) + return } defer dsClient.Close() // } @@ -60,13 +61,15 @@ func ExampleRunDatastoreContainer() { } _, err = dsClient.Put(ctx, k, &data) if err != nil { - log.Fatalf("failed to put data: %v", err) + log.Printf("failed to put data: %v", err) + return } saved := Task{} err = dsClient.Get(ctx, k, &saved) if err != nil { - log.Fatalf("failed to get data: %v", err) + log.Printf("failed to get data: %v", err) + return } fmt.Println(saved.Description) diff --git a/modules/gcloud/firestore.go b/modules/gcloud/firestore.go index 7f9ced72f7a..297b47f80c2 100644 --- a/modules/gcloud/firestore.go +++ b/modules/gcloud/firestore.go @@ -2,7 +2,6 @@ package gcloud import ( "context" - "fmt" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" @@ -33,13 +32,8 @@ func RunFirestore(ctx context.Context, img string, opts ...testcontainers.Contai req.Cmd = []string{ "/bin/sh", "-c", - "gcloud beta emulators firestore start --host-port 0.0.0.0:8080 " + fmt.Sprintf("--project=%s", settings.ProjectID), + "gcloud beta emulators firestore start --host-port 0.0.0.0:8080 --project=" + settings.ProjectID, } - container, err := testcontainers.GenericContainer(ctx, req) - if err != nil { - return nil, err - } - - return newGCloudContainer(ctx, 8080, container, settings) + return newGCloudContainer(ctx, req, 8080, settings, "") } diff --git a/modules/gcloud/firestore_test.go b/modules/gcloud/firestore_test.go index 54e03e4522a..83ccd0464c0 100644 --- a/modules/gcloud/firestore_test.go +++ b/modules/gcloud/firestore_test.go @@ -10,6 +10,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/gcloud" ) @@ -32,16 +33,15 @@ func ExampleRunFirestoreContainer() { "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators", gcloud.WithProjectID("firestore-project"), ) - if err != nil { - log.Fatalf("failed to run container: %v", err) - } - - // Clean up the container defer func() { - if err := firestoreContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %v", err) + if err := testcontainers.TerminateContainer(firestoreContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to run container: %v", err) + return + } // } // firestoreClient { @@ -49,13 +49,15 @@ func ExampleRunFirestoreContainer() { conn, err := grpc.NewClient(firestoreContainer.URI, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithPerRPCCredentials(emulatorCreds{})) if err != nil { - log.Fatalf("failed to dial: %v", err) // nolint:gocritic + log.Printf("failed to dial: %v", err) + return } options := []option.ClientOption{option.WithGRPCConn(conn)} client, err := firestore.NewClient(ctx, projectID, options...) if err != nil { - log.Fatalf("failed to create client: %v", err) + log.Printf("failed to create client: %v", err) + return } defer client.Close() // } @@ -74,17 +76,20 @@ func ExampleRunFirestoreContainer() { } _, err = docRef.Create(ctx, data) if err != nil { - log.Fatalf("failed to create document: %v", err) + log.Printf("failed to create document: %v", err) + return } docsnap, err := docRef.Get(ctx) if err != nil { - log.Fatalf("failed to get document: %v", err) + log.Printf("failed to get document: %v", err) + return } var saved Person if err := docsnap.DataTo(&saved); err != nil { - log.Fatalf("failed to convert data: %v", err) + log.Printf("failed to convert data: %v", err) + return } fmt.Println(saved.Firstname, saved.Lastname) diff --git a/modules/gcloud/gcloud.go b/modules/gcloud/gcloud.go index a5886dc7437..7f0e7cdffb5 100644 --- a/modules/gcloud/gcloud.go +++ b/modules/gcloud/gcloud.go @@ -18,26 +18,29 @@ type GCloudContainer struct { } // newGCloudContainer creates a new GCloud container, obtaining the URL to access the container from the specified port. -func newGCloudContainer(ctx context.Context, port int, c testcontainers.Container, settings options) (*GCloudContainer, error) { +func newGCloudContainer(ctx context.Context, req testcontainers.GenericContainerRequest, port int, settings options, urlPrefix string) (*GCloudContainer, error) { + container, err := testcontainers.GenericContainer(ctx, req) + var c *GCloudContainer + if container != nil { + c = &GCloudContainer{Container: container, Settings: settings} + } + if err != nil { + return c, fmt.Errorf("generic container: %w", err) + } + mappedPort, err := c.MappedPort(ctx, nat.Port(fmt.Sprintf("%d/tcp", port))) if err != nil { - return nil, err + return c, fmt.Errorf("mapped port: %w", err) } hostIP, err := c.Host(ctx) if err != nil { - return nil, err + return c, fmt.Errorf("host: %w", err) } - uri := fmt.Sprintf("%s:%s", hostIP, mappedPort.Port()) - - gCloudContainer := &GCloudContainer{ - Container: c, - Settings: settings, - URI: uri, - } + c.URI = urlPrefix + hostIP + ":" + mappedPort.Port() - return gCloudContainer, nil + return c, nil } type options struct { diff --git a/modules/gcloud/go.mod b/modules/gcloud/go.mod index 00a723f58c2..2aac0272557 100644 --- a/modules/gcloud/go.mod +++ b/modules/gcloud/go.mod @@ -33,6 +33,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect @@ -68,10 +69,12 @@ require ( github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pierrec/lz4/v4 v4.1.18 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/stretchr/testify v1.9.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect @@ -97,6 +100,7 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/gcloud/go.sum b/modules/gcloud/go.sum index 07f1f7f404a..7283a495029 100644 --- a/modules/gcloud/go.sum +++ b/modules/gcloud/go.sum @@ -146,6 +146,10 @@ github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -180,6 +184,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -362,6 +368,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/gcloud/pubsub.go b/modules/gcloud/pubsub.go index a2a4e74a1c3..d57ea35c16c 100644 --- a/modules/gcloud/pubsub.go +++ b/modules/gcloud/pubsub.go @@ -2,7 +2,6 @@ package gcloud import ( "context" - "fmt" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" @@ -33,13 +32,8 @@ func RunPubsub(ctx context.Context, img string, opts ...testcontainers.Container req.Cmd = []string{ "/bin/sh", "-c", - "gcloud beta emulators pubsub start --host-port 0.0.0.0:8085 " + fmt.Sprintf("--project=%s", settings.ProjectID), + "gcloud beta emulators pubsub start --host-port 0.0.0.0:8085 --project=" + settings.ProjectID, } - container, err := testcontainers.GenericContainer(ctx, req) - if err != nil { - return nil, err - } - - return newGCloudContainer(ctx, 8085, container, settings) + return newGCloudContainer(ctx, req, 8085, settings, "") } diff --git a/modules/gcloud/pubsub_test.go b/modules/gcloud/pubsub_test.go index e0718a4d034..151df3a5467 100644 --- a/modules/gcloud/pubsub_test.go +++ b/modules/gcloud/pubsub_test.go @@ -10,6 +10,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/gcloud" ) @@ -22,16 +23,15 @@ func ExampleRunPubsubContainer() { "gcr.io/google.com/cloudsdktool/cloud-sdk:367.0.0-emulators", gcloud.WithProjectID("pubsub-project"), ) - if err != nil { - log.Fatalf("failed to run container: %v", err) - } - - // Clean up the container defer func() { - if err := pubsubContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %v", err) + if err := testcontainers.TerminateContainer(pubsubContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to run container: %v", err) + return + } // } // pubsubClient { @@ -39,30 +39,35 @@ func ExampleRunPubsubContainer() { conn, err := grpc.NewClient(pubsubContainer.URI, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { - log.Fatalf("failed to dial: %v", err) // nolint:gocritic + log.Printf("failed to dial: %v", err) + return } options := []option.ClientOption{option.WithGRPCConn(conn)} client, err := pubsub.NewClient(ctx, projectID, options...) if err != nil { - log.Fatalf("failed to create client: %v", err) + log.Printf("failed to create client: %v", err) + return } defer client.Close() // } topic, err := client.CreateTopic(ctx, "greetings") if err != nil { - log.Fatalf("failed to create topic: %v", err) + log.Printf("failed to create topic: %v", err) + return } subscription, err := client.CreateSubscription(ctx, "subscription", pubsub.SubscriptionConfig{Topic: topic}) if err != nil { - log.Fatalf("failed to create subscription: %v", err) + log.Printf("failed to create subscription: %v", err) + return } result := topic.Publish(ctx, &pubsub.Message{Data: []byte("Hello World")}) _, err = result.Get(ctx) if err != nil { - log.Fatalf("failed to publish message: %v", err) + log.Printf("failed to publish message: %v", err) + return } var data []byte @@ -73,7 +78,8 @@ func ExampleRunPubsubContainer() { defer cancel() }) if err != nil { - log.Fatalf("failed to receive message: %v", err) + log.Printf("failed to receive message: %v", err) + return } fmt.Println(string(data)) diff --git a/modules/gcloud/spanner.go b/modules/gcloud/spanner.go index d57154ab1db..8b306db4ce4 100644 --- a/modules/gcloud/spanner.go +++ b/modules/gcloud/spanner.go @@ -29,10 +29,5 @@ func RunSpanner(ctx context.Context, img string, opts ...testcontainers.Containe return nil, err } - container, err := testcontainers.GenericContainer(ctx, req) - if err != nil { - return nil, err - } - - return newGCloudContainer(ctx, 9010, container, settings) + return newGCloudContainer(ctx, req, 9010, settings, "") } diff --git a/modules/gcloud/spanner_test.go b/modules/gcloud/spanner_test.go index 10fdec441f9..0e976c3dff5 100644 --- a/modules/gcloud/spanner_test.go +++ b/modules/gcloud/spanner_test.go @@ -15,6 +15,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/gcloud" ) @@ -27,16 +28,15 @@ func ExampleRunSpannerContainer() { "gcr.io/cloud-spanner-emulator/emulator:1.4.0", gcloud.WithProjectID("spanner-project"), ) - if err != nil { - log.Fatalf("failed to run container: %v", err) - } - - // Clean up the container defer func() { - if err := spannerContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %v", err) + if err := testcontainers.TerminateContainer(spannerContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to run container: %v", err) + return + } // } // spannerAdminClient { @@ -56,7 +56,8 @@ func ExampleRunSpannerContainer() { instanceAdmin, err := instance.NewInstanceAdminClient(ctx, options...) if err != nil { - log.Fatalf("failed to create instance admin client: %v", err) // nolint:gocritic + log.Printf("failed to create instance admin client: %v", err) + return } defer instanceAdmin.Close() // } @@ -69,18 +70,21 @@ func ExampleRunSpannerContainer() { }, }) if err != nil { - log.Fatalf("failed to create instance: %v", err) + log.Printf("failed to create instance: %v", err) + return } _, err = instanceOp.Wait(ctx) if err != nil { - log.Fatalf("failed to wait for instance creation: %v", err) + log.Printf("failed to wait for instance creation: %v", err) + return } // spannerDBAdminClient { c, err := database.NewDatabaseAdminClient(ctx, options...) if err != nil { - log.Fatalf("failed to create admin client: %v", err) + log.Printf("failed to create admin client: %v", err) + return } defer c.Close() // } @@ -93,17 +97,20 @@ func ExampleRunSpannerContainer() { }, }) if err != nil { - log.Fatalf("failed to create database: %v", err) + log.Printf("failed to create database: %v", err) + return } _, err = databaseOp.Wait(ctx) if err != nil { - log.Fatalf("failed to wait for database creation: %v", err) + log.Printf("failed to wait for database creation: %v", err) + return } db := fmt.Sprintf("projects/%s/instances/%s/databases/%s", projectId, instanceId, databaseName) client, err := spanner.NewClient(ctx, db, options...) if err != nil { - log.Fatalf("failed to create client: %v", err) + log.Printf("failed to create client: %v", err) + return } defer client.Close() @@ -113,18 +120,21 @@ func ExampleRunSpannerContainer() { []interface{}{"Go", "Gopher"}), }) if err != nil { - log.Fatalf("failed to apply mutation: %v", err) + log.Printf("failed to apply mutation: %v", err) + return } row, err := client.Single().ReadRow(ctx, "Languages", spanner.Key{"Go"}, []string{"mascot"}) if err != nil { - log.Fatalf("failed to read row: %v", err) + log.Printf("failed to read row: %v", err) + return } var mascot string err = row.ColumnByName("Mascot", &mascot) if err != nil { - log.Fatalf("failed to read column: %v", err) + log.Printf("failed to read column: %v", err) + return } fmt.Println(mascot) diff --git a/modules/grafana-lgtm/examples_test.go b/modules/grafana-lgtm/examples_test.go index c13f1bbd62e..a31a6b873f4 100644 --- a/modules/grafana-lgtm/examples_test.go +++ b/modules/grafana-lgtm/examples_test.go @@ -4,10 +4,9 @@ import ( "context" "errors" "fmt" - golog "log" + "log" "log/slog" "math/rand" - "sync" "time" "go.opentelemetry.io/contrib/bridges/otelslog" @@ -22,10 +21,12 @@ import ( "go.opentelemetry.io/otel/log/global" metricsapi "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" - "go.opentelemetry.io/otel/sdk/log" + otellog "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/trace" + "golang.org/x/sync/errgroup" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/grafanalgtm" ) @@ -34,21 +35,21 @@ func ExampleRun() { ctx := context.Background() grafanaLgtmContainer, err := grafanalgtm.Run(ctx, "grafana/otel-lgtm:0.6.0") - if err != nil { - golog.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := grafanaLgtmContainer.Terminate(ctx); err != nil { - golog.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(grafanaLgtmContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := grafanaLgtmContainer.State(ctx) if err != nil { - golog.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -61,66 +62,72 @@ func ExampleRun_otelCollector() { ctx := context.Background() ctr, err := grafanalgtm.Run(ctx, "grafana/otel-lgtm:0.6.0", grafanalgtm.WithAdminCredentials("admin", "123456789")) - if err != nil { - golog.Fatalf("failed to start Grafana LGTM container: %s", err) - } defer func() { - if err := ctr.Terminate(ctx); err != nil { - golog.Fatalf("failed to terminate Grafana LGTM container: %s", err) + if err := testcontainers.TerminateContainer(ctr); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start Grafana LGTM container: %s", err) + return + } // Set up OpenTelemetry. otelShutdown, err := setupOTelSDK(ctx, ctr) if err != nil { + log.Printf("failed to set up OpenTelemetry: %s", err) return } // Handle shutdown properly so nothing leaks. defer func() { - err = errors.Join(err, otelShutdown(context.Background())) + if err := otelShutdown(context.Background()); err != nil { + log.Printf("failed to shutdown OpenTelemetry: %s", err) + } }() // roll dice 10000 times, concurrently max := 10_000 - wg := sync.WaitGroup{} + var wg errgroup.Group for i := 0; i < max; i++ { - wg.Add(1) - - go func() { - defer wg.Done() - rolldice(ctx) - }() + wg.Go(func() error { + return rolldice(ctx) + }) } - wg.Wait() + if err = wg.Wait(); err != nil { + log.Printf("failed to roll dice: %s", err) + return + } // Output: - // shutdown errors: } // setupOTelSDK bootstraps the OpenTelemetry pipeline. // If it does not return an error, make sure to call shutdown for proper cleanup. -func setupOTelSDK(ctx context.Context, ctr *grafanalgtm.GrafanaLGTMContainer) (shutdown func(context.Context) error, err error) { // nolint:nonamedreturns // this is a pattern in the OpenTelemetry Go SDK +func setupOTelSDK(ctx context.Context, ctr *grafanalgtm.GrafanaLGTMContainer) (shutdown func(context.Context) error, err error) { //nolint:nonamedreturns // this is a pattern in the OpenTelemetry Go SDK var shutdownFuncs []func(context.Context) error // shutdown calls cleanup functions registered via shutdownFuncs. // The errors from the calls are joined. // Each registered cleanup will be invoked once. shutdown = func(ctx context.Context) error { - var err error + var errs []error for _, fn := range shutdownFuncs { - err = errors.Join(err, fn(ctx)) + if err := fn(ctx); err != nil { + errs = append(errs, err) + } } - shutdownFuncs = nil - fmt.Println("shutdown errors:", err) - return err - } - // handleErr calls shutdown for cleanup and makes sure that all errors are returned. - handleErr := func(inErr error) { - err = errors.Join(inErr, shutdown(ctx)) + return errors.Join(errs...) } + // Ensure that the OpenTelemetry SDK is properly shutdown. + defer func() { + if err != nil { + err = errors.Join(shutdown(ctx)) + } + }() + prop := propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, @@ -139,13 +146,12 @@ func setupOTelSDK(ctx context.Context, ctr *grafanalgtm.GrafanaLGTMContainer) (s ), ) if err != nil { - return nil, err + return nil, fmt.Errorf("new trace exporter: %w", err) } tracerProvider := trace.NewTracerProvider(trace.WithBatcher(traceExporter)) if err != nil { - handleErr(err) - return + return nil, fmt.Errorf("new trace provider: %w", err) } shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown) otel.SetTracerProvider(tracerProvider) @@ -155,7 +161,7 @@ func setupOTelSDK(ctx context.Context, ctr *grafanalgtm.GrafanaLGTMContainer) (s otlpmetrichttp.WithEndpoint(otlpHttpEndpoint), ) if err != nil { - return nil, err + return nil, fmt.Errorf("new metric exporter: %w", err) } // The exporter embeds a default OpenTelemetry Reader and @@ -163,7 +169,7 @@ func setupOTelSDK(ctx context.Context, ctr *grafanalgtm.GrafanaLGTMContainer) (s // both a Reader and Collector. prometheusExporter, err := prometheus.New() if err != nil { - return nil, err + return nil, fmt.Errorf("new prometheus exporter: %w", err) } meterProvider := metric.NewMeterProvider( @@ -171,9 +177,9 @@ func setupOTelSDK(ctx context.Context, ctr *grafanalgtm.GrafanaLGTMContainer) (s metric.WithReader(prometheusExporter), ) if err != nil { - handleErr(err) - return + return nil, fmt.Errorf("new meter provider: %w", err) } + shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown) otel.SetMeterProvider(meterProvider) @@ -182,23 +188,22 @@ func setupOTelSDK(ctx context.Context, ctr *grafanalgtm.GrafanaLGTMContainer) (s otlploghttp.WithEndpoint(otlpHttpEndpoint), ) if err != nil { - return nil, err + return nil, fmt.Errorf("new log exporter: %w", err) } - loggerProvider := log.NewLoggerProvider(log.WithProcessor(log.NewBatchProcessor(logExporter))) + loggerProvider := otellog.NewLoggerProvider(otellog.WithProcessor(otellog.NewBatchProcessor(logExporter))) if err != nil { - handleErr(err) - return + return nil, fmt.Errorf("new logger provider: %w", err) } + shutdownFuncs = append(shutdownFuncs, loggerProvider.Shutdown) global.SetLoggerProvider(loggerProvider) - err = runtime.Start(runtime.WithMinimumReadMemStatsInterval(time.Second)) - if err != nil { - logger.ErrorContext(ctx, "otel runtime instrumentation failed:", err) // nolint:all // this is a pattern in the OpenTelemetry Go SDK + if err = runtime.Start(runtime.WithMinimumReadMemStatsInterval(time.Second)); err != nil { + return nil, fmt.Errorf("start runtime instrumentation: %w", err) } - return + return shutdown, nil } // rollDiceApp { @@ -210,7 +215,7 @@ var ( meter = otel.Meter(schemaName) ) -func rolldice(ctx context.Context) { +func rolldice(ctx context.Context) error { ctx, span := tracer.Start(ctx, "roll") defer span.End() @@ -225,9 +230,11 @@ func rolldice(ctx context.Context) { // This is the equivalent of prometheus.NewCounterVec counter, err := meter.Int64Counter("rolldice-counter", metricsapi.WithDescription("a 20-sided dice")) if err != nil { - golog.Fatal(err) + return fmt.Errorf("roll dice: %w", err) } counter.Add(ctx, int64(roll), opt) + + return nil } // } diff --git a/modules/grafana-lgtm/go.mod b/modules/grafana-lgtm/go.mod index 73a94c714b1..26c4b8c34df 100644 --- a/modules/grafana-lgtm/go.mod +++ b/modules/grafana-lgtm/go.mod @@ -4,6 +4,7 @@ go 1.22 require ( github.com/docker/go-connections v0.5.0 + github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.33.0 go.opentelemetry.io/contrib/bridges/otelslog v0.3.0 go.opentelemetry.io/contrib/instrumentation/runtime v0.53.0 @@ -18,6 +19,7 @@ require ( go.opentelemetry.io/otel/sdk v1.28.0 go.opentelemetry.io/otel/sdk/log v0.4.0 go.opentelemetry.io/otel/sdk/metric v1.28.0 + golang.org/x/sync v0.7.0 ) require ( @@ -31,6 +33,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect @@ -54,6 +57,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect @@ -76,6 +80,7 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect google.golang.org/grpc v1.64.1 // indirect google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/grafana-lgtm/go.sum b/modules/grafana-lgtm/go.sum index 1eced8de79b..d80ed84f720 100644 --- a/modules/grafana-lgtm/go.sum +++ b/modules/grafana-lgtm/go.sum @@ -56,6 +56,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -92,6 +96,8 @@ github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -167,6 +173,8 @@ golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -204,6 +212,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/grafana-lgtm/grafana.go b/modules/grafana-lgtm/grafana.go index 1e2f33adba8..3cee949938a 100644 --- a/modules/grafana-lgtm/grafana.go +++ b/modules/grafana-lgtm/grafana.go @@ -45,7 +45,7 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom container, err := testcontainers.GenericContainer(ctx, genericContainerReq) if err != nil { - return nil, err + return nil, fmt.Errorf("generic container: %w", err) } c := &GrafanaLGTMContainer{Container: container} @@ -53,7 +53,7 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom url, err := c.OtlpHttpEndpoint(ctx) if err != nil { // return the container instance to allow the caller to clean up - return c, err + return c, fmt.Errorf("otlp http endpoint: %w", err) } testcontainers.Logger.Printf("Access to the Grafana dashboard: %s", url) diff --git a/modules/grafana-lgtm/grafana_test.go b/modules/grafana-lgtm/grafana_test.go index b0a4960616e..c6c6d9f0d86 100644 --- a/modules/grafana-lgtm/grafana_test.go +++ b/modules/grafana-lgtm/grafana_test.go @@ -8,6 +8,9 @@ import ( "net/url" "testing" + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/grafanalgtm" ) @@ -15,24 +18,14 @@ func TestGrafanaLGTM(t *testing.T) { ctx := context.Background() grafanaLgtmContainer, err := grafanalgtm.Run(ctx, "grafana/otel-lgtm:0.6.0") - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := grafanaLgtmContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, grafanaLgtmContainer) + require.NoError(t, err) // perform assertions t.Run("container is running with right version", func(t *testing.T) { healthURL, err := url.Parse(fmt.Sprintf("http://%s/api/health", grafanaLgtmContainer.MustHttpEndpoint(ctx))) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) httpReq := http.Request{ Method: http.MethodGet, @@ -42,23 +35,15 @@ func TestGrafanaLGTM(t *testing.T) { httpClient := http.Client{} httpResp, err := httpClient.Do(&httpReq) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + defer httpResp.Body.Close() - if httpResp.StatusCode != http.StatusOK { - t.Fatalf("expected status code %d, got %d", http.StatusOK, httpResp.StatusCode) - } + require.Equal(t, http.StatusOK, httpResp.StatusCode) body := make(map[string]interface{}) err = json.NewDecoder(httpResp.Body).Decode(&body) - if err != nil { - t.Fatal(err) - } - - if body["version"] != "11.0.0" { - t.Fatalf("expected version %q, got %q", "11.0.0", body["version"]) - } + require.NoError(t, err) + require.Equal(t, "11.0.0", body["version"]) }) } diff --git a/modules/inbucket/examples_test.go b/modules/inbucket/examples_test.go index 7680a9563a9..c10f8b5b58e 100644 --- a/modules/inbucket/examples_test.go +++ b/modules/inbucket/examples_test.go @@ -5,6 +5,7 @@ import ( "fmt" "log" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/inbucket" ) @@ -13,21 +14,21 @@ func ExampleRun() { ctx := context.Background() inbucketContainer, err := inbucket.Run(ctx, "inbucket/inbucket:sha-2d409bb") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := inbucketContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(inbucketContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := inbucketContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/inbucket/inbucket.go b/modules/inbucket/inbucket.go index beae784557a..565bc4253e3 100644 --- a/modules/inbucket/inbucket.go +++ b/modules/inbucket/inbucket.go @@ -77,9 +77,14 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *InbucketContainer + if container != nil { + c = &InbucketContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &InbucketContainer{Container: container}, nil + return c, nil } diff --git a/modules/inbucket/inbucket_test.go b/modules/inbucket/inbucket_test.go index eb6dc3c4932..1cb53cedd78 100644 --- a/modules/inbucket/inbucket_test.go +++ b/modules/inbucket/inbucket_test.go @@ -7,27 +7,24 @@ import ( "github.com/inbucket/inbucket/pkg/rest/client" "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" ) func TestInbucket(t *testing.T) { ctx := context.Background() - container, err := Run(ctx, "inbucket/inbucket:sha-2d409bb") + ctr, err := Run(ctx, "inbucket/inbucket:sha-2d409bb") + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - // Clean up the container after the test is complete - t.Cleanup(func() { - err := container.Terminate(ctx) - require.NoError(t, err) - }) - // smtpConnection { - smtpUrl, err := container.SmtpConnection(ctx) + smtpUrl, err := ctr.SmtpConnection(ctx) // } require.NoError(t, err) // webInterface { - webInterfaceUrl, err := container.WebInterface(ctx) + webInterfaceUrl, err := ctr.WebInterface(ctx) // } require.NoError(t, err) restClient, err := client.New(webInterfaceUrl) diff --git a/modules/influxdb/examples_test.go b/modules/influxdb/examples_test.go index 2d371179932..30c892537f2 100644 --- a/modules/influxdb/examples_test.go +++ b/modules/influxdb/examples_test.go @@ -5,6 +5,7 @@ import ( "fmt" "log" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/influxdb" ) @@ -18,21 +19,21 @@ func ExampleRun() { influxdb.WithUsername("root"), influxdb.WithPassword("password"), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := influxdbContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(influxdbContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := influxdbContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/influxdb/influxdb.go b/modules/influxdb/influxdb.go index d359c8cbbca..609b11467b5 100644 --- a/modules/influxdb/influxdb.go +++ b/modules/influxdb/influxdb.go @@ -80,11 +80,16 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *InfluxDbContainer + if container != nil { + c = &InfluxDbContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &InfluxDbContainer{container}, nil + return c, nil } func (c *InfluxDbContainer) MustConnectionUrl(ctx context.Context) string { diff --git a/modules/influxdb/influxdb_test.go b/modules/influxdb/influxdb_test.go index 6ec5ec73998..e04a800dc6f 100644 --- a/modules/influxdb/influxdb_test.go +++ b/modules/influxdb/influxdb_test.go @@ -15,18 +15,11 @@ import ( "github.com/testcontainers/testcontainers-go/modules/influxdb" ) -func containerCleanup(t *testing.T, container testcontainers.Container) { - err := container.Terminate(context.Background()) - require.NoError(t, err, "failed to terminate container") -} - func TestV1Container(t *testing.T) { ctx := context.Background() influxDbContainer, err := influxdb.Run(ctx, "influxdb:1.8.10") + testcontainers.CleanupContainer(t, influxDbContainer) require.NoError(t, err) - t.Cleanup(func() { - containerCleanup(t, influxDbContainer) - }) state, err := influxDbContainer.State(ctx) require.NoError(t, err) @@ -44,10 +37,8 @@ func TestV2Container(t *testing.T) { influxdb.WithUsername("root"), influxdb.WithPassword("password"), ) + testcontainers.CleanupContainer(t, influxDbContainer) require.NoError(t, err) - t.Cleanup(func() { - containerCleanup(t, influxDbContainer) - }) state, err := influxDbContainer.State(ctx) require.NoError(t, err) @@ -63,10 +54,8 @@ func TestWithInitDb(t *testing.T) { "influxdb:1.8.10", influxdb.WithInitDb("testdata"), ) + testcontainers.CleanupContainer(t, influxDbContainer) require.NoError(t, err) - t.Cleanup(func() { - containerCleanup(t, influxDbContainer) - }) if state, err := influxDbContainer.State(ctx); err != nil || !state.Running { require.NoError(t, err) @@ -99,10 +88,8 @@ func TestWithConfigFile(t *testing.T) { "influxdb:"+influxVersion, influxdb.WithConfigFile(filepath.Join("testdata", "influxdb.conf")), ) + testcontainers.CleanupContainer(t, influxDbContainer) require.NoError(t, err) - t.Cleanup(func() { - containerCleanup(t, influxDbContainer) - }) if state, err := influxDbContainer.State(context.Background()); err != nil || !state.Running { require.NoError(t, err) diff --git a/modules/k3s/go.mod b/modules/k3s/go.mod index da3a23fd923..724d10e24ab 100644 --- a/modules/k3s/go.mod +++ b/modules/k3s/go.mod @@ -5,7 +5,8 @@ go 1.22 require ( github.com/docker/docker v27.1.1+incompatible github.com/docker/go-connections v0.5.0 - github.com/testcontainers/testcontainers-go v0.33.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.29.2 k8s.io/apimachinery v0.29.2 @@ -56,6 +57,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect diff --git a/modules/k3s/k3s.go b/modules/k3s/k3s.go index f6cfb055c41..509719bd646 100644 --- a/modules/k3s/k3s.go +++ b/modules/k3s/k3s.go @@ -98,11 +98,16 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *K3sContainer + if container != nil { + c = &K3sContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &K3sContainer{Container: container}, nil + return c, nil } func getContainerHost(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (string, error) { diff --git a/modules/k3s/k3s_example_test.go b/modules/k3s/k3s_example_test.go index eef8f87280a..ef2ca365380 100644 --- a/modules/k3s/k3s_example_test.go +++ b/modules/k3s/k3s_example_test.go @@ -9,6 +9,7 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/k3s" ) @@ -17,43 +18,47 @@ func ExampleRun() { ctx := context.Background() k3sContainer, err := k3s.Run(ctx, "docker.io/rancher/k3s:v1.27.1-k3s1") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := k3sContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(k3sContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := k3sContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) kubeConfigYaml, err := k3sContainer.GetKubeConfig(ctx) if err != nil { - log.Fatalf("failed to get kubeconfig: %s", err) + log.Printf("failed to get kubeconfig: %s", err) + return } restcfg, err := clientcmd.RESTConfigFromKubeConfig(kubeConfigYaml) if err != nil { - log.Fatalf("failed to create rest config: %s", err) + log.Printf("failed to create rest config: %s", err) + return } k8s, err := kubernetes.NewForConfig(restcfg) if err != nil { - log.Fatalf("failed to create k8s client: %s", err) + log.Printf("failed to create k8s client: %s", err) + return } nodes, err := k8s.CoreV1().Nodes().List(ctx, v1.ListOptions{}) if err != nil { - log.Fatalf("failed to list nodes: %s", err) + log.Printf("failed to list nodes: %s", err) + return } fmt.Println(len(nodes.Items)) diff --git a/modules/k3s/k3s_test.go b/modules/k3s/k3s_test.go index 7a5fe0d94b5..f3970a25da6 100644 --- a/modules/k3s/k3s_test.go +++ b/modules/k3s/k3s_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kwait "k8s.io/apimachinery/pkg/util/wait" @@ -23,55 +24,33 @@ func Test_LoadImages(t *testing.T) { defer cancel() k3sContainer, err := k3s.Run(ctx, "docker.io/rancher/k3s:v1.27.1-k3s1") - if err != nil { - t.Fatal(err) - } - - // Clean up the container - defer func() { - if err := k3sContainer.Terminate(ctx); err != nil { - t.Fatal(err) - } - }() + testcontainers.CleanupContainer(t, k3sContainer) + require.NoError(t, err) kubeConfigYaml, err := k3sContainer.GetKubeConfig(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) restcfg, err := clientcmd.RESTConfigFromKubeConfig(kubeConfigYaml) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) k8s, err := kubernetes.NewForConfig(restcfg) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) provider, err := testcontainers.ProviderDocker.GetProvider() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // ensure nginx image is available locally err = provider.PullImage(ctx, "nginx") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) t.Run("Test load image not available", func(t *testing.T) { err := k3sContainer.LoadImages(ctx, "fake.registry/fake:non-existing") - if err == nil { - t.Fatal("should had failed") - } + require.Error(t, err) }) t.Run("Test load image in cluster", func(t *testing.T) { err := k3sContainer.LoadImages(ctx, "nginx") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) pod := &corev1.Pod{ TypeMeta: metav1.TypeMeta{ @@ -93,9 +72,7 @@ func Test_LoadImages(t *testing.T) { } _, err = k8s.CoreV1().Pods("default").Create(ctx, pod, metav1.CreateOptions{}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) err = kwait.PollUntilContextCancel(ctx, time.Second, true, func(ctx context.Context) (bool, error) { state, err := getTestPodState(ctx, k8s) @@ -107,17 +84,11 @@ func Test_LoadImages(t *testing.T) { } return state.Running != nil, nil }) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) state, err := getTestPodState(ctx, k8s) - if err != nil { - t.Fatal(err) - } - if state.Running == nil { - t.Fatalf("Unexpected status %v", state) - } + require.NoError(t, err) + require.NotNil(t, state.Running) }) } @@ -135,31 +106,17 @@ func Test_APIServerReady(t *testing.T) { ctx := context.Background() k3sContainer, err := k3s.Run(ctx, "docker.io/rancher/k3s:v1.27.1-k3s1") - if err != nil { - t.Fatal(err) - } - - // Clean up the container - defer func() { - if err := k3sContainer.Terminate(ctx); err != nil { - t.Fatal(err) - } - }() + testcontainers.CleanupContainer(t, k3sContainer) + require.NoError(t, err) kubeConfigYaml, err := k3sContainer.GetKubeConfig(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) restcfg, err := clientcmd.RESTConfigFromKubeConfig(kubeConfigYaml) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) k8s, err := kubernetes.NewForConfig(restcfg) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) pod := &corev1.Pod{ TypeMeta: metav1.TypeMeta{ @@ -180,9 +137,7 @@ func Test_APIServerReady(t *testing.T) { } _, err = k8s.CoreV1().Pods("default").Create(context.Background(), pod, metav1.CreateOptions{}) - if err != nil { - t.Fatalf("failed to create pod %v", err) - } + require.NoError(t, err) } func Test_WithManifestOption(t *testing.T) { @@ -193,14 +148,6 @@ func Test_WithManifestOption(t *testing.T) { k3s.WithManifest("nginx-manifest.yaml"), testcontainers.WithWaitStrategy(wait.ForExec([]string{"kubectl", "wait", "pod", "nginx", "--for=condition=Ready"})), ) - if err != nil { - t.Fatal(err) - } - - // Clean up the container - defer func() { - if err := k3sContainer.Terminate(ctx); err != nil { - t.Fatal(err) - } - }() + testcontainers.CleanupContainer(t, k3sContainer) + require.NoError(t, err) } diff --git a/modules/k6/examples_test.go b/modules/k6/examples_test.go index 468d113450a..c842814d4cf 100644 --- a/modules/k6/examples_test.go +++ b/modules/k6/examples_test.go @@ -29,27 +29,29 @@ func ExampleRun() { Started: true, } httpbin, err := testcontainers.GenericContainer(ctx, gcr) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := httpbin.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(httpbin); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } // getHTTPBinIP { httpbinIP, err := httpbin.ContainerIP(ctx) if err != nil { - log.Fatalf("failed to get container IP: %s", err) // nolint:gocritic + log.Printf("failed to get container IP: %s", err) + return } // } absPath, err := filepath.Abs(filepath.Join("scripts", "httpbin.js")) if err != nil { - log.Fatalf("failed to get absolute path to test script: %s", err) + log.Printf("failed to get absolute path to test script: %s", err) + return } // runK6Container { @@ -61,21 +63,32 @@ func ExampleRun() { k6.WithTestScript(absPath), k6.SetEnvVar("HTTPBIN", httpbinIP), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := k6.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + cacheMount, err := k6.CacheMount(ctx) + if err != nil { + log.Printf("failed to determine cache mount: %s", err) + } + + var options []testcontainers.TerminateOption + if cacheMount != "" { + options = append(options, testcontainers.RemoveVolumes(cacheMount)) + } + + if err = testcontainers.TerminateContainer(k6, options...); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } //} // assert the result of the test state, err := k6.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.ExitCode) diff --git a/modules/k6/go.mod b/modules/k6/go.mod index 58f96d04aab..4dcd9395d96 100644 --- a/modules/k6/go.mod +++ b/modules/k6/go.mod @@ -4,7 +4,8 @@ go 1.22 require ( github.com/docker/docker v27.1.1+incompatible - github.com/testcontainers/testcontainers-go v0.33.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 ) require ( @@ -16,6 +17,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect @@ -26,6 +28,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.4 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -37,6 +40,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -53,6 +57,7 @@ require ( golang.org/x/sys v0.21.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/k6/go.sum b/modules/k6/go.sum index ed514ea5ef8..28367d00203 100644 --- a/modules/k6/go.sum +++ b/modules/k6/go.sum @@ -16,6 +16,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -52,6 +53,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -78,6 +83,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -174,6 +181,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/k6/k6.go b/modules/k6/k6.go index 591cd48f0c7..ea99db3a88b 100644 --- a/modules/k6/k6.go +++ b/modules/k6/k6.go @@ -17,6 +17,9 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) +// cacheTarget is the path to the cache volume in the container. +const cacheTarget = "/cache" + // K6Container represents the K6 container type used in the module type K6Container struct { testcontainers.Container @@ -152,7 +155,7 @@ func WithCache() testcontainers.CustomizeRequestOption { Name: cacheVol, VolumeOptions: volOptions, }, - Target: "/cache", + Target: cacheTarget, } req.Mounts = append(req.Mounts, mount) @@ -186,9 +189,31 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *K6Container + if container != nil { + c = &K6Container{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) + } + + return c, nil +} + +// CacheMount returns the name of volume used as a cache or an empty string +// if no cache was found. +func (k *K6Container) CacheMount(ctx context.Context) (string, error) { + inspect, err := k.Inspect(ctx) + if err != nil { + return "", fmt.Errorf("inspect: %w", err) + } + + for _, m := range inspect.Mounts { + if m.Type == mount.TypeVolume && m.Destination == cacheTarget { + return m.Name, nil + } } - return &K6Container{Container: container}, nil + return "", nil } diff --git a/modules/k6/k6_test.go b/modules/k6/k6_test.go index ead72dcac42..f2cc39ef72d 100644 --- a/modules/k6/k6_test.go +++ b/modules/k6/k6_test.go @@ -7,6 +7,8 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/k6" ) @@ -39,8 +41,22 @@ func TestK6(t *testing.T) { }, } + var cacheMount string + t.Cleanup(func() { + if cacheMount == "" { + return + } + + // Ensure the cache volume is removed as mounts that specify a volume + // source as defined by the name are not removed automatically. + provider, err := testcontainers.NewDockerProvider() + require.NoError(t, err) + defer provider.Close() + + require.NoError(t, provider.Client().VolumeRemove(context.Background(), cacheMount, true)) + }) + for _, tc := range testCases { - tc := tc t.Run(tc.title, func(t *testing.T) { ctx := context.Background() @@ -62,25 +78,19 @@ func TestK6(t *testing.T) { options = k6.WithRemoteTestScript(desc) } - container, err := k6.Run(ctx, "szkiba/k6x:v0.3.1", k6.WithCache(), options) - if err != nil { - t.Fatal(err) + ctr, err := k6.Run(ctx, "szkiba/k6x:v0.3.1", k6.WithCache(), options) + if ctr != nil && cacheMount == "" { + // First container, determine the cache mount. + cacheMount, err = ctr.CacheMount(ctx) + require.NoError(t, err) } - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // assert the result of the test - state, err := container.State(ctx) - if err != nil { - t.Fatal(err) - } - if state.ExitCode != tc.expect { - t.Fatalf("expected %d got %d", tc.expect, state.ExitCode) - } + state, err := ctr.State(ctx) + require.NoError(t, err) + require.Equal(t, tc.expect, state.ExitCode) }) } } diff --git a/modules/kafka/examples_test.go b/modules/kafka/examples_test.go index 2b547970fc3..c275924ecc6 100644 --- a/modules/kafka/examples_test.go +++ b/modules/kafka/examples_test.go @@ -5,6 +5,7 @@ import ( "fmt" "log" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/kafka" ) @@ -16,21 +17,21 @@ func ExampleRun() { "confluentinc/confluent-local:7.5.0", kafka.WithClusterID("test-cluster"), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container after defer func() { - if err := kafkaContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(kafkaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := kafkaContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(kafkaContainer.ClusterID) diff --git a/modules/kafka/go.mod b/modules/kafka/go.mod index 1148bd00f8e..7595e2d7a0b 100644 --- a/modules/kafka/go.mod +++ b/modules/kafka/go.mod @@ -5,7 +5,8 @@ go 1.22 require ( github.com/IBM/sarama v1.42.1 github.com/docker/go-connections v0.5.0 - github.com/testcontainers/testcontainers-go v0.33.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 golang.org/x/mod v0.16.0 ) @@ -41,6 +42,7 @@ require ( github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/klauspost/compress v1.17.4 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -53,6 +55,7 @@ require ( github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pierrec/lz4/v4 v4.1.18 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect @@ -71,6 +74,7 @@ require ( golang.org/x/sys v0.21.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/kafka/go.sum b/modules/kafka/go.sum index 9310807a500..9dddd03e0b2 100644 --- a/modules/kafka/go.sum +++ b/modules/kafka/go.sum @@ -18,6 +18,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -86,6 +87,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -116,6 +121,8 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -239,6 +246,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/modules/kafka/kafka.go b/modules/kafka/kafka.go index c0c02890d4f..d4f070b8a15 100644 --- a/modules/kafka/kafka.go +++ b/modules/kafka/kafka.go @@ -104,16 +104,19 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom return nil, err } - clusterID := genericContainerReq.Env["CLUSTER_ID"] - configureControllerQuorumVoters(&genericContainerReq) container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *KafkaContainer + if container != nil { + c = &KafkaContainer{Container: container, ClusterID: genericContainerReq.Env["CLUSTER_ID"]} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &KafkaContainer{Container: container, ClusterID: clusterID}, nil + return c, nil } // copyStarterScript copies the starter script into the container. diff --git a/modules/kafka/kafka_test.go b/modules/kafka/kafka_test.go index 1e2e009b58e..42fca78650b 100644 --- a/modules/kafka/kafka_test.go +++ b/modules/kafka/kafka_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/IBM/sarama" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/kafka" @@ -18,16 +19,8 @@ func TestKafka(t *testing.T) { ctx := context.Background() kafkaContainer, err := kafka.Run(ctx, "confluentinc/confluent-local:7.5.0", kafka.WithClusterID("kraftCluster")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := kafkaContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, kafkaContainer) + require.NoError(t, err) assertAdvertisedListeners(t, kafkaContainer) @@ -38,17 +31,14 @@ func TestKafka(t *testing.T) { // getBrokers { brokers, err := kafkaContainer.Brokers(ctx) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) config := sarama.NewConfig() client, err := sarama.NewConsumerGroup(brokers, "groupName", config) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) consumer, ready, done, cancel := NewTestKafkaConsumer(t) + defer cancel() go func() { if err := client.Consume(context.Background(), []string{topic}, consumer); err != nil { cancel() @@ -64,19 +54,14 @@ func TestKafka(t *testing.T) { config.Producer.Return.Successes = true producer, err := sarama.NewSyncProducer(brokers, config) - if err != nil { - cancel() - t.Fatal(err) - } + require.NoError(t, err) - if _, _, err := producer.SendMessage(&sarama.ProducerMessage{ + _, _, err = producer.SendMessage(&sarama.ProducerMessage{ Topic: topic, Key: sarama.StringEncoder("key"), Value: sarama.StringEncoder("value"), - }); err != nil { - cancel() - t.Fatal(err) - } + }) + require.NoError(t, err) <-done @@ -91,35 +76,26 @@ func TestKafka(t *testing.T) { func TestKafka_invalidVersion(t *testing.T) { ctx := context.Background() - _, err := kafka.Run(ctx, "confluentinc/confluent-local:6.3.3", kafka.WithClusterID("kraftCluster")) - if err == nil { - t.Fatal(err) - } + ctr, err := kafka.Run(ctx, "confluentinc/confluent-local:6.3.3", kafka.WithClusterID("kraftCluster")) + testcontainers.CleanupContainer(t, ctr) + require.Error(t, err) } // assertAdvertisedListeners checks that the advertised listeners are set correctly: // - The BROKER:// protocol is using the hostname of the Kafka container func assertAdvertisedListeners(t *testing.T, container testcontainers.Container) { inspect, err := container.Inspect(context.Background()) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) hostname := inspect.Config.Hostname code, r, err := container.Exec(context.Background(), []string{"cat", "/usr/sbin/testcontainers_start.sh"}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if code != 0 { - t.Fatalf("expected exit code to be 0, got %d", code) - } + require.Zero(t, code) bs, err := io.ReadAll(r) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) if !strings.Contains(string(bs), "BROKER://"+hostname+":9092") { t.Fatalf("expected advertised listeners to contain %s, got %s", "BROKER://"+hostname+":9092", string(bs)) diff --git a/modules/localstack/examples_test.go b/modules/localstack/examples_test.go index 65a55c317bd..d503ecee6f2 100644 --- a/modules/localstack/examples_test.go +++ b/modules/localstack/examples_test.go @@ -24,21 +24,21 @@ func ExampleRun() { ctx := context.Background() localstackContainer, err := localstack.Run(ctx, "localstack/localstack:1.4.0") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := localstackContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(localstackContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := localstackContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -53,9 +53,16 @@ func ExampleRun_withNetwork() { newNetwork, err := network.New(ctx) if err != nil { - log.Fatalf("failed to create network: %s", err) + log.Printf("failed to create network: %s", err) + return } + defer func() { + if err := newNetwork.Remove(context.Background()); err != nil { + log.Printf("failed to remove network: %s", err) + } + }() + nwName := newNetwork.Name localstackContainer, err := localstack.Run( @@ -64,21 +71,21 @@ func ExampleRun_withNetwork() { testcontainers.WithEnv(map[string]string{"SERVICES": "s3,sqs"}), network.WithNetwork([]string{nwName}, newNetwork), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - // } - - // Clean up the container defer func() { - if err := localstackContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(localstackContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + // } networks, err := localstackContainer.Networks(ctx) if err != nil { - log.Fatalf("failed to get container networks: %s", err) // nolint:gocritic + log.Printf("failed to get container networks: %s", err) + return } fmt.Println(len(networks)) @@ -90,14 +97,20 @@ func ExampleRun_withNetwork() { func ExampleRun_legacyMode() { ctx := context.Background() - _, err := localstack.Run( + ctr, err := localstack.Run( ctx, "localstack/localstack:0.10.0", testcontainers.WithEnv(map[string]string{"SERVICES": "s3,sqs"}), testcontainers.WithWaitStrategy(wait.ForLog("Ready.").WithStartupTimeout(5*time.Minute).WithOccurrence(1)), ) + defer func() { + if err := testcontainers.TerminateContainer(ctr); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() if err == nil { - log.Fatalf("expected an error, got nil") + log.Printf("expected an error, got nil") + return } fmt.Println(err) @@ -123,7 +136,7 @@ func ExampleRun_usingLambdas() { lambdaName := "localstack-lambda-url-example" // withCustomContainerRequest { - container, err := localstack.Run(ctx, + ctr, err := localstack.Run(ctx, "localstack/localstack:2.3.0", testcontainers.WithEnv(map[string]string{ "SERVICES": "lambda", @@ -139,17 +152,17 @@ func ExampleRun_usingLambdas() { }, }, }), - // } ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } + // } defer func() { - err := container.Terminate(ctx) - if err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(ctr); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // the three commands below are doing the following: // 1. create a lambda function @@ -169,9 +182,10 @@ func ExampleRun_usingLambdas() { {"awslocal", "lambda", "wait", "function-active-v2", "--function-name", lambdaName}, } for _, cmd := range lambdaCommands { - _, _, err := container.Exec(ctx, cmd) + _, _, err := ctr.Exec(ctx, cmd) if err != nil { - log.Fatalf("failed to execute command %v: %s", cmd, err) // nolint:gocritic + log.Printf("failed to execute command %v: %s", cmd, err) + return } } @@ -179,15 +193,17 @@ func ExampleRun_usingLambdas() { cmd := []string{ "awslocal", "lambda", "list-function-url-configs", "--function-name", lambdaName, } - _, reader, err := container.Exec(ctx, cmd, exec.Multiplexed()) + _, reader, err := ctr.Exec(ctx, cmd, exec.Multiplexed()) if err != nil { - log.Fatalf("failed to execute command %v: %s", cmd, err) + log.Printf("failed to execute command %v: %s", cmd, err) + return } buf := new(bytes.Buffer) _, err = buf.ReadFrom(reader) if err != nil { - log.Fatalf("failed to read from reader: %s", err) + log.Printf("failed to read from reader: %s", err) + return } content := buf.Bytes() @@ -205,7 +221,8 @@ func ExampleRun_usingLambdas() { v := &FunctionURLConfig{} err = json.Unmarshal(content, v) if err != nil { - log.Fatalf("failed to unmarshal content: %s", err) + log.Printf("failed to unmarshal content: %s", err) + return } httpClient := http.Client{ @@ -215,21 +232,24 @@ func ExampleRun_usingLambdas() { functionURL := v.FunctionURLConfigs[0].FunctionURL // replace the port with the one exposed by the container - mappedPort, err := container.MappedPort(ctx, "4566/tcp") + mappedPort, err := ctr.MappedPort(ctx, "4566/tcp") if err != nil { - log.Fatalf("failed to get mapped port: %s", err) + log.Printf("failed to get mapped port: %s", err) + return } functionURL = strings.ReplaceAll(functionURL, "4566", mappedPort.Port()) resp, err := httpClient.Post(functionURL, "application/json", bytes.NewBufferString(`{"num1": "10", "num2": "10"}`)) if err != nil { - log.Fatalf("failed to send request to lambda function: %s", err) + log.Printf("failed to send request to lambda function: %s", err) + return } jsonResponse, err := io.ReadAll(resp.Body) if err != nil { - log.Fatalf("failed to read response body: %s", err) + log.Printf("failed to read response body: %s", err) + return } fmt.Println(string(jsonResponse)) diff --git a/modules/localstack/localstack.go b/modules/localstack/localstack.go index 961527cd3e4..5cfe054a859 100644 --- a/modules/localstack/localstack.go +++ b/modules/localstack/localstack.go @@ -116,13 +116,15 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom localStackReq.GenericContainerRequest.Logger.Printf("Setting %s to %s (%s)\n", envVar, req.Env[envVar], hostnameExternalReason) container, err := testcontainers.GenericContainer(ctx, localStackReq.GenericContainerRequest) - if err != nil { - return nil, err + var c *LocalStackContainer + if container != nil { + c = &LocalStackContainer{Container: container} } - c := &LocalStackContainer{ - Container: container, + if err != nil { + return c, fmt.Errorf("generic container: %w", err) } + return c, nil } diff --git a/modules/localstack/localstack_test.go b/modules/localstack/localstack_test.go index 70797fe3cd8..5991c52fbfa 100644 --- a/modules/localstack/localstack_test.go +++ b/modules/localstack/localstack_test.go @@ -8,7 +8,6 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" @@ -43,7 +42,7 @@ func TestConfigureDockerHost(t *testing.T) { reason, err := configureDockerHost(req, tt.envVar) require.NoError(t, err) - assert.Equal(t, "explicitly as environment variable", reason) + require.Equal(t, "explicitly as environment variable", reason) }) t.Run("HOSTNAME_EXTERNAL matches the last network alias on a container with non-default network", func(t *testing.T) { @@ -58,8 +57,8 @@ func TestConfigureDockerHost(t *testing.T) { reason, err := configureDockerHost(req, tt.envVar) require.NoError(t, err) - assert.Equal(t, "to match last network alias on container with non-default network", reason) - assert.Equal(t, "foo3", req.Env[tt.envVar]) + require.Equal(t, "to match last network alias on container with non-default network", reason) + require.Equal(t, "foo3", req.Env[tt.envVar]) }) t.Run("HOSTNAME_EXTERNAL matches the daemon host because there are no aliases", func(t *testing.T) { @@ -78,8 +77,8 @@ func TestConfigureDockerHost(t *testing.T) { reason, err := configureDockerHost(req, tt.envVar) require.NoError(t, err) - assert.Equal(t, "to match host-routable address for container", reason) - assert.Equal(t, expectedDaemonHost, req.Env[tt.envVar]) + require.Equal(t, "to match host-routable address for container", reason) + require.Equal(t, expectedDaemonHost, req.Env[tt.envVar]) }) } } @@ -102,7 +101,7 @@ func TestIsLegacyMode(t *testing.T) { for _, tt := range tests { t.Run(tt.version, func(t *testing.T) { got := isLegacyMode(fmt.Sprintf("localstack/localstack:%s", tt.version)) - assert.Equal(t, tt.want, got, "runInLegacyMode() = %v, want %v", got, tt.want) + require.Equal(t, tt.want, got, "runInLegacyMode() = %v, want %v", got, tt.want) }) } } @@ -118,16 +117,17 @@ func TestRunContainer(t *testing.T) { for _, tt := range tests { ctx := context.Background() - container, err := Run( + ctr, err := Run( ctx, fmt.Sprintf("localstack/localstack:%s", tt.version), ) + testcontainers.CleanupContainer(t, ctr) t.Run("Localstack:"+tt.version+" - multiple services exposed on same port", func(t *testing.T) { require.NoError(t, err) - assert.NotNil(t, container) + require.NotNil(t, ctr) - inspect, err := container.Inspect(ctx) + inspect, err := ctr.Inspect(ctx) require.NoError(t, err) rawPorts := inspect.NetworkSettings.Ports @@ -140,7 +140,7 @@ func TestRunContainer(t *testing.T) { } } - assert.Equal(t, 1, ports) // a single port is exposed + require.Equal(t, 1, ports) // a single port is exposed }) } } @@ -148,9 +148,10 @@ func TestRunContainer(t *testing.T) { func TestStartWithoutOverride(t *testing.T) { ctx := context.Background() - container, err := Run(ctx, "localstack/localstack:2.0.0") + ctr, err := Run(ctx, "localstack/localstack:2.0.0") + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - assert.NotNil(t, container) + require.NotNil(t, ctr) } func TestStartV2WithNetwork(t *testing.T) { @@ -158,6 +159,7 @@ func TestStartV2WithNetwork(t *testing.T) { nw, err := network.New(ctx) require.NoError(t, err) + testcontainers.CleanupNetwork(t, nw) localstack, err := Run( ctx, @@ -165,8 +167,9 @@ func TestStartV2WithNetwork(t *testing.T) { network.WithNetwork([]string{"localstack"}, nw), testcontainers.WithEnv(map[string]string{"SERVICES": "s3,sqs"}), ) + testcontainers.CleanupContainer(t, localstack) require.NoError(t, err) - assert.NotNil(t, localstack) + require.NotNil(t, localstack) networkName := nw.Name @@ -197,6 +200,7 @@ func TestStartV2WithNetwork(t *testing.T) { }, Started: true, }) + testcontainers.CleanupContainer(t, cli) require.NoError(t, err) - assert.NotNil(t, cli) + require.NotNil(t, cli) } diff --git a/modules/localstack/v1/s3_test.go b/modules/localstack/v1/s3_test.go index be643228f68..87eba460804 100644 --- a/modules/localstack/v1/s3_test.go +++ b/modules/localstack/v1/s3_test.go @@ -62,10 +62,11 @@ func awsSession(ctx context.Context, l *localstack.LocalStackContainer) (*sessio func TestS3(t *testing.T) { ctx := context.Background() - container, err := localstack.Run(ctx, "localstack/localstack:1.4.0") + ctr, err := localstack.Run(ctx, "localstack/localstack:1.4.0") + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - session, err := awsSession(ctx, container) + session, err := awsSession(ctx, ctr) require.NoError(t, err) s3Uploader := s3manager.NewUploader(session) diff --git a/modules/localstack/v2/s3_test.go b/modules/localstack/v2/s3_test.go index 2b5308ddd85..477549fb9c7 100644 --- a/modules/localstack/v2/s3_test.go +++ b/modules/localstack/v2/s3_test.go @@ -73,10 +73,11 @@ func s3Client(ctx context.Context, l *localstack.LocalStackContainer) (*s3.Clien func TestS3(t *testing.T) { ctx := context.Background() - container, err := localstack.Run(ctx, "localstack/localstack:1.4.0") + ctr, err := localstack.Run(ctx, "localstack/localstack:1.4.0") + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - s3Client, err := s3Client(ctx, container) + s3Client, err := s3Client(ctx, ctr) require.NoError(t, err) t.Run("S3 operations", func(t *testing.T) { diff --git a/modules/mariadb/examples_test.go b/modules/mariadb/examples_test.go index d33970df143..59e168d3e4c 100644 --- a/modules/mariadb/examples_test.go +++ b/modules/mariadb/examples_test.go @@ -6,6 +6,7 @@ import ( "log" "path/filepath" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/mariadb" ) @@ -21,21 +22,21 @@ func ExampleRun() { mariadb.WithUsername("root"), mariadb.WithPassword(""), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := mariadbContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(mariadbContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := mariadbContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/mariadb/go.mod b/modules/mariadb/go.mod index e8039caf585..a3d2ee8f702 100644 --- a/modules/mariadb/go.mod +++ b/modules/mariadb/go.mod @@ -4,7 +4,8 @@ go 1.22 require ( github.com/go-sql-driver/mysql v1.7.1 - github.com/testcontainers/testcontainers-go v0.33.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 ) require ( @@ -16,6 +17,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect @@ -27,6 +29,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.4 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -38,6 +41,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -54,6 +58,7 @@ require ( golang.org/x/sys v0.21.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/mariadb/go.sum b/modules/mariadb/go.sum index e2bb93b49dc..dcc1a8d165a 100644 --- a/modules/mariadb/go.sum +++ b/modules/mariadb/go.sum @@ -16,6 +16,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -54,6 +55,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -80,6 +85,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -176,6 +183,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/mariadb/mariadb.go b/modules/mariadb/mariadb.go index fae71c78711..1d0b553e417 100644 --- a/modules/mariadb/mariadb.go +++ b/modules/mariadb/mariadb.go @@ -169,13 +169,21 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) - if err != nil { - return nil, err + var c *MariaDBContainer + if container != nil { + c = &MariaDBContainer{ + Container: container, + username: username, + password: password, + database: req.Env["MARIADB_DATABASE"], + } } - database := req.Env["MARIADB_DATABASE"] + if err != nil { + return c, fmt.Errorf("generic container: %w", err) + } - return &MariaDBContainer{container, username, password, database}, nil + return c, nil } // MustConnectionString panics if the address cannot be determined. diff --git a/modules/mariadb/mariadb_test.go b/modules/mariadb/mariadb_test.go index f1863f472c3..8e55609ec4d 100644 --- a/modules/mariadb/mariadb_test.go +++ b/modules/mariadb/mariadb_test.go @@ -8,55 +8,41 @@ import ( // Import mysql into the scope of this package (required) _ "github.com/go-sql-driver/mysql" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/mariadb" ) func TestMariaDB(t *testing.T) { ctx := context.Background() - container, err := mariadb.Run(ctx, "mariadb:11.0.3") - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := mariadb.Run(ctx, "mariadb:11.0.3") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // connectionString { // By default, MariaDB transmits data between the server and clients without encrypting it. - connectionString, err := container.ConnectionString(ctx, "tls=false") + connectionString, err := ctr.ConnectionString(ctx, "tls=false") // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - mustConnectionString := container.MustConnectionString(ctx, "tls=false") - if mustConnectionString != connectionString { - t.Errorf("ConnectionString was not equal to MustConnectionString") - } + mustConnectionString := ctr.MustConnectionString(ctx, "tls=false") + require.Equal(t, connectionString, mustConnectionString) db, err := sql.Open("mysql", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) + _, err = db.Exec("CREATE TABLE IF NOT EXISTS a_table ( \n" + " `col_1` VARCHAR(128) NOT NULL, \n" + " `col_2` VARCHAR(128) NOT NULL, \n" + " PRIMARY KEY (`col_1`, `col_2`) \n" + ")") - if err != nil { - t.Errorf("error creating table: %+v\n", err) - } + require.NoError(t, err) } func TestMariaDBWithNonRootUserAndEmptyPassword(t *testing.T) { @@ -75,163 +61,105 @@ func TestMariaDBWithNonRootUserAndEmptyPassword(t *testing.T) { func TestMariaDBWithRootUserAndEmptyPassword(t *testing.T) { ctx := context.Background() - container, err := mariadb.Run(ctx, + ctr, err := mariadb.Run(ctx, "mariadb:11.0.3", mariadb.WithDatabase("foo"), mariadb.WithUsername("root"), mariadb.WithPassword("")) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - - connectionString, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } + connectionString, err := ctr.ConnectionString(ctx) + require.NoError(t, err) db, err := sql.Open("mysql", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) + _, err = db.Exec("CREATE TABLE IF NOT EXISTS a_table ( \n" + " `col_1` VARCHAR(128) NOT NULL, \n" + " `col_2` VARCHAR(128) NOT NULL, \n" + " PRIMARY KEY (`col_1`, `col_2`) \n" + ")") - if err != nil { - t.Errorf("error creating table: %+v\n", err) - } + require.NoError(t, err) } func TestMariaDBWithMySQLEnvVars(t *testing.T) { ctx := context.Background() - container, err := mariadb.Run(ctx, "mariadb:10.3.29", + ctr, err := mariadb.Run(ctx, "mariadb:10.3.29", mariadb.WithScripts(filepath.Join("testdata", "schema.sql"))) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - - assertDataCanBeFetched(t, ctx, container) + assertDataCanBeFetched(t, ctx, ctr) } func TestMariaDBWithConfigFile(t *testing.T) { ctx := context.Background() - container, err := mariadb.Run(ctx, "mariadb:11.0.3", + ctr, err := mariadb.Run(ctx, "mariadb:11.0.3", mariadb.WithConfigFile(filepath.Join("testdata", "my.cnf"))) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - - connectionString, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } + connectionString, err := ctr.ConnectionString(ctx) + require.NoError(t, err) db, err := sql.Open("mysql", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) // In MariaDB 10.2.2 and later, the default file format is Barracuda and Antelope is deprecated. // Barracuda is a newer InnoDB file format. It supports the COMPACT, REDUNDANT, DYNAMIC and // COMPRESSED row formats. Tables with large BLOB or TEXT columns in particular could benefit // from the dynamic row format. stmt, err := db.Prepare("SELECT @@GLOBAL.innodb_default_row_format") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + defer stmt.Close() row := stmt.QueryRow() innodbFileFormat := "" err = row.Scan(&innodbFileFormat) - if err != nil { - t.Errorf("error fetching innodb_default_row_format value") - } - if innodbFileFormat != "dynamic" { - t.Fatal("The InnoDB file format has been set by the ini file content") - } + require.NoError(t, err) + require.Equal(t, "dynamic", innodbFileFormat) } func TestMariaDBWithScripts(t *testing.T) { ctx := context.Background() - container, err := mariadb.Run(ctx, + ctr, err := mariadb.Run(ctx, "mariadb:11.0.3", mariadb.WithScripts(filepath.Join("testdata", "schema.sql"))) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - assertDataCanBeFetched(t, ctx, container) + assertDataCanBeFetched(t, ctx, ctr) } func assertDataCanBeFetched(t *testing.T, ctx context.Context, container *mariadb.MariaDBContainer) { connectionString, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } - + require.NoError(t, err) db, err := sql.Open("mysql", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) stmt, err := db.Prepare("SELECT name from profile") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer stmt.Close() + row := stmt.QueryRow() var name string err = row.Scan(&name) - if err != nil { - t.Errorf("error fetching data") - } - if name != "profile 1" { - t.Fatal("The expected record was not found in the database.") - } + require.NoError(t, err) + require.Equal(t, "profile 1", name) } diff --git a/modules/milvus/examples_test.go b/modules/milvus/examples_test.go index 79ca1b98121..a8242f15b2c 100644 --- a/modules/milvus/examples_test.go +++ b/modules/milvus/examples_test.go @@ -8,6 +8,7 @@ import ( "github.com/milvus-io/milvus-sdk-go/v2/client" "github.com/milvus-io/milvus-sdk-go/v2/entity" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/milvus" ) @@ -16,21 +17,21 @@ func ExampleRun() { ctx := context.Background() milvusContainer, err := milvus.Run(ctx, "milvusdb/milvus:v2.3.9") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := milvusContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(milvusContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := milvusContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -44,26 +45,27 @@ func ExampleMilvusContainer_collections() { ctx := context.Background() milvusContainer, err := milvus.Run(ctx, "milvusdb/milvus:v2.3.9") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := milvusContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(milvusContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } connectionStr, err := milvusContainer.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) // nolint:gocritic + log.Printf("failed to get connection string: %s", err) + return } // Create a client to interact with the Milvus container milvusClient, err := client.NewGrpcClient(context.Background(), connectionStr) if err != nil { - log.Fatal("failed to connect to Milvus:", err.Error()) + log.Print("failed to connect to Milvus:", err.Error()) + return } defer milvusClient.Close() @@ -101,12 +103,14 @@ func ExampleMilvusContainer_collections() { 2, // shardNum ) if err != nil { - log.Fatalf("failed to create collection: %s", err) // nolint:gocritic + log.Printf("failed to create collection: %s", err) + return } list, err := milvusClient.ListCollections(context.Background()) if err != nil { - log.Fatalf("failed to list collections: %s", err) // nolint:gocritic + log.Printf("failed to list collections: %s", err) + return } // } diff --git a/modules/milvus/milvus.go b/modules/milvus/milvus.go index 9b944f61602..b35cc993358 100644 --- a/modules/milvus/milvus.go +++ b/modules/milvus/milvus.go @@ -85,11 +85,16 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *MilvusContainer + if container != nil { + c = &MilvusContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &MilvusContainer{Container: container}, nil + return c, nil } type embedEtcdConfigTplParams struct { diff --git a/modules/milvus/milvus_test.go b/modules/milvus/milvus_test.go index c49f37c92ff..c1ad0a070e1 100644 --- a/modules/milvus/milvus_test.go +++ b/modules/milvus/milvus_test.go @@ -7,24 +7,20 @@ import ( "github.com/milvus-io/milvus-sdk-go/v2/client" "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/milvus" ) func TestMilvus(t *testing.T) { ctx := context.Background() - container, err := milvus.Run(ctx, "milvusdb/milvus:v2.3.9") + ctr, err := milvus.Run(ctx, "milvusdb/milvus:v2.3.9") + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - // Clean up the container after the test is complete - t.Cleanup(func() { - err = container.Terminate(ctx) - require.NoError(t, err) - }) - t.Run("Connect to Milvus with gRPC", func(tt *testing.T) { // connectionString { - connectionStr, err := container.ConnectionString(ctx) + connectionStr, err := ctr.ConnectionString(ctx) // } require.NoError(t, err) diff --git a/modules/minio/examples_test.go b/modules/minio/examples_test.go index c13e6793882..a1e50b6c84a 100644 --- a/modules/minio/examples_test.go +++ b/modules/minio/examples_test.go @@ -5,6 +5,7 @@ import ( "fmt" "log" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/minio" ) @@ -13,21 +14,21 @@ func ExampleRun() { ctx := context.Background() minioContainer, err := minio.Run(ctx, "minio/minio:RELEASE.2024-01-16T16-07-38Z") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := minioContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(minioContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := minioContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/minio/go.mod b/modules/minio/go.mod index c50c958c213..84d98587fd7 100644 --- a/modules/minio/go.mod +++ b/modules/minio/go.mod @@ -4,7 +4,8 @@ go 1.22 require ( github.com/minio/minio-go/v7 v7.0.68 - github.com/testcontainers/testcontainers-go v0.33.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 ) require ( @@ -16,6 +17,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect @@ -30,6 +32,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.6 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/minio/md5-simd v1.1.2 // indirect @@ -45,6 +48,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/rs/xid v1.5.0 // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect @@ -64,6 +68,7 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/minio/go.sum b/modules/minio/go.sum index e837e4f48e9..5afc27ffd9e 100644 --- a/modules/minio/go.sum +++ b/modules/minio/go.sum @@ -16,6 +16,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -60,6 +61,10 @@ github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6K github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -97,6 +102,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= @@ -197,6 +204,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/minio/minio.go b/modules/minio/minio.go index c7e2898d9f5..6907b1372b6 100644 --- a/modules/minio/minio.go +++ b/modules/minio/minio.go @@ -93,9 +93,14 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *MinioContainer + if container != nil { + c = &MinioContainer{Container: container, Username: username, Password: password} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &MinioContainer{Container: container, Username: username, Password: password}, nil + return c, nil } diff --git a/modules/minio/minio_test.go b/modules/minio/minio_test.go index 60bf8034b34..d8ca857cb3a 100644 --- a/modules/minio/minio_test.go +++ b/modules/minio/minio_test.go @@ -8,51 +8,39 @@ import ( "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" tcminio "github.com/testcontainers/testcontainers-go/modules/minio" ) func TestMinio(t *testing.T) { ctx := context.Background() - container, err := tcminio.Run(ctx, + ctr, err := tcminio.Run(ctx, "minio/minio:RELEASE.2024-01-16T16-07-38Z", tcminio.WithUsername("thisismyuser"), tcminio.WithPassword("thisismypassword")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions // connectionString { - url, err := container.ConnectionString(ctx) + url, err := ctr.ConnectionString(ctx) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) minioClient, err := minio.New(url, &minio.Options{ - Creds: credentials.NewStaticV4(container.Username, container.Password, ""), + Creds: credentials.NewStaticV4(ctr.Username, ctr.Password, ""), Secure: false, }) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) bucketName := "testcontainers" location := "eu-west-2" // create bucket err = minioClient.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: location}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) objectName := "testdata" contentType := "applcation/octet-stream" @@ -60,23 +48,15 @@ func TestMinio(t *testing.T) { contentLength := int64(len(content)) uploadInfo, err := minioClient.PutObject(ctx, bucketName, objectName, strings.NewReader(content), contentLength, minio.PutObjectOptions{ContentType: contentType}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // object is a readSeekCloser object, err := minioClient.GetObject(ctx, uploadInfo.Bucket, uploadInfo.Key, minio.GetObjectOptions{}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + defer object.Close() n, err := io.Copy(io.Discard, object) - if err != nil { - t.Fatal(err) - } - - if n != contentLength { - t.Fatalf("expected %d; got %d", contentLength, n) - } + require.NoError(t, err) + require.Equal(t, contentLength, n) } diff --git a/modules/mockserver/examples_test.go b/modules/mockserver/examples_test.go index 17f4cfffea3..a93c8bcbf00 100644 --- a/modules/mockserver/examples_test.go +++ b/modules/mockserver/examples_test.go @@ -10,6 +10,7 @@ import ( client "github.com/BraspagDevelopers/mock-server-client" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/mockserver" ) @@ -18,21 +19,21 @@ func ExampleRun() { ctx := context.Background() mockserverContainer, err := mockserver.Run(ctx, "mockserver/mockserver:5.15.0") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := mockserverContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(mockserverContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := mockserverContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -46,20 +47,20 @@ func ExampleRun_connect() { ctx := context.Background() mockserverContainer, err := mockserver.Run(ctx, "mockserver/mockserver:5.15.0") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := mockserverContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(mockserverContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } url, err := mockserverContainer.URL(ctx) if err != nil { - log.Fatalf("failed to get container URL: %s", err) // nolint:gocritic + log.Printf("failed to get container URL: %s", err) + return } ms := client.NewClientURL(url) // } @@ -71,18 +72,21 @@ func ExampleRun_connect() { requestMatcher = requestMatcher.WithJSONFields(map[string]interface{}{"name": "Tools"}) err = ms.RegisterExpectation(client.NewExpectation(requestMatcher).WithResponse(client.NewResponseOK().WithJSONBody(map[string]any{"test": "value"}))) if err != nil { - log.Fatalf("failed to register expectation: %s", err) + log.Printf("failed to register expectation: %s", err) + return } httpClient := &http.Client{} resp, err := httpClient.Post(url+"/api/categories", "application/json", strings.NewReader(`{"name": "Tools"}`)) if err != nil { - log.Fatalf("failed to send request: %s", err) + log.Printf("failed to send request: %s", err) + return } buf, err := io.ReadAll(resp.Body) if err != nil { - log.Fatalf("failed to read response: %s", err) + log.Printf("failed to read response: %s", err) + return } resp.Body.Close() diff --git a/modules/mockserver/go.mod b/modules/mockserver/go.mod index e2ba76d72d6..36f5772800e 100644 --- a/modules/mockserver/go.mod +++ b/modules/mockserver/go.mod @@ -16,6 +16,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect @@ -28,6 +29,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.4 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -39,10 +41,12 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/stretchr/testify v1.9.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect @@ -55,6 +59,7 @@ require ( golang.org/x/sys v0.21.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/mockserver/go.sum b/modules/mockserver/go.sum index acf88d134a7..16335ff9dd9 100644 --- a/modules/mockserver/go.sum +++ b/modules/mockserver/go.sum @@ -18,6 +18,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -56,6 +57,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -82,6 +87,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -180,6 +187,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/mongodb/examples_test.go b/modules/mongodb/examples_test.go index 5e8cbe8009c..98a31d61fa7 100644 --- a/modules/mongodb/examples_test.go +++ b/modules/mongodb/examples_test.go @@ -19,21 +19,21 @@ func ExampleRun() { ctx := context.Background() mongodbContainer, err := mongodb.Run(ctx, "mongo:6") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := mongodbContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(mongodbContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := mongodbContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -47,31 +47,33 @@ func ExampleRun_connect() { ctx := context.Background() mongodbContainer, err := mongodb.Run(ctx, "mongo:6") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := mongodbContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(mongodbContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } endpoint, err := mongodbContainer.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) // nolint:gocritic + log.Printf("failed to get connection string: %s", err) + return } mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(endpoint)) if err != nil { - log.Fatalf("failed to connect to MongoDB: %s", err) + log.Printf("failed to connect to MongoDB: %s", err) + return } // } err = mongoClient.Ping(ctx, nil) if err != nil { - log.Fatalf("failed to ping MongoDB: %s", err) + log.Printf("failed to ping MongoDB: %s", err) + return } fmt.Println(mongoClient.Database("test").Name()) @@ -83,36 +85,38 @@ func ExampleRun_connect() { func ExampleRun_withCredentials() { ctx := context.Background() - container, err := mongodb.Run(ctx, + ctr, err := mongodb.Run(ctx, "mongo:6", mongodb.WithUsername("root"), mongodb.WithPassword("password"), testcontainers.WithWaitStrategy(wait.ForLog("Waiting for connections")), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := container.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(ctr); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } - connStr, err := container.ConnectionString(ctx) + connStr, err := ctr.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) // nolint:gocritic + log.Printf("failed to get connection string: %s", err) + return } mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(connStr)) if err != nil { - log.Fatalf("failed to connect to MongoDB: %s", err) + log.Printf("failed to connect to MongoDB: %s", err) + return } err = mongoClient.Ping(ctx, nil) if err != nil { - log.Fatalf("failed to ping MongoDB: %s", err) + log.Printf("failed to ping MongoDB: %s", err) + return } fmt.Println(strings.Split(connStr, "@")[0]) diff --git a/modules/mongodb/go.mod b/modules/mongodb/go.mod index e7e8c724bcc..66311410d79 100644 --- a/modules/mongodb/go.mod +++ b/modules/mongodb/go.mod @@ -3,7 +3,8 @@ module github.com/testcontainers/testcontainers-go/modules/mongodb go 1.22 require ( - github.com/testcontainers/testcontainers-go v0.33.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 go.mongodb.org/mongo-driver v1.13.1 ) @@ -16,6 +17,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect @@ -28,6 +30,7 @@ require ( github.com/golang/snappy v0.0.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.4 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -40,6 +43,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -62,6 +66,7 @@ require ( golang.org/x/text v0.16.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/mongodb/go.sum b/modules/mongodb/go.sum index bc45e6d3335..7f2154a1b2a 100644 --- a/modules/mongodb/go.sum +++ b/modules/mongodb/go.sum @@ -16,6 +16,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -56,6 +57,10 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -84,6 +89,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -212,6 +219,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/mongodb/mongodb.go b/modules/mongodb/mongodb.go index 188c55e85b5..49234735934 100644 --- a/modules/mongodb/mongodb.go +++ b/modules/mongodb/mongodb.go @@ -50,14 +50,16 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) - if err != nil { - return nil, err + var c *MongoDBContainer + if container != nil { + c = &MongoDBContainer{Container: container, username: username, password: password} } - if username != "" && password != "" { - return &MongoDBContainer{Container: container, username: username, password: password}, nil + if err != nil { + return c, fmt.Errorf("generic container: %w", err) } - return &MongoDBContainer{Container: container}, nil + + return c, nil } // WithUsername sets the initial username to be created when the container starts diff --git a/modules/mongodb/mongodb_test.go b/modules/mongodb/mongodb_test.go index ead2b1818bb..663f05cff6b 100644 --- a/modules/mongodb/mongodb_test.go +++ b/modules/mongodb/mongodb_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -57,37 +58,21 @@ func TestMongoDB(t *testing.T) { ctx := context.Background() mongodbContainer, err := mongodb.Run(ctx, tc.img, tc.opts...) - if err != nil { - tt.Fatalf("failed to start container: %s", err) - } - - defer func() { - if err := mongodbContainer.Terminate(ctx); err != nil { - tt.Fatalf("failed to terminate container: %s", err) - } - }() + testcontainers.CleanupContainer(t, mongodbContainer) + require.NoError(tt, err) endpoint, err := mongodbContainer.ConnectionString(ctx) - if err != nil { - tt.Fatalf("failed to get connection string: %s", err) - } + require.NoError(tt, err) // Force direct connection to the container to avoid the replica set // connection string that is returned by the container itself when // using the replica set option. mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(endpoint+"/?connect=direct")) - if err != nil { - tt.Fatalf("failed to connect to MongoDB: %s", err) - } + require.NoError(tt, err) err = mongoClient.Ping(ctx, nil) - if err != nil { - tt.Fatalf("failed to ping MongoDB: %s", err) - } - - if mongoClient.Database("test").Name() != "test" { - tt.Fatalf("failed to connect to the correct database") - } + require.NoError(tt, err) + require.Equal(t, "test", mongoClient.Database("test").Name()) }) } } diff --git a/modules/mssql/examples_test.go b/modules/mssql/examples_test.go index 10363d9b486..f5e7ebd04d0 100644 --- a/modules/mssql/examples_test.go +++ b/modules/mssql/examples_test.go @@ -5,6 +5,7 @@ import ( "fmt" "log" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/mssql" ) @@ -19,21 +20,21 @@ func ExampleRun() { mssql.WithAcceptEULA(), mssql.WithPassword(password), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := mssqlContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(mssqlContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := mssqlContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/mssql/go.mod b/modules/mssql/go.mod index 83411fb7aaa..80bd7ba2198 100644 --- a/modules/mssql/go.mod +++ b/modules/mssql/go.mod @@ -4,7 +4,8 @@ go 1.22 require ( github.com/microsoft/go-mssqldb v1.7.0 - github.com/testcontainers/testcontainers-go v0.33.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 ) require ( @@ -16,6 +17,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect @@ -29,6 +31,7 @@ require ( github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.4 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -40,6 +43,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -57,6 +61,7 @@ require ( golang.org/x/text v0.16.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/mssql/go.sum b/modules/mssql/go.sum index 2f512564ff0..452ef256614 100644 --- a/modules/mssql/go.sum +++ b/modules/mssql/go.sum @@ -28,6 +28,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -70,6 +71,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= @@ -102,6 +107,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -198,6 +205,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/mssql/mssql.go b/modules/mssql/mssql.go index ca30d023850..57f634c02cd 100644 --- a/modules/mssql/mssql.go +++ b/modules/mssql/mssql.go @@ -70,14 +70,16 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) - if err != nil { - return nil, err + var c *MSSQLServerContainer + if container != nil { + c = &MSSQLServerContainer{Container: container, password: req.Env["MSSQL_SA_PASSWORD"], username: defaultUsername} } - username := defaultUsername - password := req.Env["MSSQL_SA_PASSWORD"] + if err != nil { + return c, fmt.Errorf("generic container: %w", err) + } - return &MSSQLServerContainer{Container: container, password: password, username: username}, nil + return c, nil } func (c *MSSQLServerContainer) ConnectionString(ctx context.Context, args ...string) (string, error) { diff --git a/modules/mssql/mssql_test.go b/modules/mssql/mssql_test.go index 4e2050385af..602778f8a0b 100644 --- a/modules/mssql/mssql_test.go +++ b/modules/mssql/mssql_test.go @@ -6,6 +6,7 @@ import ( "testing" _ "github.com/microsoft/go-mssqldb" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/mssql" @@ -15,63 +16,45 @@ import ( func TestMSSQLServer(t *testing.T) { ctx := context.Background() - container, err := mssql.Run(ctx, + ctr, err := mssql.Run(ctx, "mcr.microsoft.com/mssql/server:2022-CU10-ubuntu-22.04", mssql.WithAcceptEULA(), ) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions - connectionString, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } + connectionString, err := ctr.ConnectionString(ctx) + require.NoError(t, err) db, err := sql.Open("sqlserver", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) _, err = db.Exec("CREATE TABLE a_table ( " + " [col_1] NVARCHAR(128) NOT NULL, " + " [col_2] NVARCHAR(128) NOT NULL, " + " PRIMARY KEY ([col_1], [col_2]) " + ")") - if err != nil { - t.Errorf("error creating table: %+v\n", err) - } + require.NoError(t, err) } func TestMSSQLServerWithMissingEulaOption(t *testing.T) { ctx := context.Background() - container, err := mssql.Run(ctx, + ctr, err := mssql.Run(ctx, "mcr.microsoft.com/mssql/server:2022-CU10-ubuntu-22.04", testcontainers.WithWaitStrategy( wait.ForLog("The SQL Server End-User License Agreement (EULA) must be accepted")), ) - if err != nil { - t.Fatalf("Expected a log to confirm missing EULA but got error: %s", err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - state, err := container.State(ctx) - if err != nil { - t.Fatalf("failed to get container state: %s", err) - } + state, err := ctr.State(ctx) + require.NoError(t, err) if !state.Running { t.Log("Success: Confirmed proper handling of missing EULA, so container is not running.") @@ -81,140 +64,89 @@ func TestMSSQLServerWithMissingEulaOption(t *testing.T) { func TestMSSQLServerWithConnectionStringParameters(t *testing.T) { ctx := context.Background() - container, err := mssql.Run(ctx, + ctr, err := mssql.Run(ctx, "mcr.microsoft.com/mssql/server:2022-CU10-ubuntu-22.04", mssql.WithAcceptEULA(), ) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions - connectionString, err := container.ConnectionString(ctx, "encrypt=false", "TrustServerCertificate=true") - if err != nil { - t.Fatal(err) - } + connectionString, err := ctr.ConnectionString(ctx, "encrypt=false", "TrustServerCertificate=true") + require.NoError(t, err) db, err := sql.Open("sqlserver", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) _, err = db.Exec("CREATE TABLE a_table ( " + " [col_1] NVARCHAR(128) NOT NULL, " + " [col_2] NVARCHAR(128) NOT NULL, " + " PRIMARY KEY ([col_1], [col_2]) " + ")") - if err != nil { - t.Errorf("error creating table: %+v\n", err) - } + require.NoError(t, err) } func TestMSSQLServerWithCustomStrongPassword(t *testing.T) { ctx := context.Background() - container, err := mssql.Run(ctx, + ctr, err := mssql.Run(ctx, "mcr.microsoft.com/mssql/server:2022-CU10-ubuntu-22.04", mssql.WithAcceptEULA(), mssql.WithPassword("Strong@Passw0rd"), ) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions - connectionString, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } + connectionString, err := ctr.ConnectionString(ctx) + require.NoError(t, err) db, err := sql.Open("sqlserver", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) } // tests that a weak password is not accepted by the container due to Microsoft's password strength policy func TestMSSQLServerWithInvalidPassword(t *testing.T) { ctx := context.Background() - container, err := mssql.Run(ctx, + ctr, err := mssql.Run(ctx, "mcr.microsoft.com/mssql/server:2022-CU10-ubuntu-22.04", testcontainers.WithWaitStrategy( wait.ForLog("Password validation failed")), mssql.WithAcceptEULA(), mssql.WithPassword("weakPassword"), ) - - if err == nil { - t.Log("Success: Received invalid password validation docker log.") - } else { - t.Fatalf("Expected a password validation log but got error: %s", err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) } func TestMSSQLServerWithAlternativeImage(t *testing.T) { ctx := context.Background() - container, err := mssql.Run(ctx, + ctr, err := mssql.Run(ctx, "mcr.microsoft.com/mssql/server:2022-RTM-GDR1-ubuntu-20.04", mssql.WithAcceptEULA(), ) - if err != nil { - t.Fatalf("Failed to create the container with alternative image: %s", err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions - connectionString, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } + connectionString, err := ctr.ConnectionString(ctx) + require.NoError(t, err) db, err := sql.Open("sqlserver", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) } diff --git a/modules/mysql/examples_test.go b/modules/mysql/examples_test.go index bf203c9018a..61ee33113d5 100644 --- a/modules/mysql/examples_test.go +++ b/modules/mysql/examples_test.go @@ -7,6 +7,7 @@ import ( "log" "path/filepath" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/mysql" ) @@ -22,21 +23,21 @@ func ExampleRun() { mysql.WithPassword("password"), mysql.WithScripts(filepath.Join("testdata", "schema.sql")), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := mysqlContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(mysqlContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := mysqlContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -56,40 +57,45 @@ func ExampleRun_connect() { mysql.WithPassword("password"), mysql.WithScripts(filepath.Join("testdata", "schema.sql")), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := mysqlContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(mysqlContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } connectionString, err := mysqlContainer.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) // nolint:gocritic + log.Printf("failed to get connection string: %s", err) + return } db, err := sql.Open("mysql", connectionString) if err != nil { - log.Fatalf("failed to connect to MySQL: %s", err) // nolint:gocritic + log.Printf("failed to connect to MySQL: %s", err) + return } defer db.Close() if err = db.Ping(); err != nil { - log.Fatalf("failed to ping MySQL: %s", err) + log.Printf("failed to ping MySQL: %s", err) + return } stmt, err := db.Prepare("SELECT @@GLOBAL.tmpdir") if err != nil { - log.Fatalf("failed to prepare statement: %s", err) + log.Printf("failed to prepare statement: %s", err) + return } defer stmt.Close() row := stmt.QueryRow() tmpDir := "" err = row.Scan(&tmpDir) if err != nil { - log.Fatalf("failed to scan row: %s", err) + log.Printf("failed to scan row: %s", err) + return } fmt.Println(tmpDir) diff --git a/modules/mysql/go.mod b/modules/mysql/go.mod index f3f00dcb8be..9e0da8e36a2 100644 --- a/modules/mysql/go.mod +++ b/modules/mysql/go.mod @@ -4,7 +4,8 @@ go 1.22 require ( github.com/go-sql-driver/mysql v1.7.1 - github.com/testcontainers/testcontainers-go v0.33.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 ) @@ -17,6 +18,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect @@ -28,6 +30,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.4 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -39,6 +42,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -55,6 +59,7 @@ require ( golang.org/x/sys v0.21.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/mysql/go.sum b/modules/mysql/go.sum index e2bb93b49dc..dcc1a8d165a 100644 --- a/modules/mysql/go.sum +++ b/modules/mysql/go.sum @@ -16,6 +16,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -54,6 +55,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -80,6 +85,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -176,6 +183,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/mysql/mysql.go b/modules/mysql/mysql.go index 7bc6bf7e25b..4eee6e654e4 100644 --- a/modules/mysql/mysql.go +++ b/modules/mysql/mysql.go @@ -86,13 +86,21 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) - if err != nil { - return nil, err + var c *MySQLContainer + if container != nil { + c = &MySQLContainer{ + Container: container, + password: password, + username: username, + database: req.Env["MYSQL_DATABASE"], + } } - database := req.Env["MYSQL_DATABASE"] + if err != nil { + return c, fmt.Errorf("generic container: %w", err) + } - return &MySQLContainer{container, username, password, database}, nil + return c, nil } // MustConnectionString panics if the address cannot be determined. diff --git a/modules/mysql/mysql_test.go b/modules/mysql/mysql_test.go index e40ce9bf588..364f2a97a87 100644 --- a/modules/mysql/mysql_test.go +++ b/modules/mysql/mysql_test.go @@ -8,151 +8,110 @@ import ( // Import mysql into the scope of this package (required) _ "github.com/go-sql-driver/mysql" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/mysql" ) func TestMySQL(t *testing.T) { ctx := context.Background() - container, err := mysql.Run(ctx, "mysql:8.0.36") - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := mysql.Run(ctx, "mysql:8.0.36") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions // connectionString { - connectionString, err := container.ConnectionString(ctx, "tls=skip-verify") + connectionString, err := ctr.ConnectionString(ctx, "tls=skip-verify") // } - if err != nil { - t.Fatal(err) - } - mustConnectionString := container.MustConnectionString(ctx, "tls=skip-verify") - if mustConnectionString != connectionString { - t.Errorf("ConnectionString was not equal to MustConnectionString") - } + require.NoError(t, err) + + mustConnectionString := ctr.MustConnectionString(ctx, "tls=skip-verify") + require.Equal(t, connectionString, mustConnectionString) db, err := sql.Open("mysql", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) + _, err = db.Exec("CREATE TABLE IF NOT EXISTS a_table ( \n" + " `col_1` VARCHAR(128) NOT NULL, \n" + " `col_2` VARCHAR(128) NOT NULL, \n" + " PRIMARY KEY (`col_1`, `col_2`) \n" + ")") - if err != nil { - t.Errorf("error creating table: %+v\n", err) - } + require.NoError(t, err) } func TestMySQLWithNonRootUserAndEmptyPassword(t *testing.T) { ctx := context.Background() - _, err := mysql.Run(ctx, + ctr, err := mysql.Run(ctx, "mysql:8.0.36", mysql.WithDatabase("foo"), mysql.WithUsername("test"), mysql.WithPassword("")) - if err.Error() != "empty password can be used only with the root user" { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.EqualError(t, err, "empty password can be used only with the root user") } func TestMySQLWithRootUserAndEmptyPassword(t *testing.T) { ctx := context.Background() - container, err := mysql.Run(ctx, + ctr, err := mysql.Run(ctx, "mysql:8.0.36", mysql.WithDatabase("foo"), mysql.WithUsername("root"), mysql.WithPassword("")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions - connectionString, _ := container.ConnectionString(ctx) + connectionString, _ := ctr.ConnectionString(ctx) db, err := sql.Open("mysql", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) + _, err = db.Exec("CREATE TABLE IF NOT EXISTS a_table ( \n" + " `col_1` VARCHAR(128) NOT NULL, \n" + " `col_2` VARCHAR(128) NOT NULL, \n" + " PRIMARY KEY (`col_1`, `col_2`) \n" + ")") - if err != nil { - t.Errorf("error creating table: %+v\n", err) - } + require.NoError(t, err) } func TestMySQLWithScripts(t *testing.T) { ctx := context.Background() - container, err := mysql.Run(ctx, + ctr, err := mysql.Run(ctx, "mysql:8.0.36", mysql.WithScripts(filepath.Join("testdata", "schema.sql"))) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions - connectionString, _ := container.ConnectionString(ctx) + connectionString, _ := ctr.ConnectionString(ctx) db, err := sql.Open("mysql", connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if err = db.Ping(); err != nil { - t.Errorf("error pinging db: %+v\n", err) - } + err = db.Ping() + require.NoError(t, err) + stmt, err := db.Prepare("SELECT name from profile") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer stmt.Close() + row := stmt.QueryRow() var name string err = row.Scan(&name) - if err != nil { - t.Errorf("error fetching data") - } - if name != "profile 1" { - t.Fatal("The expected record was not found in the database.") - } + require.NoError(t, err) + require.Equal(t, "profile 1", name) } diff --git a/modules/nats/examples_test.go b/modules/nats/examples_test.go index 56ade421877..b88fba4c4a1 100644 --- a/modules/nats/examples_test.go +++ b/modules/nats/examples_test.go @@ -8,6 +8,7 @@ import ( natsgo "github.com/nats-io/nats.go" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/nats" "github.com/testcontainers/testcontainers-go/network" ) @@ -17,21 +18,21 @@ func ExampleRun() { ctx := context.Background() natsContainer, err := nats.Run(ctx, "nats:2.9") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := natsContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(natsContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := natsContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -44,26 +45,27 @@ func ExampleRun_connectWithCredentials() { // natsConnect { ctx := context.Background() - container, err := nats.Run(ctx, "nats:2.9", nats.WithUsername("foo"), nats.WithPassword("bar")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container + ctr, err := nats.Run(ctx, "nats:2.9", nats.WithUsername("foo"), nats.WithPassword("bar")) defer func() { - if err := container.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(ctr); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } - uri, err := container.ConnectionString(ctx) + uri, err := ctr.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) // nolint:gocritic + log.Printf("failed to get connection string: %s", err) + return } - nc, err := natsgo.Connect(uri, natsgo.UserInfo(container.User, container.Password)) + nc, err := natsgo.Connect(uri, natsgo.UserInfo(ctr.User, ctr.Password)) if err != nil { - log.Fatalf("failed to connect to NATS: %s", err) + log.Printf("failed to connect to NATS: %s", err) + return } defer nc.Close() // } @@ -79,9 +81,16 @@ func ExampleRun_cluster() { nwr, err := network.New(ctx) if err != nil { - log.Fatalf("failed to create network: %s", err) + log.Printf("failed to create network: %s", err) + return } + defer func() { + if err := nwr.Remove(context.Background()); err != nil { + log.Printf("failed to remove network: %s", err) + } + }() + // withArguments { natsContainer1, err := nats.Run(ctx, "nats:2.9", @@ -93,15 +102,15 @@ func ExampleRun_cluster() { nats.WithArgument("http_port", "8222"), ) // } - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - // Clean up the container defer func() { - if err := natsContainer1.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(natsContainer1); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } natsContainer2, err := nats.Run(ctx, "nats:2.9", @@ -112,15 +121,15 @@ func ExampleRun_cluster() { nats.WithArgument("routes", "nats://nats1:6222,nats://nats2:6222,nats://nats3:6222"), nats.WithArgument("http_port", "8222"), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) // nolint:gocritic - } - // Clean up the container defer func() { - if err := natsContainer2.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(natsContainer2); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } natsContainer3, err := nats.Run(ctx, "nats:2.9", @@ -131,28 +140,34 @@ func ExampleRun_cluster() { nats.WithArgument("routes", "nats://nats1:6222,nats://nats2:6222,nats://nats3:6222"), nats.WithArgument("http_port", "8222"), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) // nolint:gocritic - } defer func() { - if err := natsContainer3.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(natsContainer3); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // cluster URL servers := natsContainer1.MustConnectionString(ctx) + "," + natsContainer2.MustConnectionString(ctx) + "," + natsContainer3.MustConnectionString(ctx) nc, err := natsgo.Connect(servers, natsgo.MaxReconnects(5), natsgo.ReconnectWait(2*time.Second)) if err != nil { - log.Fatalf("connecting to nats container failed:\n\t%v\n", err) // nolint:gocritic + log.Printf("connecting to nats container failed:\n\t%v\n", err) + return } + // Close connection + defer nc.Close() + { // Simple Publisher err = nc.Publish("foo", []byte("Hello World")) if err != nil { - log.Fatalf("failed to publish message: %s", err) // nolint:gocritic + log.Printf("failed to publish message: %s", err) + return } } @@ -161,13 +176,15 @@ func ExampleRun_cluster() { ch := make(chan *natsgo.Msg, 64) sub, err := nc.ChanSubscribe("channel", ch) if err != nil { - log.Fatalf("failed to subscribe to message: %s", err) // nolint:gocritic + log.Printf("failed to subscribe to message: %s", err) + return } // Request err = nc.Publish("channel", []byte("Hello NATS Cluster!")) if err != nil { - log.Fatalf("failed to publish message: %s", err) // nolint:gocritic + log.Printf("failed to publish message: %s", err) + return } msg := <-ch @@ -175,12 +192,14 @@ func ExampleRun_cluster() { err = sub.Unsubscribe() if err != nil { - log.Fatalf("failed to unsubscribe: %s", err) // nolint:gocritic + log.Printf("failed to unsubscribe: %s", err) + return } err = sub.Drain() if err != nil { - log.Fatalf("failed to drain: %s", err) // nolint:gocritic + log.Printf("failed to drain: %s", err) + return } } @@ -189,29 +208,34 @@ func ExampleRun_cluster() { sub, err := nc.Subscribe("request", func(m *natsgo.Msg) { err1 := m.Respond([]byte("answer is 42")) if err1 != nil { - log.Fatalf("failed to respond to message: %s", err1) // nolint:gocritic + log.Printf("failed to respond to message: %s", err1) + return } }) if err != nil { - log.Fatalf("failed to subscribe to message: %s", err) // nolint:gocritic + log.Printf("failed to subscribe to message: %s", err) + return } // Request msg, err := nc.Request("request", []byte("what is the answer?"), 1*time.Second) if err != nil { - log.Fatalf("failed to send request: %s", err) // nolint:gocritic + log.Printf("failed to send request: %s", err) + return } fmt.Println(string(msg.Data)) err = sub.Unsubscribe() if err != nil { - log.Fatalf("failed to unsubscribe: %s", err) // nolint:gocritic + log.Printf("failed to unsubscribe: %s", err) + return } err = sub.Drain() if err != nil { - log.Fatalf("failed to drain: %s", err) // nolint:gocritic + log.Printf("failed to drain: %s", err) + return } } @@ -219,12 +243,10 @@ func ExampleRun_cluster() { // Close() not needed if this is called. err = nc.Drain() if err != nil { - log.Fatalf("failed to drain connection: %s", err) // nolint:gocritic + log.Printf("failed to drain connection: %s", err) + return } - // Close connection - nc.Close() - // Output: // Hello NATS Cluster! // answer is 42 diff --git a/modules/nats/go.mod b/modules/nats/go.mod index 0a4863c41ec..066f3d976f0 100644 --- a/modules/nats/go.mod +++ b/modules/nats/go.mod @@ -4,7 +4,8 @@ go 1.22 require ( github.com/nats-io/nats.go v1.33.1 - github.com/testcontainers/testcontainers-go v0.33.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 ) require ( @@ -16,6 +17,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect @@ -27,6 +29,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.4 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -40,6 +43,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -56,6 +60,7 @@ require ( golang.org/x/sys v0.21.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/nats/go.sum b/modules/nats/go.sum index 61daa84ab42..ec68d27737f 100644 --- a/modules/nats/go.sum +++ b/modules/nats/go.sum @@ -16,6 +16,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -52,6 +53,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -84,6 +89,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -180,6 +187,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/nats/nats.go b/modules/nats/nats.go index 0ded01dd09a..cd040c09e2a 100644 --- a/modules/nats/nats.go +++ b/modules/nats/nats.go @@ -59,17 +59,20 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) - if err != nil { - return nil, err + var c *NATSContainer + if container != nil { + c = &NATSContainer{ + Container: container, + User: settings.CmdArgs["user"], + Password: settings.CmdArgs["pass"], + } } - natsContainer := NATSContainer{ - Container: container, - User: settings.CmdArgs["user"], - Password: settings.CmdArgs["pass"], + if err != nil { + return c, fmt.Errorf("generic container: %w", err) } - return &natsContainer, nil + return c, nil } func (c *NATSContainer) MustConnectionString(ctx context.Context, args ...string) string { diff --git a/modules/nats/nats_test.go b/modules/nats/nats_test.go index e223f0aa240..660473c2e5e 100644 --- a/modules/nats/nats_test.go +++ b/modules/nats/nats_test.go @@ -5,7 +5,9 @@ import ( "testing" "github.com/nats-io/nats.go" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" tcnats "github.com/testcontainers/testcontainers-go/modules/nats" ) @@ -13,67 +15,45 @@ func TestNATS(t *testing.T) { ctx := context.Background() // createNATSContainer { - container, err := tcnats.Run(ctx, "nats:2.9") + ctr, err := tcnats.Run(ctx, "nats:2.9") // } - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // connectionString { - uri, err := container.ConnectionString(ctx) + uri, err := ctr.ConnectionString(ctx) // } - if err != nil { - t.Fatalf("failed to get connection string: %s", err) - } - mustUri := container.MustConnectionString(ctx) - if mustUri != uri { - t.Errorf("URI was not equal to MustUri") - } + require.NoError(t, err) + + mustUri := ctr.MustConnectionString(ctx) + require.Equal(t, mustUri, uri) + // perform assertions nc, err := nats.Connect(uri) - if err != nil { - t.Fatalf("failed to connect to nats: %s", err) - } + require.NoError(t, err) defer nc.Close() js, err := nc.JetStream() - if err != nil { - t.Fatalf("failed to create jetstream context: %s", err) - } + require.NoError(t, err) // add stream to nats - if _, err = js.AddStream(&nats.StreamConfig{ + _, err = js.AddStream(&nats.StreamConfig{ Name: "hello", Subjects: []string{"hello"}, - }); err != nil { - t.Fatalf("failed to add stream: %s", err) - } + }) + require.NoError(t, err) // add subscriber to nats sub, err := js.SubscribeSync("hello", nats.Durable("worker")) - if err != nil { - t.Fatalf("failed to subscribe to hello: %s", err) - } + require.NoError(t, err) // publish a message to nats - if _, err = js.Publish("hello", []byte("hello")); err != nil { - t.Fatalf("failed to publish hello: %s", err) - } + _, err = js.Publish("hello", []byte("hello")) + require.NoError(t, err) // wait for the message to be received msg, err := sub.NextMsgWithContext(ctx) - if err != nil { - t.Fatalf("failed to get message: %s", err) - } + require.NoError(t, err) - if string(msg.Data) != "hello" { - t.Fatalf("expected message to be 'hello', got '%s'", msg.Data) - } + require.Equal(t, "hello", string(msg.Data)) } diff --git a/modules/neo4j/examples_test.go b/modules/neo4j/examples_test.go index 375184a1d68..6e8c42936aa 100644 --- a/modules/neo4j/examples_test.go +++ b/modules/neo4j/examples_test.go @@ -5,6 +5,7 @@ import ( "fmt" "log" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/neo4j" ) @@ -20,21 +21,21 @@ func ExampleRun() { neo4j.WithLabsPlugin(neo4j.Apoc), neo4j.WithNeo4jSetting("dbms.tx_log.rotation.size", "42M"), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := neo4jContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(neo4jContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := neo4jContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/neo4j/go.mod b/modules/neo4j/go.mod index 13f310eeb50..50da4a55d49 100644 --- a/modules/neo4j/go.mod +++ b/modules/neo4j/go.mod @@ -5,7 +5,8 @@ go 1.22 require ( github.com/docker/go-connections v0.5.0 github.com/neo4j/neo4j-go-driver/v5 v5.18.0 - github.com/testcontainers/testcontainers-go v0.33.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 ) require ( @@ -17,6 +18,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect @@ -27,6 +29,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.4 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -38,6 +41,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -54,6 +58,7 @@ require ( golang.org/x/sys v0.21.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/neo4j/go.sum b/modules/neo4j/go.sum index 8cccf1cd871..54b5d9c6d56 100644 --- a/modules/neo4j/go.sum +++ b/modules/neo4j/go.sum @@ -16,6 +16,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -52,6 +53,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -80,6 +85,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -176,6 +183,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/neo4j/neo4j.go b/modules/neo4j/neo4j.go index e4f7fc3314d..36cac5e4be5 100644 --- a/modules/neo4j/neo4j.go +++ b/modules/neo4j/neo4j.go @@ -93,11 +93,16 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *Neo4jContainer + if container != nil { + c = &Neo4jContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &Neo4jContainer{Container: container}, nil + return c, nil } func isHttpOk() func(status int) bool { diff --git a/modules/neo4j/neo4j_test.go b/modules/neo4j/neo4j_test.go index 20bd17188b3..466bd3ddf78 100644 --- a/modules/neo4j/neo4j_test.go +++ b/modules/neo4j/neo4j_test.go @@ -8,7 +8,9 @@ import ( "testing" neo "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/neo4j" ) @@ -19,16 +21,12 @@ func TestNeo4j(outer *testing.T) { ctx := context.Background() - container := setupNeo4j(ctx, outer) - - outer.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - outer.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := setupNeo4j(ctx) + testcontainers.CleanupContainer(outer, ctr) + require.NoError(outer, err) outer.Run("connects via Bolt", func(t *testing.T) { - driver := createDriver(t, ctx, container) + driver := createDriver(t, ctx, ctr) err := driver.VerifyConnectivity(ctx) if err != nil { @@ -37,7 +35,7 @@ func TestNeo4j(outer *testing.T) { }) outer.Run("exercises APOC plugin", func(t *testing.T) { - driver := createDriver(t, ctx, container) + driver := createDriver(t, ctx, ctr) result, err := neo.ExecuteQuery(ctx, driver, "RETURN apoc.number.arabicToRoman(1986) AS output", nil, @@ -51,7 +49,7 @@ func TestNeo4j(outer *testing.T) { }) outer.Run("is configured with custom Neo4j settings", func(t *testing.T) { - env := getContainerEnv(t, ctx, container) + env := getContainerEnv(t, ctx, ctr) if !strings.Contains(env, "NEO4J_dbms_tx__log_rotation_size=42M") { t.Fatal("expected to custom setting to be exported but was not") @@ -73,22 +71,15 @@ func TestNeo4jWithEnterpriseLicense(t *testing.T) { edition, img := edition, img t.Run(edition, func(t *testing.T) { t.Parallel() - container, err := neo4j.Run(ctx, + ctr, err := neo4j.Run(ctx, img, neo4j.WithAdminPassword(testPassword), neo4j.WithAcceptCommercialLicenseAgreement(), ) - if err != nil { - t.Fatalf("expected container to successfully initialize but did not: %s", err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - - env := getContainerEnv(t, ctx, container) + env := getContainerEnv(t, ctx, ctr) if !strings.Contains(env, "NEO4J_ACCEPT_LICENSE_AGREEMENT=yes") { t.Fatal("expected to accept license agreement but did not") @@ -103,27 +94,22 @@ func TestNeo4jWithWrongSettings(outer *testing.T) { ctx := context.Background() outer.Run("without authentication", func(t *testing.T) { - container, err := neo4j.Run(ctx, "neo4j:4.4") - if err != nil { - t.Fatalf("expected env to successfully run but did not: %s", err) - } - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - outer.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := neo4j.Run(ctx, "neo4j:4.4") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) }) outer.Run("auth setting outside WithAdminPassword raises error", func(t *testing.T) { - container, err := neo4j.Run(ctx, + ctr, err := neo4j.Run(ctx, "neo4j:4.4", neo4j.WithAdminPassword(testPassword), neo4j.WithNeo4jSetting("AUTH", "neo4j/thisisgonnafail"), ) + testcontainers.CleanupContainer(t, ctr) if err == nil { t.Fatalf("expected env to fail due to conflicting auth settings but did not") } - if container != nil { + if ctr != nil { t.Fatalf("container must not be created with conflicting auth settings") } }) @@ -131,7 +117,7 @@ func TestNeo4jWithWrongSettings(outer *testing.T) { outer.Run("warns about overwrites of setting keys", func(t *testing.T) { // withSettings { logger := &inMemoryLogger{} - container, err := neo4j.Run(ctx, + ctr, err := neo4j.Run(ctx, "neo4j:4.4", neo4j.WithLogger(logger), // needs to go before WithNeo4jSetting and WithNeo4jSettings neo4j.WithAdminPassword(testPassword), @@ -140,29 +126,23 @@ func TestNeo4jWithWrongSettings(outer *testing.T) { neo4j.WithNeo4jSetting("some.key", "value3"), ) // } - if err != nil { - t.Fatalf("expected env to successfully run but did not: %s", err) - } - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - outer.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) errorLogs := logger.Logs() if !Contains(errorLogs, `setting "some.key" with value "value1" is now overwritten with value "value2"`+"\n") || !Contains(errorLogs, `setting "some.key" with value "value2" is now overwritten with value "value3"`+"\n") { t.Fatalf("expected setting overwrites to be logged") } - if !strings.Contains(getContainerEnv(t, ctx, container), "NEO4J_some_key=value3") { + if !strings.Contains(getContainerEnv(t, ctx, ctr), "NEO4J_some_key=value3") { t.Fatalf("expected custom setting to be set with last value") } }) outer.Run("rejects nil logger", func(t *testing.T) { - container, err := neo4j.Run(ctx, "neo4j:4.4", neo4j.WithLogger(nil)) - - if container != nil { + ctr, err := neo4j.Run(ctx, "neo4j:4.4", neo4j.WithLogger(nil)) + testcontainers.CleanupContainer(t, ctr) + if ctr != nil { t.Fatalf("container must not be created with nil logger") } if err == nil || err.Error() != "nil logger is not permitted" { @@ -171,8 +151,8 @@ func TestNeo4jWithWrongSettings(outer *testing.T) { }) } -func setupNeo4j(ctx context.Context, t *testing.T) *neo4j.Neo4jContainer { - container, err := neo4j.Run(ctx, +func setupNeo4j(ctx context.Context) (*neo4j.Neo4jContainer, error) { + return neo4j.Run(ctx, "neo4j:4.4", neo4j.WithAdminPassword(testPassword), // withLabsPlugin { @@ -180,10 +160,6 @@ func setupNeo4j(ctx context.Context, t *testing.T) *neo4j.Neo4jContainer { // } neo4j.WithNeo4jSetting("dbms.tx_log.rotation.size", "42M"), ) - if err != nil { - t.Fatalf("expected container to successfully initialize but did not: %s", err) - } - return container } func createDriver(t *testing.T, ctx context.Context, container *neo4j.Neo4jContainer) neo.DriverWithContext { diff --git a/modules/ollama/examples_test.go b/modules/ollama/examples_test.go index 3e2a273854c..46d65ebe0a7 100644 --- a/modules/ollama/examples_test.go +++ b/modules/ollama/examples_test.go @@ -10,6 +10,7 @@ import ( "github.com/tmc/langchaingo/llms" langchainollama "github.com/tmc/langchaingo/llms/ollama" + "github.com/testcontainers/testcontainers-go" tcollama "github.com/testcontainers/testcontainers-go/modules/ollama" ) @@ -18,21 +19,21 @@ func ExampleRun() { ctx := context.Background() ollamaContainer, err := tcollama.Run(ctx, "ollama/ollama:0.1.25") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := ollamaContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(ollamaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := ollamaContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -46,30 +47,34 @@ func ExampleRun_withModel_llama2_http() { ctx := context.Background() ollamaContainer, err := tcollama.Run(ctx, "ollama/ollama:0.1.25") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } defer func() { - if err := ollamaContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(ollamaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } model := "llama2" _, _, err = ollamaContainer.Exec(ctx, []string{"ollama", "pull", model}) if err != nil { - log.Fatalf("failed to pull model %s: %s", model, err) // nolint:gocritic + log.Printf("failed to pull model %s: %s", model, err) + return } _, _, err = ollamaContainer.Exec(ctx, []string{"ollama", "run", model}) if err != nil { - log.Fatalf("failed to run model %s: %s", model, err) // nolint:gocritic + log.Printf("failed to run model %s: %s", model, err) + return } connectionStr, err := ollamaContainer.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) // nolint:gocritic + log.Printf("failed to get connection string: %s", err) + return } httpClient := &http.Client{} @@ -82,12 +87,14 @@ func ExampleRun_withModel_llama2_http() { req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/generate", connectionStr), strings.NewReader(payload)) if err != nil { - log.Fatalf("failed to create request: %s", err) // nolint:gocritic + log.Printf("failed to create request: %s", err) + return } resp, err := httpClient.Do(req) if err != nil { - log.Fatalf("failed to get response: %s", err) // nolint:gocritic + log.Printf("failed to get response: %s", err) + return } // } @@ -101,30 +108,34 @@ func ExampleRun_withModel_llama2_langchain() { ctx := context.Background() ollamaContainer, err := tcollama.Run(ctx, "ollama/ollama:0.1.25") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } defer func() { - if err := ollamaContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(ollamaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } model := "llama2" _, _, err = ollamaContainer.Exec(ctx, []string{"ollama", "pull", model}) if err != nil { - log.Fatalf("failed to pull model %s: %s", model, err) // nolint:gocritic + log.Printf("failed to pull model %s: %s", model, err) + return } _, _, err = ollamaContainer.Exec(ctx, []string{"ollama", "run", model}) if err != nil { - log.Fatalf("failed to run model %s: %s", model, err) // nolint:gocritic + log.Printf("failed to run model %s: %s", model, err) + return } connectionStr, err := ollamaContainer.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) // nolint:gocritic + log.Printf("failed to get connection string: %s", err) + return } var llm *langchainollama.LLM @@ -132,7 +143,8 @@ func ExampleRun_withModel_llama2_langchain() { langchainollama.WithModel(model), langchainollama.WithServerURL(connectionStr), ); err != nil { - log.Fatalf("failed to create langchain ollama: %s", err) // nolint:gocritic + log.Printf("failed to create langchain ollama: %s", err) + return } completion, err := llm.Call( @@ -142,7 +154,8 @@ func ExampleRun_withModel_llama2_langchain() { llms.WithTemperature(0.0), // the lower the temperature, the more creative the completion ) if err != nil { - log.Fatalf("failed to create langchain ollama: %s", err) // nolint:gocritic + log.Printf("failed to create langchain ollama: %s", err) + return } words := []string{ diff --git a/modules/ollama/go.mod b/modules/ollama/go.mod index 5e586e858ed..b338de0b0d7 100644 --- a/modules/ollama/go.mod +++ b/modules/ollama/go.mod @@ -5,7 +5,8 @@ go 1.22 require ( github.com/docker/docker v27.1.1+incompatible github.com/google/uuid v1.6.0 - github.com/testcontainers/testcontainers-go v0.33.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 github.com/tmc/langchaingo v0.1.5 ) @@ -18,6 +19,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/dlclark/regexp2 v1.8.1 // indirect github.com/docker/go-connections v0.5.0 // indirect @@ -40,6 +42,7 @@ require ( github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkoukk/tiktoken-go v0.1.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -56,6 +59,7 @@ require ( golang.org/x/sys v0.21.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/ollama/go.sum b/modules/ollama/go.sum index d2ff261f5b1..9e9bf8b2c1e 100644 --- a/modules/ollama/go.sum +++ b/modules/ollama/go.sum @@ -54,6 +54,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -82,6 +86,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -180,6 +186,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/ollama/ollama.go b/modules/ollama/ollama.go index b8a2fc1de66..203d80103f0 100644 --- a/modules/ollama/ollama.go +++ b/modules/ollama/ollama.go @@ -101,9 +101,14 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *OllamaContainer + if container != nil { + c = &OllamaContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &OllamaContainer{Container: container}, nil + return c, nil } diff --git a/modules/ollama/ollama_test.go b/modules/ollama/ollama_test.go index b60538835b6..0f6614a51e4 100644 --- a/modules/ollama/ollama_test.go +++ b/modules/ollama/ollama_test.go @@ -4,13 +4,14 @@ import ( "context" "fmt" "io" - "log" "net/http" "strings" "testing" "github.com/google/uuid" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/exec" "github.com/testcontainers/testcontainers-go/modules/ollama" ) @@ -18,52 +19,34 @@ import ( func TestOllama(t *testing.T) { ctx := context.Background() - container, err := ollama.Run(ctx, "ollama/ollama:0.1.25") - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := ollama.Run(ctx, "ollama/ollama:0.1.25") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) t.Run("ConnectionString", func(t *testing.T) { // connectionString { - connectionStr, err := container.ConnectionString(ctx) + connectionStr, err := ctr.ConnectionString(ctx) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) httpClient := &http.Client{} resp, err := httpClient.Get(connectionStr) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("expected status code 200, got %d", resp.StatusCode) - } + require.Equal(t, http.StatusOK, resp.StatusCode) }) t.Run("Pull and Run Model", func(t *testing.T) { model := "all-minilm" - _, _, err = container.Exec(context.Background(), []string{"ollama", "pull", model}) - if err != nil { - log.Fatalf("failed to pull model %s: %s", model, err) - } + _, _, err = ctr.Exec(context.Background(), []string{"ollama", "pull", model}) + require.NoError(t, err) - _, _, err = container.Exec(context.Background(), []string{"ollama", "run", model}) - if err != nil { - log.Fatalf("failed to run model %s: %s", model, err) - } + _, _, err = ctr.Exec(context.Background(), []string{"ollama", "run", model}) + require.NoError(t, err) - assertLoadedModel(t, container) + assertLoadedModel(t, ctr) }) t.Run("Commit to image including model", func(t *testing.T) { @@ -73,24 +56,16 @@ func TestOllama(t *testing.T) { // Users can change the way this is generated, but it should be unique. targetImage := fmt.Sprintf("%s-%s", ollama.DefaultOllamaImage, strings.ToLower(uuid.New().String()[:4])) - err := container.Commit(context.Background(), targetImage) + err := ctr.Commit(context.Background(), targetImage) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) newOllamaContainer, err := ollama.Run( context.Background(), targetImage, ) - if err != nil { - t.Fatal(err) - } - t.Cleanup(func() { - if err := newOllamaContainer.Terminate(context.Background()); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, newOllamaContainer) + require.NoError(t, err) assertLoadedModel(t, newOllamaContainer) }) @@ -101,30 +76,20 @@ func TestOllama(t *testing.T) { // contains the model name. func assertLoadedModel(t *testing.T, c *ollama.OllamaContainer) { url, err := c.ConnectionString(context.Background()) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) httpCli := &http.Client{} resp, err := httpCli.Get(url + "/api/tags") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("expected status code 200, got %d", resp.StatusCode) - } + require.Equal(t, http.StatusOK, resp.StatusCode) bs, err := io.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if !strings.Contains(string(bs), "all-minilm") { - t.Fatalf("expected response to contain all-minilm, got %s", string(bs)) - } + require.Contains(t, string(bs), "all-minilm") } func TestRunContainer_withModel_error(t *testing.T) { @@ -134,30 +99,21 @@ func TestRunContainer_withModel_error(t *testing.T) { ctx, "ollama/ollama:0.1.25", ) - if err != nil { - t.Fatalf("expected error to be nil, got %s", err) - } + testcontainers.CleanupContainer(t, ollamaContainer) + require.NoError(t, err) model := "non-existent" _, _, err = ollamaContainer.Exec(ctx, []string{"ollama", "pull", model}) - if err != nil { - log.Fatalf("expected nil error, got %s", err) - } + require.NoError(t, err) // we need to parse the response here to check if the error message is correct _, r, err := ollamaContainer.Exec(ctx, []string{"ollama", "run", model}, exec.Multiplexed()) - if err != nil { - log.Fatalf("expected nil error, got %s", err) - } + require.NoError(t, err) bs, err := io.ReadAll(r) - if err != nil { - t.Fatalf("failed to run %s model: %s", model, err) - } + require.NoError(t, err) stdOutput := string(bs) - if !strings.Contains(stdOutput, "Error: pull model manifest: file does not exist") { - t.Fatalf("expected output to contain %q, got %s", "Error: pull model manifest: file does not exist", stdOutput) - } + require.Contains(t, stdOutput, "Error: pull model manifest: file does not exist") } diff --git a/modules/openfga/examples_test.go b/modules/openfga/examples_test.go index 38609451ef9..cb0443b8639 100644 --- a/modules/openfga/examples_test.go +++ b/modules/openfga/examples_test.go @@ -22,21 +22,21 @@ func ExampleRun() { ctx := context.Background() openfgaContainer, err := openfga.Run(ctx, "openfga/openfga:v1.5.0") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := openfgaContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(openfgaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := openfgaContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -47,21 +47,21 @@ func ExampleRun() { func ExampleRun_connectToPlayground() { openfgaContainer, err := openfga.Run(context.Background(), "openfga/openfga:v1.5.0") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := openfgaContainer.Terminate(context.Background()); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(openfgaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // playgroundEndpoint { playgroundEndpoint, err := openfgaContainer.PlaygroundEndpoint(context.Background()) if err != nil { - log.Fatalf("failed to get playground endpoint: %s", err) // nolint:gocritic + log.Printf("failed to get playground endpoint: %s", err) + return } // } @@ -69,7 +69,8 @@ func ExampleRun_connectToPlayground() { resp, err := httpClient.Get(playgroundEndpoint) if err != nil { - log.Fatalf("failed to get playground endpoint: %s", err) // nolint:gocritic + log.Printf("failed to get playground endpoint: %s", err) + return } fmt.Println(resp.StatusCode) @@ -80,21 +81,21 @@ func ExampleRun_connectToPlayground() { func ExampleRun_connectWithSDKClient() { openfgaContainer, err := openfga.Run(context.Background(), "openfga/openfga:v1.5.0") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := openfgaContainer.Terminate(context.Background()); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(openfgaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // httpEndpoint { httpEndpoint, err := openfgaContainer.HttpEndpoint(context.Background()) if err != nil { - log.Fatalf("failed to get HTTP endpoint: %s", err) // nolint:gocritic + log.Printf("failed to get HTTP endpoint: %s", err) + return } // } @@ -103,26 +104,30 @@ func ExampleRun_connectWithSDKClient() { ApiUrl: httpEndpoint, // required }) if err != nil { - log.Fatalf("failed to create SDK client: %s", err) // nolint:gocritic + log.Printf("failed to create SDK client: %s", err) + return } list, err := fgaClient.ListStores(context.Background()).Execute() if err != nil { - log.Fatalf("failed to list stores: %s", err) // nolint:gocritic + log.Printf("failed to list stores: %s", err) + return } fmt.Println(len(list.Stores)) store, err := fgaClient.CreateStore(context.Background()).Body(client.ClientCreateStoreRequest{Name: "test"}).Execute() if err != nil { - log.Fatalf("failed to create store: %s", err) // nolint:gocritic + log.Printf("failed to create store: %s", err) + return } fmt.Println(store.Name) list, err = fgaClient.ListStores(context.Background()).Execute() if err != nil { - log.Fatalf("failed to list stores: %s", err) // nolint:gocritic + log.Printf("failed to list stores: %s", err) + return } fmt.Println(len(list.Stores)) @@ -145,20 +150,20 @@ func ExampleRun_writeModel() { "OPENFGA_AUTHN_PRESHARED_KEYS": secret, }), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := openfgaContainer.Terminate(context.Background()); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(openfgaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } httpEndpoint, err := openfgaContainer.HttpEndpoint(context.Background()) if err != nil { - log.Fatalf("failed to get HTTP endpoint: %s", err) // nolint:gocritic + log.Printf("failed to get HTTP endpoint: %s", err) + return } fgaClient, err := client.NewSdkClient(&client.ClientConfiguration{ @@ -177,28 +182,33 @@ func ExampleRun_writeModel() { StoreId: "11111111111111111111111111", }) if err != nil { - log.Fatalf("failed to create openfga client: %v", err) + log.Printf("failed to create openfga client: %v", err) + return } f, err := os.Open(filepath.Join("testdata", "authorization_model.json")) if err != nil { - log.Fatalf("failed to open file: %v", err) + log.Printf("failed to open file: %v", err) + return } defer f.Close() bs, err := io.ReadAll(f) if err != nil { - log.Fatalf("failed to read file: %v", err) + log.Printf("failed to read file: %v", err) + return } var body client.ClientWriteAuthorizationModelRequest if err := json.Unmarshal(bs, &body); err != nil { - log.Fatalf("failed to unmarshal json: %v", err) + log.Printf("failed to unmarshal json: %v", err) + return } resp, err := fgaClient.WriteAuthorizationModel(context.Background()).Body(body).Execute() if err != nil { - log.Fatalf("failed to write authorization model: %v", err) + log.Printf("failed to write authorization model: %v", err) + return } // } diff --git a/modules/openfga/go.mod b/modules/openfga/go.mod index 388b9c040c1..3f5a238a821 100644 --- a/modules/openfga/go.mod +++ b/modules/openfga/go.mod @@ -4,7 +4,8 @@ go 1.22 require ( github.com/openfga/go-sdk v0.3.5 - github.com/testcontainers/testcontainers-go v0.33.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 ) require ( @@ -16,6 +17,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect @@ -27,6 +29,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.4 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -38,6 +41,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -55,6 +59,7 @@ require ( golang.org/x/sys v0.21.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/openfga/go.sum b/modules/openfga/go.sum index 1ab48ebfe26..a24767f45fa 100644 --- a/modules/openfga/go.sum +++ b/modules/openfga/go.sum @@ -16,6 +16,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -54,6 +55,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -82,6 +87,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -180,6 +187,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/openfga/openfga.go b/modules/openfga/openfga.go index ccdaab71cb2..d6a3930a1d6 100644 --- a/modules/openfga/openfga.go +++ b/modules/openfga/openfga.go @@ -78,9 +78,14 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *OpenFGAContainer + if container != nil { + c = &OpenFGAContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &OpenFGAContainer{Container: container}, nil + return c, nil } diff --git a/modules/openfga/openfga_test.go b/modules/openfga/openfga_test.go index ec0a16bf1b6..85e1966198b 100644 --- a/modules/openfga/openfga_test.go +++ b/modules/openfga/openfga_test.go @@ -4,23 +4,18 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/openfga" ) func TestOpenFGA(t *testing.T) { ctx := context.Background() - container, err := openfga.Run(ctx, "openfga/openfga:v1.5.0") - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := openfga.Run(ctx, "openfga/openfga:v1.5.0") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // perform assertions } diff --git a/modules/openldap/examples_test.go b/modules/openldap/examples_test.go index f3bf2f40f5f..757385cc925 100644 --- a/modules/openldap/examples_test.go +++ b/modules/openldap/examples_test.go @@ -7,6 +7,7 @@ import ( "github.com/go-ldap/ldap/v3" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/openldap" ) @@ -15,21 +16,21 @@ func ExampleRun() { ctx := context.Background() openldapContainer, err := openldap.Run(ctx, "bitnami/openldap:2.6.6") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := openldapContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(openldapContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := openldapContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -43,32 +44,34 @@ func ExampleRun_connect() { ctx := context.Background() openldapContainer, err := openldap.Run(ctx, "bitnami/openldap:2.6.6") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := openldapContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(openldapContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } connectionString, err := openldapContainer.ConnectionString(ctx) if err != nil { - log.Fatalf("failed to get connection string: %s", err) // nolint:gocritic + log.Printf("failed to get connection string: %s", err) + return } client, err := ldap.DialURL(connectionString) if err != nil { - log.Fatalf("failed to connect to LDAP server: %s", err) + log.Printf("failed to connect to LDAP server: %s", err) + return } defer client.Close() // First bind with a read only user err = client.Bind("cn=admin,dc=example,dc=org", "adminpassword") if err != nil { - log.Fatalf("failed to bind to LDAP server: %s", err) + log.Printf("failed to bind to LDAP server: %s", err) + return } // Search for the given username @@ -82,11 +85,13 @@ func ExampleRun_connect() { sr, err := client.Search(searchRequest) if err != nil { - log.Fatalf("failed to search LDAP server: %s", err) + log.Printf("failed to search LDAP server: %s", err) + return } if len(sr.Entries) != 1 { - log.Fatal("User does not exist or too many entries returned") + log.Print("User does not exist or too many entries returned") + return } fmt.Println(sr.Entries[0].DN) diff --git a/modules/openldap/go.mod b/modules/openldap/go.mod index 2f13ed78cc1..e096880ed6e 100644 --- a/modules/openldap/go.mod +++ b/modules/openldap/go.mod @@ -4,7 +4,8 @@ go 1.22 require ( github.com/go-ldap/ldap/v3 v3.4.6 - github.com/testcontainers/testcontainers-go v0.33.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 ) require ( @@ -17,6 +18,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect @@ -29,6 +31,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.4 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -40,6 +43,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -56,6 +60,7 @@ require ( golang.org/x/sys v0.21.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/openldap/go.sum b/modules/openldap/go.sum index eb7f8b79e36..0723cad7624 100644 --- a/modules/openldap/go.sum +++ b/modules/openldap/go.sum @@ -20,6 +20,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -61,6 +62,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -87,6 +92,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -211,6 +218,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/openldap/openldap.go b/modules/openldap/openldap.go index dc215226c1d..940897f4ddd 100644 --- a/modules/openldap/openldap.go +++ b/modules/openldap/openldap.go @@ -161,14 +161,19 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *OpenLDAPContainer + if container != nil { + c = &OpenLDAPContainer{ + Container: container, + adminUsername: req.Env["LDAP_ADMIN_USERNAME"], + adminPassword: req.Env["LDAP_ADMIN_PASSWORD"], + rootDn: req.Env["LDAP_ROOT"], + } + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &OpenLDAPContainer{ - Container: container, - adminUsername: req.Env["LDAP_ADMIN_USERNAME"], - adminPassword: req.Env["LDAP_ADMIN_PASSWORD"], - rootDn: req.Env["LDAP_ROOT"], - }, nil + return c, nil } diff --git a/modules/openldap/openldap_test.go b/modules/openldap/openldap_test.go index 40639b9932a..b73a8dce366 100644 --- a/modules/openldap/openldap_test.go +++ b/modules/openldap/openldap_test.go @@ -6,112 +6,70 @@ import ( "testing" "github.com/go-ldap/ldap/v3" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/openldap" ) func TestOpenLDAP(t *testing.T) { ctx := context.Background() - container, err := openldap.Run(ctx, "bitnami/openldap:2.6.6") - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := openldap.Run(ctx, "bitnami/openldap:2.6.6") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) } func TestOpenLDAPWithAdminUsernameAndPassword(t *testing.T) { ctx := context.Background() - container, err := openldap.Run(ctx, + ctr, err := openldap.Run(ctx, "bitnami/openldap:2.6.6", openldap.WithAdminUsername("openldap"), openldap.WithAdminPassword("openldap"), ) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - connectionString, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } + connectionString, err := ctr.ConnectionString(ctx) + require.NoError(t, err) client, err := ldap.DialURL(connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer client.Close() // First bind with a read only user err = client.Bind("cn=openldap,dc=example,dc=org", "openldap") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } func TestOpenLDAPWithDifferentRoot(t *testing.T) { ctx := context.Background() - container, err := openldap.Run(ctx, "bitnami/openldap:2.6.6", openldap.WithRoot("dc=mydomain,dc=com")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := openldap.Run(ctx, "bitnami/openldap:2.6.6", openldap.WithRoot("dc=mydomain,dc=com")) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // connectionString { - connectionString, err := container.ConnectionString(ctx) + connectionString, err := ctr.ConnectionString(ctx) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) client, err := ldap.DialURL(connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer client.Close() // First bind with a read only user err = client.Bind("cn=admin,dc=mydomain,dc=com", "adminpassword") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } func TestOpenLDAPLoadLdif(t *testing.T) { ctx := context.Background() - container, err := openldap.Run(ctx, "bitnami/openldap:2.6.6") - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := openldap.Run(ctx, "bitnami/openldap:2.6.6") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // loadLdif { ldif := ` @@ -124,28 +82,20 @@ mail: test.user@example.org userPassword: Password1 ` - err = container.LoadLdif(ctx, []byte(ldif)) + err = ctr.LoadLdif(ctx, []byte(ldif)) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - connectionString, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } + connectionString, err := ctr.ConnectionString(ctx) + require.NoError(t, err) client, err := ldap.DialURL(connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer client.Close() // First bind with a read only user err = client.Bind("cn=admin,dc=example,dc=org", "adminpassword") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) result, err := client.Search(&ldap.SearchRequest{ BaseDN: "uid=test.user,ou=users,dc=example,dc=org", @@ -153,16 +103,9 @@ userPassword: Password1 Filter: "(objectClass=*)", Attributes: []string{"dn"}, }) - if err != nil { - t.Fatal(err) - } - - if len(result.Entries) != 1 { - t.Fatal("Invalid number of entries returned", result.Entries) - } - if result.Entries[0].DN != "uid=test.user,ou=users,dc=example,dc=org" { - t.Fatal("Invalid entry returned", result.Entries[0].DN) - } + require.NoError(t, err) + require.Len(t, result.Entries, 1) + require.Equal(t, "uid=test.user,ou=users,dc=example,dc=org", result.Entries[0].DN) } func TestOpenLDAPWithInitialLdif(t *testing.T) { @@ -178,47 +121,28 @@ userPassword: Password1 ` f, err := os.CreateTemp(t.TempDir(), "test.ldif") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) _, err = f.WriteString(ldif) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + err = f.Close() - if err != nil { - t.Fatal(err) - } - - container, err := openldap.Run(ctx, "bitnami/openldap:2.6.6", openldap.WithInitialLdif(f.Name())) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + require.NoError(t, err) - connectionString, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } + ctr, err := openldap.Run(ctx, "bitnami/openldap:2.6.6", openldap.WithInitialLdif(f.Name())) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + connectionString, err := ctr.ConnectionString(ctx) + require.NoError(t, err) client, err := ldap.DialURL(connectionString) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer client.Close() // First bind with a read only user err = client.Bind("cn=admin,dc=example,dc=org", "adminpassword") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) result, err := client.Search(&ldap.SearchRequest{ BaseDN: "uid=test.user,ou=users,dc=example,dc=org", @@ -226,14 +150,8 @@ userPassword: Password1 Filter: "(objectClass=*)", Attributes: []string{"dn"}, }) - if err != nil { - t.Fatal(err) - } - - if len(result.Entries) != 1 { - t.Fatal("Invalid number of entries returned", result.Entries) - } - if result.Entries[0].DN != "uid=test.user,ou=users,dc=example,dc=org" { - t.Fatal("Invalid entry returned", result.Entries[0].DN) - } + require.NoError(t, err) + + require.Len(t, result.Entries, 1) + require.Equal(t, "uid=test.user,ou=users,dc=example,dc=org", result.Entries[0].DN) } diff --git a/modules/opensearch/examples_test.go b/modules/opensearch/examples_test.go index 89bc9cb7c60..d2e4c5807d8 100644 --- a/modules/opensearch/examples_test.go +++ b/modules/opensearch/examples_test.go @@ -5,6 +5,7 @@ import ( "fmt" "log" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/opensearch" ) @@ -18,21 +19,21 @@ func ExampleRun() { opensearch.WithUsername("new-username"), opensearch.WithPassword("new-password"), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := opensearchContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(opensearchContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := opensearchContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/opensearch/go.mod b/modules/opensearch/go.mod index 50146f5964b..7fe56f884bc 100644 --- a/modules/opensearch/go.mod +++ b/modules/opensearch/go.mod @@ -5,7 +5,8 @@ go 1.22 require ( github.com/docker/docker v27.1.1+incompatible github.com/docker/go-units v0.5.0 - github.com/testcontainers/testcontainers-go v0.33.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 ) require ( @@ -17,6 +18,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -26,6 +28,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.4 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -37,6 +40,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -53,6 +57,7 @@ require ( golang.org/x/sys v0.21.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/opensearch/go.sum b/modules/opensearch/go.sum index ed514ea5ef8..28367d00203 100644 --- a/modules/opensearch/go.sum +++ b/modules/opensearch/go.sum @@ -16,6 +16,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -52,6 +53,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -78,6 +83,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -174,6 +181,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/opensearch/opensearch.go b/modules/opensearch/opensearch.go index 83177c8471b..fcc5a1f7147 100644 --- a/modules/opensearch/opensearch.go +++ b/modules/opensearch/opensearch.go @@ -118,11 +118,16 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom }) container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *OpenSearchContainer + if container != nil { + c = &OpenSearchContainer{Container: container, User: username, Password: password} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &OpenSearchContainer{Container: container, User: username, Password: password}, nil + return c, nil } // Address retrieves the address of the OpenSearch container. diff --git a/modules/opensearch/opensearch_test.go b/modules/opensearch/opensearch_test.go index 64d0db37a52..3829ea2dfbf 100644 --- a/modules/opensearch/opensearch_test.go +++ b/modules/opensearch/opensearch_test.go @@ -5,41 +5,30 @@ import ( "net/http" "testing" + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/opensearch" ) func TestOpenSearch(t *testing.T) { ctx := context.Background() - container, err := opensearch.Run(ctx, "opensearchproject/opensearch:2.11.1") - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := opensearch.Run(ctx, "opensearchproject/opensearch:2.11.1") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) t.Run("Connect to Address", func(t *testing.T) { - address, err := container.Address(ctx) - if err != nil { - t.Fatal(err) - } + address, err := ctr.Address(ctx) + require.NoError(t, err) client := &http.Client{} req, err := http.NewRequest("GET", address, nil) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) resp, err := client.Do(req) - if err != nil { - t.Fatalf("failed to perform GET request: %s", err) - } + require.NoError(t, err) defer resp.Body.Close() }) } diff --git a/modules/postgres/examples_test.go b/modules/postgres/examples_test.go index d5790686867..8b7f562ea9b 100644 --- a/modules/postgres/examples_test.go +++ b/modules/postgres/examples_test.go @@ -32,21 +32,21 @@ func ExampleRun() { WithOccurrence(2). WithStartupTimeout(5*time.Second)), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := postgresContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(postgresContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := postgresContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/postgres/postgres.go b/modules/postgres/postgres.go index 0ab4d889d15..a6bc418ff37 100644 --- a/modules/postgres/postgres.go +++ b/modules/postgres/postgres.go @@ -169,15 +169,22 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) - if err != nil { - return nil, err + var c *PostgresContainer + if container != nil { + c = &PostgresContainer{ + Container: container, + dbName: req.Env["POSTGRES_DB"], + password: req.Env["POSTGRES_PASSWORD"], + user: req.Env["POSTGRES_USER"], + sqlDriverName: settings.SQLDriverName, + } } - user := req.Env["POSTGRES_USER"] - password := req.Env["POSTGRES_PASSWORD"] - dbName := req.Env["POSTGRES_DB"] + if err != nil { + return c, fmt.Errorf("generic container: %w", err) + } - return &PostgresContainer{Container: container, dbName: dbName, password: password, user: user, sqlDriverName: settings.SQLDriverName}, nil + return c, nil } type snapshotConfig struct { diff --git a/modules/postgres/postgres_test.go b/modules/postgres/postgres_test.go index adef0defe39..825a5bdc6f2 100644 --- a/modules/postgres/postgres_test.go +++ b/modules/postgres/postgres_test.go @@ -3,7 +3,6 @@ package postgres_test import ( "context" "database/sql" - "errors" "fmt" "path/filepath" "testing" @@ -13,7 +12,6 @@ import ( "github.com/jackc/pgx/v5" _ "github.com/jackc/pgx/v5/stdlib" _ "github.com/lib/pq" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" @@ -60,53 +58,45 @@ func TestPostgres(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - container, err := postgres.Run(ctx, + ctr, err := postgres.Run(ctx, tt.image, postgres.WithDatabase(dbname), postgres.WithUsername(user), postgres.WithPassword(password), postgres.BasicWaitStrategies(), ) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // connectionString { // explicitly set sslmode=disable because the container is not configured to use TLS - connStr, err := container.ConnectionString(ctx, "sslmode=disable", "application_name=test") + connStr, err := ctr.ConnectionString(ctx, "sslmode=disable", "application_name=test") // } require.NoError(t, err) - mustConnStr := container.MustConnectionString(ctx, "sslmode=disable", "application_name=test") + mustConnStr := ctr.MustConnectionString(ctx, "sslmode=disable", "application_name=test") if mustConnStr != connStr { t.Errorf("ConnectionString was not equal to MustConnectionString") } // Ensure connection string is using generic format - id, err := container.MappedPort(ctx, "5432/tcp") + id, err := ctr.MappedPort(ctx, "5432/tcp") require.NoError(t, err) - assert.Equal(t, fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable&application_name=test", user, password, "localhost", id.Port(), dbname), connStr) + require.Equal(t, fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable&application_name=test", user, password, "localhost", id.Port(), dbname), connStr) // perform assertions db, err := sql.Open("postgres", connStr) require.NoError(t, err) - assert.NotNil(t, db) + require.NotNil(t, db) defer db.Close() result, err := db.Exec("CREATE TABLE IF NOT EXISTS test (id int, name varchar(255));") require.NoError(t, err) - assert.NotNil(t, result) + require.NotNil(t, result) result, err = db.Exec("INSERT INTO test (id, name) VALUES (1, 'test');") require.NoError(t, err) - assert.NotNil(t, result) + require.NotNil(t, result) }) } } @@ -120,7 +110,7 @@ func TestContainerWithWaitForSQL(t *testing.T) { } t.Run("default query", func(t *testing.T) { - container, err := postgres.Run( + ctr, err := postgres.Run( ctx, "docker.io/postgres:16-alpine", postgres.WithDatabase(dbname), @@ -128,11 +118,12 @@ func TestContainerWithWaitForSQL(t *testing.T) { postgres.WithPassword(password), testcontainers.WithWaitStrategy(wait.ForSQL(nat.Port(port), "postgres", dbURL)), ) + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - require.NotNil(t, container) + require.NotNil(t, ctr) }) t.Run("custom query", func(t *testing.T) { - container, err := postgres.Run( + ctr, err := postgres.Run( ctx, "docker.io/postgres:16-alpine", postgres.WithDatabase(dbname), @@ -140,11 +131,12 @@ func TestContainerWithWaitForSQL(t *testing.T) { postgres.WithPassword(password), testcontainers.WithWaitStrategy(wait.ForSQL(nat.Port(port), "postgres", dbURL).WithStartupTimeout(time.Second*5).WithQuery("SELECT 10")), ) + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - require.NotNil(t, container) + require.NotNil(t, ctr) }) t.Run("custom bad query", func(t *testing.T) { - container, err := postgres.Run( + ctr, err := postgres.Run( ctx, "docker.io/postgres:16-alpine", postgres.WithDatabase(dbname), @@ -152,15 +144,15 @@ func TestContainerWithWaitForSQL(t *testing.T) { postgres.WithPassword(password), testcontainers.WithWaitStrategy(wait.ForSQL(nat.Port(port), "postgres", dbURL).WithStartupTimeout(time.Second*5).WithQuery("SELECT 'a' from b")), ) + testcontainers.CleanupContainer(t, ctr) require.Error(t, err) - require.Nil(t, container) }) } func TestWithConfigFile(t *testing.T) { ctx := context.Background() - container, err := postgres.Run(ctx, + ctr, err := postgres.Run(ctx, "docker.io/postgres:16-alpine", postgres.WithConfigFile(filepath.Join("testdata", "my-postgres.conf")), postgres.WithDatabase(dbname), @@ -168,30 +160,23 @@ func TestWithConfigFile(t *testing.T) { postgres.WithPassword(password), postgres.BasicWaitStrategies(), ) - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // explicitly set sslmode=disable because the container is not configured to use TLS - connStr, err := container.ConnectionString(ctx, "sslmode=disable") + connStr, err := ctr.ConnectionString(ctx, "sslmode=disable") require.NoError(t, err) db, err := sql.Open("postgres", connStr) require.NoError(t, err) - assert.NotNil(t, db) + require.NotNil(t, db) defer db.Close() } func TestWithInitScript(t *testing.T) { ctx := context.Background() - container, err := postgres.Run(ctx, + ctr, err := postgres.Run(ctx, "docker.io/postgres:15.2-alpine", postgres.WithInitScripts(filepath.Join("testdata", "init-user-db.sh")), postgres.WithDatabase(dbname), @@ -199,37 +184,30 @@ func TestWithInitScript(t *testing.T) { postgres.WithPassword(password), postgres.BasicWaitStrategies(), ) - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // explicitly set sslmode=disable because the container is not configured to use TLS - connStr, err := container.ConnectionString(ctx, "sslmode=disable") + connStr, err := ctr.ConnectionString(ctx, "sslmode=disable") require.NoError(t, err) db, err := sql.Open("postgres", connStr) require.NoError(t, err) - assert.NotNil(t, db) + require.NotNil(t, db) defer db.Close() // database created in init script. See testdata/init-user-db.sh result, err := db.Exec("SELECT * FROM testdb;") require.NoError(t, err) - assert.NotNil(t, result) + require.NotNil(t, result) } func TestSnapshot(t *testing.T) { // snapshotAndReset { ctx := context.Background() - // 1. Start the postgres container and run any migrations on it - container, err := postgres.Run( + // 1. Start the postgres ctr and run any migrations on it + ctr, err := postgres.Run( ctx, "docker.io/postgres:16-alpine", postgres.WithDatabase(dbname), @@ -238,90 +216,58 @@ func TestSnapshot(t *testing.T) { postgres.BasicWaitStrategies(), postgres.WithSQLDriver("pgx"), ) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // Run any migrations on the database - _, _, err = container.Exec(ctx, []string{"psql", "-U", user, "-d", dbname, "-c", "CREATE TABLE users (id SERIAL, name TEXT NOT NULL, age INT NOT NULL)"}) - if err != nil { - t.Fatal(err) - } + _, _, err = ctr.Exec(ctx, []string{"psql", "-U", user, "-d", dbname, "-c", "CREATE TABLE users (id SERIAL, name TEXT NOT NULL, age INT NOT NULL)"}) + require.NoError(t, err) // 2. Create a snapshot of the database to restore later - err = container.Snapshot(ctx, postgres.WithSnapshotName("test-snapshot")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + err = ctr.Snapshot(ctx, postgres.WithSnapshotName("test-snapshot")) + require.NoError(t, err) - dbURL, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } + dbURL, err := ctr.ConnectionString(ctx) + require.NoError(t, err) t.Run("Test inserting a user", func(t *testing.T) { t.Cleanup(func() { // 3. In each test, reset the DB to its snapshot state. - err = container.Restore(ctx) - if err != nil { - t.Fatal(err) - } + err = ctr.Restore(ctx) + require.NoError(t, err) }) conn, err := pgx.Connect(context.Background(), dbURL) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer conn.Close(context.Background()) _, err = conn.Exec(ctx, "INSERT INTO users(name, age) VALUES ($1, $2)", "test", 42) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) var name string var age int64 err = conn.QueryRow(context.Background(), "SELECT name, age FROM users LIMIT 1").Scan(&name, &age) - if err != nil { - t.Fatal(err) - } - - if name != "test" { - t.Fatalf("Expected %s to equal `test`", name) - } - if age != 42 { - t.Fatalf("Expected %d to equal `42`", age) - } + require.NoError(t, err) + + require.Equal(t, "test", name) + require.EqualValues(t, 42, age) }) // 4. Run as many tests as you need, they will each get a clean database t.Run("Test querying empty DB", func(t *testing.T) { t.Cleanup(func() { - err = container.Restore(ctx) - if err != nil { - t.Fatal(err) - } + err = ctr.Restore(ctx) + require.NoError(t, err) }) conn, err := pgx.Connect(context.Background(), dbURL) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer conn.Close(context.Background()) var name string var age int64 err = conn.QueryRow(context.Background(), "SELECT name, age FROM users LIMIT 1").Scan(&name, &age) - if !errors.Is(err, pgx.ErrNoRows) { - t.Fatalf("Expected error to be a NoRows error, since the DB should be empty on every test. Got %s instead", err) - } + require.ErrorIs(t, err, pgx.ErrNoRows) }) // } } @@ -333,7 +279,7 @@ func TestSnapshotWithOverrides(t *testing.T) { user := "other-user" password := "other-password" - container, err := postgres.Run( + ctr, err := postgres.Run( ctx, "docker.io/postgres:16-alpine", postgres.WithDatabase(dbname), @@ -341,58 +287,34 @@ func TestSnapshotWithOverrides(t *testing.T) { postgres.WithPassword(password), postgres.BasicWaitStrategies(), ) - if err != nil { - t.Fatal(err) - } - - _, _, err = container.Exec(ctx, []string{"psql", "-U", user, "-d", dbname, "-c", "CREATE TABLE users (id SERIAL, name TEXT NOT NULL, age INT NOT NULL)"}) - if err != nil { - t.Fatal(err) - } - - err = container.Snapshot(ctx, postgres.WithSnapshotName("other-snapshot")) - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + _, _, err = ctr.Exec(ctx, []string{"psql", "-U", user, "-d", dbname, "-c", "CREATE TABLE users (id SERIAL, name TEXT NOT NULL, age INT NOT NULL)"}) + require.NoError(t, err) + err = ctr.Snapshot(ctx, postgres.WithSnapshotName("other-snapshot")) + require.NoError(t, err) - dbURL, err := container.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } + dbURL, err := ctr.ConnectionString(ctx) + require.NoError(t, err) t.Run("Test that the restore works when not using defaults", func(t *testing.T) { - _, _, err = container.Exec(ctx, []string{"psql", "-U", user, "-d", dbname, "-c", "INSERT INTO users(name, age) VALUES ('test', 42)"}) - if err != nil { - t.Fatal(err) - } + _, _, err = ctr.Exec(ctx, []string{"psql", "-U", user, "-d", dbname, "-c", "INSERT INTO users(name, age) VALUES ('test', 42)"}) + require.NoError(t, err) // Doing the restore before we connect since this resets the pgx connection - err = container.Restore(ctx) - if err != nil { - t.Fatal(err) - } + err = ctr.Restore(ctx) + require.NoError(t, err) conn, err := pgx.Connect(context.Background(), dbURL) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer conn.Close(context.Background()) var count int64 err = conn.QueryRow(context.Background(), "SELECT COUNT(1) FROM users").Scan(&count) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if count != 0 { - t.Fatalf("Expected %d to equal `0`", count) - } + require.Zero(t, count) }) } @@ -413,90 +335,58 @@ func TestSnapshotWithDockerExecFallback(t *testing.T) { postgres.WithSQLDriver("DoesNotExist"), ) // } - if err != nil { - t.Fatal(err) - } + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // Run any migrations on the database _, _, err = ctr.Exec(ctx, []string{"psql", "-U", user, "-d", dbname, "-c", "CREATE TABLE users (id SERIAL, name TEXT NOT NULL, age INT NOT NULL)"}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // 2. Create a snapshot of the database to restore later err = ctr.Snapshot(ctx, postgres.WithSnapshotName("test-snapshot")) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := ctr.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + require.NoError(t, err) dbURL, err := ctr.ConnectionString(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) t.Run("Test inserting a user", func(t *testing.T) { t.Cleanup(func() { // 3. In each test, reset the DB to its snapshot state. err := ctr.Restore(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) }) conn, err2 := pgx.Connect(context.Background(), dbURL) - if err2 != nil { - t.Fatal(err2) - } + require.NoError(t, err2) defer conn.Close(context.Background()) _, err2 = conn.Exec(ctx, "INSERT INTO users(name, age) VALUES ($1, $2)", "test", 42) - if err2 != nil { - t.Fatal(err2) - } + require.NoError(t, err2) var name string var age int64 err2 = conn.QueryRow(context.Background(), "SELECT name, age FROM users LIMIT 1").Scan(&name, &age) - if err2 != nil { - t.Fatal(err2) - } - - if name != "test" { - t.Fatalf("Expected %s to equal `test`", name) - } - if age != 42 { - t.Fatalf("Expected %d to equal `42`", age) - } + require.NoError(t, err2) + + require.Equal(t, "test", name) + require.EqualValues(t, 42, age) }) t.Run("Test querying empty DB", func(t *testing.T) { // 4. Run as many tests as you need, they will each get a clean database t.Cleanup(func() { err := ctr.Restore(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) }) conn, err2 := pgx.Connect(context.Background(), dbURL) - if err2 != nil { - t.Fatal(err2) - } + require.NoError(t, err2) defer conn.Close(context.Background()) var name string var age int64 err2 = conn.QueryRow(context.Background(), "SELECT name, age FROM users LIMIT 1").Scan(&name, &age) - if !errors.Is(err2, pgx.ErrNoRows) { - t.Fatalf("Expected error to be a NoRows error, since the DB should be empty on every test. Got %s instead", err2) - } + require.ErrorIs(t, err2, pgx.ErrNoRows) }) // } } diff --git a/modules/pulsar/examples_test.go b/modules/pulsar/examples_test.go index 2ee1f3c38e6..ad871f14946 100644 --- a/modules/pulsar/examples_test.go +++ b/modules/pulsar/examples_test.go @@ -5,6 +5,7 @@ import ( "fmt" "log" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/pulsar" ) @@ -13,21 +14,21 @@ func ExampleRun() { ctx := context.Background() pulsarContainer, err := pulsar.Run(ctx, "docker.io/apachepulsar/pulsar:2.10.2") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := pulsarContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(pulsarContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := pulsarContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/pulsar/pulsar.go b/modules/pulsar/pulsar.go index c1c5c945171..26c57b57428 100644 --- a/modules/pulsar/pulsar.go +++ b/modules/pulsar/pulsar.go @@ -164,14 +164,15 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } } - c, err := testcontainers.GenericContainer(ctx, genericContainerReq) - if err != nil { - return nil, err + container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *Container + if container != nil { + c = &Container{Container: container} } - pc := &Container{ - Container: c, + if err != nil { + return c, fmt.Errorf("generic container: %w", err) } - return pc, nil + return c, nil } diff --git a/modules/pulsar/pulsar_test.go b/modules/pulsar/pulsar_test.go index 6d911bf9e5f..cd042849138 100644 --- a/modules/pulsar/pulsar_test.go +++ b/modules/pulsar/pulsar_test.go @@ -3,7 +3,6 @@ package pulsar_test import ( "context" "encoding/json" - "fmt" "io" "net/http" "strings" @@ -11,6 +10,7 @@ import ( "time" "github.com/apache/pulsar-client-go/pulsar" + "github.com/apache/pulsar-client-go/pulsar/log" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" "github.com/stretchr/testify/assert" @@ -21,12 +21,20 @@ import ( tcnetwork "github.com/testcontainers/testcontainers-go/network" ) +// noopLogConsumer implements testcontainers.LogConsumer +// and does nothing with the logs. +type noopLogConsumer struct{} + +// Accept implements testcontainers.LogConsumer. +func (*noopLogConsumer) Accept(testcontainers.Log) {} + func TestPulsar(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() nw, err := tcnetwork.New(ctx) require.NoError(t, err) + testcontainers.CleanupNetwork(t, nw) nwName := nw.Name @@ -80,7 +88,7 @@ func TestPulsar(t *testing.T) { name: "with log consumers", opts: []testcontainers.ContainerCustomizer{ // withLogconsumers { - testcontainers.WithLogConsumers(&testcontainers.StdoutLogConsumer{}), + testcontainers.WithLogConsumers(&noopLogConsumer{}), // } }, }, @@ -93,11 +101,8 @@ func TestPulsar(t *testing.T) { "docker.io/apachepulsar/pulsar:2.10.2", tt.opts..., ) + testcontainers.CleanupContainer(t, c) require.NoError(t, err) - defer func() { - err := c.Terminate(ctx) - require.NoError(t, err) - }() // getBrokerURL { brokerURL, err := c.BrokerURL(ctx) @@ -116,6 +121,7 @@ func TestPulsar(t *testing.T) { URL: brokerURL, OperationTimeout: 30 * time.Second, ConnectionTimeout: 30 * time.Second, + Logger: log.DefaultNopLogger(), }) require.NoError(t, err) t.Cleanup(func() { pc.Close() }) @@ -134,13 +140,13 @@ func TestPulsar(t *testing.T) { go func() { msg, err := consumer.Receive(ctx) if err != nil { - fmt.Println("failed to receive message", err) + t.Log("failed to receive message", err) return } msgChan <- msg.Payload() err = consumer.Ack(msg) if err != nil { - fmt.Println("failed to send ack", err) + t.Log("failed to send ack", err) return } }() @@ -188,14 +194,7 @@ func TestPulsar(t *testing.T) { // check that the subscription exists _, ok := subscriptionsMap[subscriptionName] - assert.True(t, ok) + require.True(t, ok) }) } - - // remove the network after the last, so that all containers are already removed - // and there are no active endpoints on the network - t.Cleanup(func() { - err := nw.Remove(context.Background()) - require.NoError(t, err) - }) } diff --git a/modules/qdrant/examples_test.go b/modules/qdrant/examples_test.go index eca3adb4ad9..85bd2763de7 100644 --- a/modules/qdrant/examples_test.go +++ b/modules/qdrant/examples_test.go @@ -10,6 +10,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/qdrant" ) @@ -18,21 +19,21 @@ func ExampleRun() { ctx := context.Background() qdrantContainer, err := qdrant.Run(ctx, "qdrant/qdrant:v1.7.4") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := qdrantContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(qdrantContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := qdrantContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -44,25 +45,27 @@ func ExampleRun() { func ExampleRun_createPoints() { // fullExample { qdrantContainer, err := qdrant.Run(context.Background(), "qdrant/qdrant:v1.7.4") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := qdrantContainer.Terminate(context.Background()); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(qdrantContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } grpcEndpoint, err := qdrantContainer.GRPCEndpoint(context.Background()) if err != nil { - log.Fatalf("failed to get gRPC endpoint: %s", err) // nolint:gocritic + log.Printf("failed to get gRPC endpoint: %s", err) + return } // Set up a connection to the server. conn, err := grpc.NewClient(grpcEndpoint, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { - log.Fatalf("did not connect: %v", err) + log.Printf("did not connect: %v", err) + return } defer conn.Close() @@ -89,7 +92,8 @@ func ExampleRun_createPoints() { }, }) if err != nil { - log.Fatalf("Could not create collection: %v", err) + log.Printf("Could not create collection: %v", err) + return } // 2. Contact the server and print out its response. @@ -97,7 +101,8 @@ func ExampleRun_createPoints() { defer cancel() r, err := collections_client.List(ctx, &pb.ListCollectionsRequest{}) if err != nil { - log.Fatalf("could not get collections: %v", err) + log.Printf("could not get collections: %v", err) + return } fmt.Printf("List of collections: %s\n", r.GetCollections()) @@ -113,7 +118,8 @@ func ExampleRun_createPoints() { FieldType: &fieldIndex1Type, }) if err != nil { - log.Fatalf("Could not create field index: %v", err) + log.Printf("Could not create field index: %v", err) + return } // 5. Create integer field index @@ -125,7 +131,8 @@ func ExampleRun_createPoints() { FieldType: &fieldIndex2Type, }) if err != nil { - log.Fatalf("Could not create field index: %v", err) + log.Printf("Could not create field index: %v", err) + return } // 6. Upsert points @@ -258,7 +265,8 @@ func ExampleRun_createPoints() { Points: upsertPoints, }) if err != nil { - log.Fatalf("Could not upsert points: %v", err) + log.Printf("Could not upsert points: %v", err) + return } // 7. Retrieve points by ids @@ -270,7 +278,8 @@ func ExampleRun_createPoints() { }, }) if err != nil { - log.Fatalf("Could not retrieve points: %v", err) + log.Printf("Could not retrieve points: %v", err) + return } fmt.Printf("Retrieved points: %d\n", len(pointsById.GetResult())) @@ -285,7 +294,8 @@ func ExampleRun_createPoints() { WithPayload: &pb.WithPayloadSelector{SelectorOptions: &pb.WithPayloadSelector_Enable{Enable: true}}, }) if err != nil { - log.Fatalf("Could not search points: %v", err) + log.Printf("Could not search points: %v", err) + return } fmt.Printf("Found points: %d\n", len(unfilteredSearchResult.GetResult())) @@ -313,7 +323,8 @@ func ExampleRun_createPoints() { }, }) if err != nil { - log.Fatalf("Could not search points: %v", err) + log.Printf("Could not search points: %v", err) + return } // } diff --git a/modules/qdrant/go.mod b/modules/qdrant/go.mod index 85db7229b35..05c20884eef 100644 --- a/modules/qdrant/go.mod +++ b/modules/qdrant/go.mod @@ -4,7 +4,8 @@ go 1.22 require ( github.com/qdrant/go-client v1.7.0 - github.com/testcontainers/testcontainers-go v0.33.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 google.golang.org/grpc v1.64.1 ) @@ -17,6 +18,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect @@ -28,6 +30,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.4 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -39,6 +42,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -56,6 +60,7 @@ require ( golang.org/x/text v0.16.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/qdrant/go.sum b/modules/qdrant/go.sum index c38866d982d..b33ba47727b 100644 --- a/modules/qdrant/go.sum +++ b/modules/qdrant/go.sum @@ -16,6 +16,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -52,6 +53,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -80,6 +85,8 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/qdrant/go-client v1.7.0 h1:2TeeWyZAWIup7vvD7Ne6aAvo0H+F5OUb1pB9Z8Y4pFk= github.com/qdrant/go-client v1.7.0/go.mod h1:680gkxNAsVtre0Z8hAQmtPzJtz1xFAyCu2TUxULtnoE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -177,6 +184,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/qdrant/qdrant.go b/modules/qdrant/qdrant.go index e934403f96b..5a2b6d365f5 100644 --- a/modules/qdrant/qdrant.go +++ b/modules/qdrant/qdrant.go @@ -43,11 +43,16 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *QdrantContainer + if container != nil { + c = &QdrantContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &QdrantContainer{Container: container}, nil + return c, nil } // RESTEndpoint returns the REST endpoint of the Qdrant container diff --git a/modules/qdrant/qdrant_test.go b/modules/qdrant/qdrant_test.go index 2b9bcdbb758..63b95d30ea8 100644 --- a/modules/qdrant/qdrant_test.go +++ b/modules/qdrant/qdrant_test.go @@ -5,79 +5,57 @@ import ( "net/http" "testing" + "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/qdrant" ) func TestQdrant(t *testing.T) { ctx := context.Background() - container, err := qdrant.Run(ctx, "qdrant/qdrant:v1.7.4") - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := qdrant.Run(ctx, "qdrant/qdrant:v1.7.4") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) t.Run("REST Endpoint", func(tt *testing.T) { // restEndpoint { - restEndpoint, err := container.RESTEndpoint(ctx) + restEndpoint, err := ctr.RESTEndpoint(ctx) // } - if err != nil { - tt.Fatalf("failed to get REST endpoint: %s", err) - } + require.NoError(t, err) cli := &http.Client{} resp, err := cli.Get(restEndpoint) - if err != nil { - tt.Fatalf("failed to perform GET request: %s", err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - tt.Fatalf("unexpected status code: %d", resp.StatusCode) - } + require.Equal(t, http.StatusOK, resp.StatusCode) }) t.Run("gRPC Endpoint", func(tt *testing.T) { // gRPCEndpoint { - grpcEndpoint, err := container.GRPCEndpoint(ctx) + grpcEndpoint, err := ctr.GRPCEndpoint(ctx) // } - if err != nil { - tt.Fatalf("failed to get REST endpoint: %s", err) - } + require.NoError(t, err) conn, err := grpc.NewClient(grpcEndpoint, grpc.WithTransportCredentials(insecure.NewCredentials())) - if err != nil { - t.Fatalf("did not connect: %v", err) - } + require.NoError(t, err) defer conn.Close() }) t.Run("Web UI", func(tt *testing.T) { // webUIEndpoint { - webUI, err := container.WebUI(ctx) + webUI, err := ctr.WebUI(ctx) // } - if err != nil { - tt.Fatalf("failed to get REST endpoint: %s", err) - } + require.NoError(t, err) cli := &http.Client{} resp, err := cli.Get(webUI) - if err != nil { - tt.Fatalf("failed to perform GET request: %s", err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - tt.Fatalf("unexpected status code: %d", resp.StatusCode) - } + require.Equal(t, http.StatusOK, resp.StatusCode) }) } diff --git a/modules/rabbitmq/examples_test.go b/modules/rabbitmq/examples_test.go index 4471d512d47..bc6a8494569 100644 --- a/modules/rabbitmq/examples_test.go +++ b/modules/rabbitmq/examples_test.go @@ -24,21 +24,21 @@ func ExampleRun() { rabbitmq.WithAdminUsername("admin"), rabbitmq.WithAdminPassword("password"), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := rabbitmqContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(rabbitmqContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := rabbitmqContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -55,28 +55,31 @@ func ExampleRun_connectUsingAmqp() { rabbitmq.WithAdminUsername("admin"), rabbitmq.WithAdminPassword("password"), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } defer func() { - if err := rabbitmqContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(rabbitmqContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } amqpURL, err := rabbitmqContainer.AmqpURL(ctx) if err != nil { - log.Fatalf("failed to get AMQP URL: %s", err) // nolint:gocritic + log.Printf("failed to get AMQP URL: %s", err) + return } amqpConnection, err := amqp.Dial(amqpURL) if err != nil { - log.Fatalf("failed to connect to RabbitMQ: %s", err) // nolint:gocritic + log.Printf("failed to connect to RabbitMQ: %s", err) + return } defer func() { err := amqpConnection.Close() if err != nil { - log.Fatalf("failed to close connection: %s", err) // nolint:gocritic + log.Printf("failed to close connection: %s", err) } }() @@ -93,7 +96,8 @@ func ExampleRun_withSSL() { tmpDir := os.TempDir() certDirs := tmpDir + "/rabbitmq" if err := os.MkdirAll(certDirs, 0o755); err != nil { - log.Fatalf("failed to create temporary directory: %s", err) + log.Printf("failed to create temporary directory: %s", err) + return } defer os.RemoveAll(certDirs) @@ -105,7 +109,8 @@ func ExampleRun_withSSL() { ParentDir: certDirs, }) if caCert == nil { - log.Fatal("failed to generate CA certificate") // nolint:gocritic + log.Print("failed to generate CA certificate") + return } cert := tlscert.SelfSignedFromRequest(tlscert.Request{ @@ -116,7 +121,8 @@ func ExampleRun_withSSL() { ParentDir: certDirs, }) if cert == nil { - log.Fatal("failed to generate certificate") // nolint:gocritic + log.Print("failed to generate certificate") + return } sslSettings := rabbitmq.SSLSettings{ @@ -132,20 +138,21 @@ func ExampleRun_withSSL() { "rabbitmq:3.7.25-management-alpine", rabbitmq.WithSSL(sslSettings), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) // nolint:gocritic - } - // } - defer func() { - if err := rabbitmqContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(rabbitmqContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + // } state, err := rabbitmqContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -166,17 +173,22 @@ func ExampleRun_withPlugins() { testcontainers.NewRawCommand([]string{"rabbitmq_random_exchange"}), ), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := rabbitmqContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(rabbitmqContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + + if err = assertPlugins(rabbitmqContainer, "rabbitmq_shovel", "rabbitmq_random_exchange"); err != nil { + log.Printf("failed to find plugin: %s", err) + return + } - fmt.Println(assertPlugins(rabbitmqContainer, "rabbitmq_shovel", "rabbitmq_random_exchange")) + fmt.Println(true) // Output: // true @@ -188,24 +200,26 @@ func ExampleRun_withCustomConfigFile() { rabbitmqContainer, err := rabbitmq.Run(ctx, "rabbitmq:3.7.25-management-alpine", ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := rabbitmqContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(rabbitmqContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } logs, err := rabbitmqContainer.Logs(ctx) if err != nil { - log.Fatalf("failed to get logs: %s", err) // nolint:gocritic + log.Printf("failed to get logs: %s", err) + return } bytes, err := io.ReadAll(logs) if err != nil { - log.Fatalf("failed to read logs: %s", err) // nolint:gocritic + log.Printf("failed to read logs: %s", err) + return } fmt.Println(strings.Contains(string(bytes), "config file(s) : /etc/rabbitmq/rabbitmq-testcontainers.conf")) @@ -214,25 +228,24 @@ func ExampleRun_withCustomConfigFile() { // true } -func assertPlugins(container testcontainers.Container, plugins ...string) bool { +func assertPlugins(container testcontainers.Container, plugins ...string) error { ctx := context.Background() for _, plugin := range plugins { - _, out, err := container.Exec(ctx, []string{"rabbitmq-plugins", "is_enabled", plugin}) if err != nil { - log.Fatalf("failed to execute command: %s", err) + return fmt.Errorf("failed to execute command: %w", err) } check, err := io.ReadAll(out) if err != nil { - log.Fatalf("failed to read output: %s", err) + return fmt.Errorf("failed to read output: %w", err) } if !strings.Contains(string(check), plugin+" is enabled") { - return false + return fmt.Errorf("plugin %q is not enabled", plugin) } } - return true + return nil } diff --git a/modules/rabbitmq/go.mod b/modules/rabbitmq/go.mod index e9d4a267fa2..46f915a46db 100644 --- a/modules/rabbitmq/go.mod +++ b/modules/rabbitmq/go.mod @@ -5,15 +5,8 @@ go 1.22 require ( github.com/docker/go-connections v0.5.0 github.com/rabbitmq/amqp091-go v1.9.0 - github.com/testcontainers/testcontainers-go v0.33.0 -) - -require ( - github.com/containerd/platforms v0.2.1 // indirect - github.com/moby/docker-image-spec v1.3.1 // indirect - golang.org/x/crypto v0.24.0 // indirect - golang.org/x/net v0.26.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 ) require ( @@ -23,7 +16,9 @@ require ( github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/containerd/containerd v1.7.18 // indirect github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect @@ -37,6 +32,7 @@ require ( github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mdelapenya/tlscert v0.1.0 + github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/user v0.1.0 // indirect @@ -45,6 +41,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -56,8 +53,12 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect + golang.org/x/crypto v0.24.0 // indirect + golang.org/x/net v0.26.0 // indirect golang.org/x/sys v0.21.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/rabbitmq/go.sum b/modules/rabbitmq/go.sum index bf809122dab..5994d5e5b7c 100644 --- a/modules/rabbitmq/go.sum +++ b/modules/rabbitmq/go.sum @@ -53,7 +53,10 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= @@ -85,6 +88,8 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo= github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -184,6 +189,8 @@ google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGm google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/rabbitmq/rabbitmq.go b/modules/rabbitmq/rabbitmq.go index bcc1a849d7c..32d18c09a94 100644 --- a/modules/rabbitmq/rabbitmq.go +++ b/modules/rabbitmq/rabbitmq.go @@ -133,14 +133,17 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) - if err != nil { - return nil, err + var c *RabbitMQContainer + if container != nil { + c = &RabbitMQContainer{ + Container: container, + AdminUsername: settings.AdminUsername, + AdminPassword: settings.AdminPassword, + } } - c := &RabbitMQContainer{ - Container: container, - AdminUsername: settings.AdminUsername, - AdminPassword: settings.AdminPassword, + if err != nil { + return c, fmt.Errorf("generic container: %w", err) } return c, nil diff --git a/modules/rabbitmq/rabbitmq_test.go b/modules/rabbitmq/rabbitmq_test.go index 9b024d7b5a2..f0b82ca2db1 100644 --- a/modules/rabbitmq/rabbitmq_test.go +++ b/modules/rabbitmq/rabbitmq_test.go @@ -12,6 +12,7 @@ import ( "github.com/mdelapenya/tlscert" amqp "github.com/rabbitmq/amqp091-go" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/rabbitmq" @@ -21,29 +22,17 @@ func TestRunContainer_connectUsingAmqp(t *testing.T) { ctx := context.Background() rabbitmqContainer, err := rabbitmq.Run(ctx, "rabbitmq:3.12.11-management-alpine") - if err != nil { - t.Fatal(err) - } - - defer func() { - if err := rabbitmqContainer.Terminate(ctx); err != nil { - t.Fatal(err) - } - }() + testcontainers.CleanupContainer(t, rabbitmqContainer) + require.NoError(t, err) amqpURL, err := rabbitmqContainer.AmqpURL(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) amqpConnection, err := amqp.Dial(amqpURL) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if err = amqpConnection.Close(); err != nil { - t.Fatal(err) - } + err = amqpConnection.Close() + require.NoError(t, err) } func TestRunContainer_connectUsingAmqps(t *testing.T) { @@ -82,20 +71,11 @@ func TestRunContainer_connectUsingAmqps(t *testing.T) { } rabbitmqContainer, err := rabbitmq.Run(ctx, "rabbitmq:3.12.11-management-alpine", rabbitmq.WithSSL(sslSettings)) - if err != nil { - t.Fatal(err) - } - - defer func() { - if err := rabbitmqContainer.Terminate(ctx); err != nil { - t.Fatal(err) - } - }() + testcontainers.CleanupContainer(t, rabbitmqContainer) + require.NoError(t, err) amqpsURL, err := rabbitmqContainer.AmqpsURL(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) if !strings.HasPrefix(amqpsURL, "amqps") { t.Fatal(fmt.Errorf("AMQPS Url should begin with `amqps`")) @@ -104,15 +84,11 @@ func TestRunContainer_connectUsingAmqps(t *testing.T) { certs := x509.NewCertPool() pemData, err := os.ReadFile(sslSettings.CACertFile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) certs.AppendCertsFromPEM(pemData) amqpsConnection, err := amqp.DialTLS(amqpsURL, &tls.Config{InsecureSkipVerify: false, RootCAs: certs}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) if amqpsConnection.IsClosed() { t.Fatal(fmt.Errorf("AMQPS Connection unexpectdely closed")) @@ -238,15 +214,8 @@ func TestRunContainer_withAllSettings(t *testing.T) { testcontainers.WithAfterReadyCommand(Plugin{Name: "rabbitmq_shovel"}, Plugin{Name: "rabbitmq_random_exchange"}), // } ) - if err != nil { - t.Fatal(err) - } - - defer func() { - if err := rabbitmqContainer.Terminate(ctx); err != nil { - t.Fatal(err) - } - }() + testcontainers.CleanupContainer(t, rabbitmqContainer) + require.NoError(t, err) if !assertEntity(t, rabbitmqContainer, "queues", "queue1", "queue2", "queue3", "queue4") { t.Fatal(err) diff --git a/modules/redis/examples_test.go b/modules/redis/examples_test.go index 9fb7c2cf113..e5e83a01a1b 100644 --- a/modules/redis/examples_test.go +++ b/modules/redis/examples_test.go @@ -6,6 +6,7 @@ import ( "log" "path/filepath" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/redis" ) @@ -19,21 +20,21 @@ func ExampleRun() { redis.WithLogLevel(redis.LogLevelVerbose), redis.WithConfigFile(filepath.Join("testdata", "redis7.conf")), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := redisContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(redisContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := redisContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/redis/redis.go b/modules/redis/redis.go index 33ce8239945..df75ad73110 100644 --- a/modules/redis/redis.go +++ b/modules/redis/redis.go @@ -69,11 +69,16 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *RedisContainer + if container != nil { + c = &RedisContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &RedisContainer{Container: container}, nil + return c, nil } // WithConfigFile sets the config file to be used for the redis container, and sets the command to run the redis server diff --git a/modules/redis/redis_test.go b/modules/redis/redis_test.go index f98079685fe..0fd0a960f97 100644 --- a/modules/redis/redis_test.go +++ b/modules/redis/redis_test.go @@ -11,6 +11,7 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" tcredis "github.com/testcontainers/testcontainers-go/modules/redis" ) @@ -18,12 +19,8 @@ func TestIntegrationSetGet(t *testing.T) { ctx := context.Background() redisContainer, err := tcredis.Run(ctx, "docker.io/redis:7") + testcontainers.CleanupContainer(t, redisContainer) require.NoError(t, err) - t.Cleanup(func() { - if err := redisContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) assertSetsGets(t, ctx, redisContainer, 1) } @@ -32,12 +29,8 @@ func TestRedisWithConfigFile(t *testing.T) { ctx := context.Background() redisContainer, err := tcredis.Run(ctx, "docker.io/redis:7", tcredis.WithConfigFile(filepath.Join("testdata", "redis7.conf"))) + testcontainers.CleanupContainer(t, redisContainer) require.NoError(t, err) - t.Cleanup(func() { - if err := redisContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) assertSetsGets(t, ctx, redisContainer, 1) } @@ -74,12 +67,8 @@ func TestRedisWithImage(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { redisContainer, err := tcredis.Run(ctx, tt.image, tcredis.WithConfigFile(filepath.Join("testdata", "redis6.conf"))) + testcontainers.CleanupContainer(t, redisContainer) require.NoError(t, err) - t.Cleanup(func() { - if err := redisContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) assertSetsGets(t, ctx, redisContainer, 1) }) @@ -90,12 +79,8 @@ func TestRedisWithLogLevel(t *testing.T) { ctx := context.Background() redisContainer, err := tcredis.Run(ctx, "docker.io/redis:7", tcredis.WithLogLevel(tcredis.LogLevelVerbose)) + testcontainers.CleanupContainer(t, redisContainer) require.NoError(t, err) - t.Cleanup(func() { - if err := redisContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) assertSetsGets(t, ctx, redisContainer, 10) } @@ -104,12 +89,8 @@ func TestRedisWithSnapshotting(t *testing.T) { ctx := context.Background() redisContainer, err := tcredis.Run(ctx, "docker.io/redis:7", tcredis.WithSnapshotting(10, 1)) + testcontainers.CleanupContainer(t, redisContainer) require.NoError(t, err) - t.Cleanup(func() { - if err := redisContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) assertSetsGets(t, ctx, redisContainer, 10) } diff --git a/modules/redpanda/examples_test.go b/modules/redpanda/examples_test.go index 68cb3144183..69fb0c9d6a4 100644 --- a/modules/redpanda/examples_test.go +++ b/modules/redpanda/examples_test.go @@ -5,6 +5,7 @@ import ( "fmt" "log" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/redpanda" ) @@ -25,21 +26,21 @@ func ExampleRun() { redpanda.WithSuperusers("superuser-1", "superuser-2"), redpanda.WithEnableSchemaRegistryHTTPBasicAuth(), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := redpandaContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(redpandaContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := redpandaContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/redpanda/options.go b/modules/redpanda/options.go index ae15e330017..180edcc48f8 100644 --- a/modules/redpanda/options.go +++ b/modules/redpanda/options.go @@ -139,7 +139,7 @@ func WithTLS(cert, key []byte) Option { // WithListener adds a custom listener to the Redpanda containers. Listener // will be aliases to all networks, so they can be accessed from within docker -// networks. At leas one network must be attached to the container, if not an +// networks. At least one network must be attached to the container, if not an // error will be thrown when starting the container. func WithListener(lis string) Option { host, port, err := net.SplitHostPort(lis) diff --git a/modules/redpanda/redpanda.go b/modules/redpanda/redpanda.go index 3ed39320662..686d310d9f0 100644 --- a/modules/redpanda/redpanda.go +++ b/modules/redpanda/redpanda.go @@ -179,32 +179,36 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom ) } - container, err := testcontainers.GenericContainer(ctx, req) + ctr, err := testcontainers.GenericContainer(ctx, req) + var c *Container + if ctr != nil { + c = &Container{Container: ctr} + } if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } // 6. Get mapped port for the Kafka API, so that we can render and then mount // the Redpanda config with the advertised Kafka address. - hostIP, err := container.Host(ctx) + hostIP, err := ctr.Host(ctx) if err != nil { - return nil, fmt.Errorf("failed to get container host: %w", err) + return c, fmt.Errorf("failed to get container host: %w", err) } - kafkaPort, err := container.MappedPort(ctx, nat.Port(defaultKafkaAPIPort)) + kafkaPort, err := ctr.MappedPort(ctx, nat.Port(defaultKafkaAPIPort)) if err != nil { - return nil, fmt.Errorf("failed to get mapped Kafka port: %w", err) + return c, fmt.Errorf("failed to get mapped Kafka port: %w", err) } // 7. Render redpanda.yaml config and mount it. nodeConfig, err := renderNodeConfig(settings, hostIP, kafkaPort.Int()) if err != nil { - return nil, fmt.Errorf("failed to render node config: %w", err) + return c, fmt.Errorf("failed to render node config: %w", err) } - err = container.CopyToContainer(ctx, nodeConfig, filepath.Join(redpandaDir, "redpanda.yaml"), 600) + err = ctr.CopyToContainer(ctx, nodeConfig, filepath.Join(redpandaDir, "redpanda.yaml"), 600) if err != nil { - return nil, fmt.Errorf("failed to copy redpanda.yaml into container: %w", err) + return c, fmt.Errorf("failed to copy redpanda.yaml into container: %w", err) } // 8. Wait until Redpanda is ready to serve requests. @@ -213,29 +217,29 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom wait.ForListeningPort(defaultAdminAPIPort), wait.ForListeningPort(defaultSchemaRegistryPort), wait.ForLog("Successfully started Redpanda!"), - ).WaitUntilReady(ctx, container) + ).WaitUntilReady(ctx, ctr) if err != nil { - return nil, fmt.Errorf("failed to wait for Redpanda readiness: %w", err) + return c, fmt.Errorf("failed to wait for Redpanda readiness: %w", err) } - scheme := "http" + c.urlScheme = "http" if settings.EnableTLS { - scheme += "s" + c.urlScheme += "s" } // 9. Create Redpanda Service Accounts if configured to do so. if len(settings.ServiceAccounts) > 0 { - adminAPIPort, err := container.MappedPort(ctx, nat.Port(defaultAdminAPIPort)) + adminAPIPort, err := ctr.MappedPort(ctx, nat.Port(defaultAdminAPIPort)) if err != nil { - return nil, fmt.Errorf("failed to get mapped Admin API port: %w", err) + return c, fmt.Errorf("failed to get mapped Admin API port: %w", err) } - adminAPIUrl := fmt.Sprintf("%s://%v:%d", scheme, hostIP, adminAPIPort.Int()) + adminAPIUrl := fmt.Sprintf("%s://%v:%d", c.urlScheme, hostIP, adminAPIPort.Int()) adminCl := NewAdminAPIClient(adminAPIUrl) if settings.EnableTLS { cert, err := tls.X509KeyPair(settings.cert, settings.key) if err != nil { - return nil, fmt.Errorf("failed to create admin client with cert: %w", err) + return c, fmt.Errorf("failed to create admin client with cert: %w", err) } caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(settings.cert) @@ -254,12 +258,12 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom for username, password := range settings.ServiceAccounts { if err := adminCl.CreateUser(ctx, username, password); err != nil { - return nil, fmt.Errorf("failed to create service account with username %q: %w", username, err) + return c, fmt.Errorf("failed to create service account with username %q: %w", username, err) } } } - return &Container{Container: container, urlScheme: scheme}, nil + return c, nil } // KafkaSeedBroker returns the seed broker that should be used for connecting diff --git a/modules/redpanda/redpanda_test.go b/modules/redpanda/redpanda_test.go index 09bad2c0d06..09d391f7945 100644 --- a/modules/redpanda/redpanda_test.go +++ b/modules/redpanda/redpanda_test.go @@ -27,18 +27,12 @@ import ( func TestRedpanda(t *testing.T) { ctx := context.Background() - container, err := redpanda.Run(ctx, "docker.redpanda.com/redpandadata/redpanda:v23.3.3") + ctr, err := redpanda.Run(ctx, "docker.redpanda.com/redpandadata/redpanda:v23.3.3") + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - // Test Kafka API - seedBroker, err := container.KafkaSeedBroker(ctx) + seedBroker, err := ctr.KafkaSeedBroker(ctx) require.NoError(t, err) kafkaCl, err := kgo.NewClient( @@ -54,7 +48,7 @@ func TestRedpanda(t *testing.T) { // Test Schema Registry API httpCl := &http.Client{Timeout: 5 * time.Second} - schemaRegistryURL, err := container.SchemaRegistryAddress(ctx) + schemaRegistryURL, err := ctr.SchemaRegistryAddress(ctx) require.NoError(t, err) req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/subjects", schemaRegistryURL), nil) require.NoError(t, err) @@ -65,7 +59,7 @@ func TestRedpanda(t *testing.T) { // Test Admin API // adminAPIAddress { - adminAPIURL, err := container.AdminAPIAddress(ctx) + adminAPIURL, err := ctr.AdminAPIAddress(ctx) // } require.NoError(t, err) req, err = http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/v1/cluster/health_overview", adminAPIURL), nil) @@ -83,7 +77,7 @@ func TestRedpanda(t *testing.T) { func TestRedpandaWithAuthentication(t *testing.T) { ctx := context.Background() // redpandaCreateContainer { - container, err := redpanda.Run(ctx, + ctr, err := redpanda.Run(ctx, "docker.redpanda.com/redpandadata/redpanda:v23.3.3", redpanda.WithEnableSASL(), redpanda.WithEnableKafkaAuthorization(), @@ -94,18 +88,12 @@ func TestRedpandaWithAuthentication(t *testing.T) { redpanda.WithSuperusers("superuser-1", "superuser-2"), redpanda.WithEnableSchemaRegistryHTTPBasicAuth(), ) + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) // } - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - // kafkaSeedBroker { - seedBroker, err := container.KafkaSeedBroker(ctx) + seedBroker, err := ctr.KafkaSeedBroker(ctx) // } require.NoError(t, err) @@ -169,7 +157,7 @@ func TestRedpandaWithAuthentication(t *testing.T) { // Test Schema Registry API httpCl := &http.Client{Timeout: 5 * time.Second} // schemaRegistryAddress { - schemaRegistryURL, err := container.SchemaRegistryAddress(ctx) + schemaRegistryURL, err := ctr.SchemaRegistryAddress(ctx) // } require.NoError(t, err) @@ -178,7 +166,7 @@ func TestRedpandaWithAuthentication(t *testing.T) { require.NoError(t, err) resp, err := httpCl.Do(req) require.NoError(t, err) - assert.Equal(t, http.StatusUnauthorized, resp.StatusCode) + require.Equal(t, http.StatusUnauthorized, resp.StatusCode) resp.Body.Close() // Successful authentication @@ -186,7 +174,7 @@ func TestRedpandaWithAuthentication(t *testing.T) { req.SetBasicAuth(user, password) resp, err = httpCl.Do(req) require.NoError(t, err) - assert.Equal(t, http.StatusOK, resp.StatusCode) + require.Equal(t, http.StatusOK, resp.StatusCode) resp.Body.Close() } } @@ -195,7 +183,7 @@ func TestRedpandaWithOldVersionAndWasm(t *testing.T) { ctx := context.Background() // redpandaCreateContainer { // this would fail to start if we weren't ignoring wasm transforms for older versions - container, err := redpanda.Run(ctx, + ctr, err := redpanda.Run(ctx, "redpandadata/redpanda:v23.2.18", redpanda.WithEnableSASL(), redpanda.WithEnableKafkaAuthorization(), @@ -206,18 +194,12 @@ func TestRedpandaWithOldVersionAndWasm(t *testing.T) { redpanda.WithSuperusers("superuser-1", "superuser-2"), redpanda.WithEnableSchemaRegistryHTTPBasicAuth(), ) + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) // } - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - // kafkaSeedBroker { - seedBroker, err := container.KafkaSeedBroker(ctx) + seedBroker, err := ctr.KafkaSeedBroker(ctx) // } require.NoError(t, err) @@ -298,7 +280,7 @@ func TestRedpandaWithOldVersionAndWasm(t *testing.T) { // Test Schema Registry API httpCl := &http.Client{Timeout: 5 * time.Second} // schemaRegistryAddress { - schemaRegistryURL, err := container.SchemaRegistryAddress(ctx) + schemaRegistryURL, err := ctr.SchemaRegistryAddress(ctx) // } require.NoError(t, err) @@ -323,16 +305,11 @@ func TestRedpandaWithOldVersionAndWasm(t *testing.T) { func TestRedpandaProduceWithAutoCreateTopics(t *testing.T) { ctx := context.Background() - container, err := redpanda.Run(ctx, "docker.redpanda.com/redpandadata/redpanda:v23.3.3", redpanda.WithAutoCreateTopics()) + ctr, err := redpanda.Run(ctx, "docker.redpanda.com/redpandadata/redpanda:v23.3.3", redpanda.WithAutoCreateTopics()) + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - - brokers, err := container.KafkaSeedBroker(ctx) + brokers, err := ctr.KafkaSeedBroker(ctx) require.NoError(t, err) kafkaCl, err := kgo.NewClient( @@ -357,15 +334,10 @@ func TestRedpandaWithTLS(t *testing.T) { ctx := context.Background() - container, err := redpanda.Run(ctx, "docker.redpanda.com/redpandadata/redpanda:v23.3.3", redpanda.WithTLS(cert.Bytes, cert.KeyBytes)) + ctr, err := redpanda.Run(ctx, "docker.redpanda.com/redpandadata/redpanda:v23.3.3", redpanda.WithTLS(cert.Bytes, cert.KeyBytes)) + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - tlsConfig := cert.TLSConfig() httpCl := &http.Client{ @@ -378,7 +350,7 @@ func TestRedpandaWithTLS(t *testing.T) { } // Test Admin API - adminAPIURL, err := container.AdminAPIAddress(ctx) + adminAPIURL, err := ctr.AdminAPIAddress(ctx) require.NoError(t, err) require.True(t, strings.HasPrefix(adminAPIURL, "https://"), "AdminAPIAddress should return https url") req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/v1/cluster/health_overview", adminAPIURL), nil) @@ -389,7 +361,7 @@ func TestRedpandaWithTLS(t *testing.T) { resp.Body.Close() // Test Schema Registry API - schemaRegistryURL, err := container.SchemaRegistryAddress(ctx) + schemaRegistryURL, err := ctr.SchemaRegistryAddress(ctx) require.NoError(t, err) require.True(t, strings.HasPrefix(adminAPIURL, "https://"), "SchemaRegistryAddress should return https url") req, err = http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/subjects", schemaRegistryURL), nil) @@ -399,7 +371,7 @@ func TestRedpandaWithTLS(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) resp.Body.Close() - brokers, err := container.KafkaSeedBroker(ctx) + brokers, err := ctr.KafkaSeedBroker(ctx) require.NoError(t, err) kafkaCl, err := kgo.NewClient( @@ -426,7 +398,7 @@ func TestRedpandaWithTLSAndSASL(t *testing.T) { ctx := context.Background() - container, err := redpanda.Run(ctx, + ctr, err := redpanda.Run(ctx, "docker.redpanda.com/redpandadata/redpanda:v23.3.3", redpanda.WithTLS(cert.Bytes, cert.KeyBytes), redpanda.WithEnableSASL(), @@ -434,17 +406,12 @@ func TestRedpandaWithTLSAndSASL(t *testing.T) { redpanda.WithNewServiceAccount("superuser-1", "test"), redpanda.WithSuperusers("superuser-1"), ) + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) - tlsConfig := cert.TLSConfig() - broker, err := container.KafkaSeedBroker(ctx) + broker, err := ctr.KafkaSeedBroker(ctx) require.NoError(t, err) kafkaCl, err := kgo.NewClient( @@ -469,14 +436,17 @@ func TestRedpandaListener_Simple(t *testing.T) { rpNetwork, err := network.New(ctx) require.NoError(t, err) - // 2. Start Redpanda container + testcontainers.CleanupNetwork(t, rpNetwork) + + // 2. Start Redpanda ctr // withListenerRP { - container, err := redpanda.Run(ctx, + ctr, err := redpanda.Run(ctx, "redpandadata/redpanda:v23.2.18", network.WithNetwork([]string{"redpanda-host"}, rpNetwork), redpanda.WithListener("redpanda:29092"), redpanda.WithAutoCreateTopics(), ) // } + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) // 3. Start KCat container @@ -498,7 +468,7 @@ func TestRedpandaListener_Simple(t *testing.T) { Started: true, }) // } - + testcontainers.CleanupContainer(t, kcat) require.NoError(t, err) // 4. Copy message to kcat @@ -519,21 +489,7 @@ func TestRedpandaListener_Simple(t *testing.T) { // 7. Read Message from stdout out, err := io.ReadAll(stdout) require.NoError(t, err) - require.Contains(t, string(out), "Message produced by kcat") - - t.Cleanup(func() { - if err := kcat.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate kcat container: %s", err) - } - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate redpanda container: %s", err) - } - - if err := rpNetwork.Remove(ctx); err != nil { - t.Fatalf("failed to remove network: %s", err) - } - }) } func TestRedpandaListener_InvalidPort(t *testing.T) { @@ -542,34 +498,28 @@ func TestRedpandaListener_InvalidPort(t *testing.T) { // 1. Create network RPNetwork, err := network.New(ctx) require.NoError(t, err) + testcontainers.CleanupNetwork(t, RPNetwork) - // 2. Attempt Start Redpanda container - _, err = redpanda.Run(ctx, + // 2. Attempt Start Redpanda ctr + ctr, err := redpanda.Run(ctx, "redpandadata/redpanda:v23.2.18", redpanda.WithListener("redpanda:99092"), network.WithNetwork([]string{"redpanda-host"}, RPNetwork), ) - + testcontainers.CleanupContainer(t, ctr) require.Error(t, err) - require.Contains(t, err.Error(), "invalid port on listener redpanda:99092") - - t.Cleanup(func() { - if err := RPNetwork.Remove(ctx); err != nil { - t.Fatalf("failed to remove network: %s", err) - } - }) } func TestRedpandaListener_NoNetwork(t *testing.T) { ctx := context.Background() - // 1. Attempt Start Redpanda container - _, err := redpanda.Run(ctx, + // 1. Attempt Start Redpanda ctr + ctr, err := redpanda.Run(ctx, "redpandadata/redpanda:v23.2.18", redpanda.WithListener("redpanda:99092"), ) - + testcontainers.CleanupContainer(t, ctr) require.Error(t, err) require.Contains(t, err.Error(), "container must be attached to at least one network") diff --git a/modules/registry/examples_test.go b/modules/registry/examples_test.go index ada7e33b857..8742456eefd 100644 --- a/modules/registry/examples_test.go +++ b/modules/registry/examples_test.go @@ -14,21 +14,21 @@ import ( func ExampleRun() { // runRegistryContainer { registryContainer, err := registry.Run(context.Background(), "registry:2.8.3") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := registryContainer.Terminate(context.Background()); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(registryContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := registryContainer.State(context.Background()) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -47,23 +47,26 @@ func ExampleRun_withAuthentication() { registry.WithData(filepath.Join("testdata", "data")), ) // } - if err != nil { - log.Fatalf("failed to start container: %s", err) - } defer func() { - if err := registryContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(registryContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } registryHost, err := registryContainer.HostAddress(ctx) if err != nil { - log.Fatalf("failed to get host: %s", err) // nolint:gocritic + log.Printf("failed to get host: %s", err) + return } cleanup, err := registry.SetDockerAuthConfig(registryHost, "testuser", "testpassword") if err != nil { - log.Fatalf("failed to set docker auth config: %s", err) // nolint:gocritic + log.Printf("failed to set docker auth config: %s", err) + return } defer cleanup() @@ -77,7 +80,6 @@ func ExampleRun_withAuthentication() { BuildArgs: map[string]*string{ "REGISTRY_HOST": ®istryHost, }, - PrintBuildLog: true, }, AlwaysPullImage: true, // make sure the authentication takes place ExposedPorts: []string{"6379/tcp"}, @@ -85,18 +87,20 @@ func ExampleRun_withAuthentication() { }, Started: true, }) - if err != nil { - log.Fatalf("failed to start container: %s", err) // nolint:gocritic - } defer func() { - if err := redisC.Terminate(context.Background()); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(redisC); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } state, err := redisC.State(context.Background()) if err != nil { - log.Fatalf("failed to get redis container state: %s", err) // nolint:gocritic + log.Printf("failed to get redis container state: %s", err) + return } fmt.Println(state.Running) @@ -113,18 +117,20 @@ func ExampleRun_pushImage() { registry.WithHtpasswdFile(filepath.Join("testdata", "auth", "htpasswd")), registry.WithData(filepath.Join("testdata", "data")), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } defer func() { - if err := registryContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(registryContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } registryHost, err := registryContainer.HostAddress(ctx) if err != nil { - log.Fatalf("failed to get host: %s", err) // nolint:gocritic + log.Printf("failed to get host: %s", err) + return } // Besides, we are also setting the authentication @@ -135,13 +141,14 @@ func ExampleRun_pushImage() { registryContainer.RegistryName, "testuser", "testpassword", ) if err != nil { - log.Fatalf("failed to set docker auth config: %s", err) // nolint:gocritic + log.Printf("failed to set docker auth config: %s", err) + return } defer cleanup() // build a custom redis image from the private registry, // using RegistryName of the container as the registry. - // We are agoing to build the image with a fixed tag + // We are going to build the image with a fixed tag // that matches the private registry, and we are going to // push it again to the registry after the build. @@ -155,9 +162,8 @@ func ExampleRun_pushImage() { BuildArgs: map[string]*string{ "REGISTRY_HOST": ®istryHost, }, - Repo: repo, - Tag: tag, - PrintBuildLog: true, + Repo: repo, + Tag: tag, }, AlwaysPullImage: true, // make sure the authentication takes place ExposedPorts: []string{"6379/tcp"}, @@ -165,21 +171,23 @@ func ExampleRun_pushImage() { }, Started: true, }) - if err != nil { - log.Fatalf("failed to start container: %s", err) // nolint:gocritic - } defer func() { - if err := redisC.Terminate(context.Background()); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(redisC); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // pushingImage { // repo is localhost:32878/customredis // tag is v1.2.3 err = registryContainer.PushImage(context.Background(), fmt.Sprintf("%s:%s", repo, tag)) if err != nil { - log.Fatalf("failed to push image: %s", err) // nolint:gocritic + log.Printf("failed to push image: %s", err) + return } // } @@ -192,7 +200,8 @@ func ExampleRun_pushImage() { // newImage is customredis:v1.2.3 err = registryContainer.DeleteImage(context.Background(), newImage) if err != nil { - log.Fatalf("failed to delete image: %s", err) // nolint:gocritic + log.Printf("failed to delete image: %s", err) + return } // } @@ -204,18 +213,20 @@ func ExampleRun_pushImage() { }, Started: true, }) - if err != nil { - log.Fatalf("failed to start container from %s: %s", newImage, err) // nolint:gocritic - } defer func() { - if err := newRedisC.Terminate(context.Background()); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(newRedisC); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container from %s: %s", newImage, err) + return + } state, err := newRedisC.State(context.Background()) if err != nil { - log.Fatalf("failed to get redis container state from %s: %s", newImage, err) // nolint:gocritic + log.Printf("failed to get redis container state from %s: %s", newImage, err) + return } fmt.Println(state.Running) diff --git a/modules/registry/registry.go b/modules/registry/registry.go index b554f8dcc74..6cfa3d537be 100644 --- a/modules/registry/registry.go +++ b/modules/registry/registry.go @@ -238,15 +238,17 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *RegistryContainer + if container != nil { + c = &RegistryContainer{Container: container} + } if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - c := &RegistryContainer{Container: container} - address, err := c.Address(ctx) if err != nil { - return c, err + return c, fmt.Errorf("address: %w", err) } c.RegistryName = strings.TrimPrefix(address, "http://") diff --git a/modules/registry/registry_test.go b/modules/registry/registry_test.go index d40f75125e4..6b90349ff3e 100644 --- a/modules/registry/registry_test.go +++ b/modules/registry/registry_test.go @@ -17,11 +17,11 @@ import ( func TestRegistry_unauthenticated(t *testing.T) { ctx := context.Background() - container, err := registry.Run(ctx, registry.DefaultImage) - terminateContainerOnEnd(t, ctx, container) + ctr, err := registry.Run(ctx, registry.DefaultImage) + testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) - httpAddress, err := container.Address(ctx) + httpAddress, err := ctr.Address(ctx) require.NoError(t, err) resp, err := http.Get(httpAddress + "/v2/_catalog") @@ -39,7 +39,7 @@ func TestRunContainer_authenticated(t *testing.T) { registry.WithHtpasswdFile(filepath.Join("testdata", "auth", "htpasswd")), registry.WithData(filepath.Join("testdata", "data")), ) - terminateContainerOnEnd(t, ctx, registryContainer) + testcontainers.CleanupContainer(t, registryContainer) require.NoError(t, err) // httpAddress { @@ -107,7 +107,7 @@ func TestRunContainer_authenticated(t *testing.T) { }, Started: true, }) - terminateContainerOnEnd(tt, ctx, redisC) + testcontainers.CleanupContainer(tt, redisC) require.Error(tt, err) require.Contains(tt, err.Error(), "unauthorized: authentication required") }) @@ -134,7 +134,7 @@ func TestRunContainer_authenticated(t *testing.T) { }, Started: true, }) - terminateContainerOnEnd(tt, ctx, redisC) + testcontainers.CleanupContainer(tt, redisC) require.NoError(tt, err) state, err := redisC.State(context.Background()) @@ -152,7 +152,7 @@ func TestRunContainer_authenticated_withCredentials(t *testing.T) { registry.WithHtpasswd("testuser:$2y$05$tTymaYlWwJOqie.bcSUUN.I.kxmo1m5TLzYQ4/ejJ46UMXGtq78EO"), ) // } - terminateContainerOnEnd(t, ctx, registryContainer) + testcontainers.CleanupContainer(t, registryContainer) require.NoError(t, err) httpAddress, err := registryContainer.Address(ctx) @@ -179,7 +179,7 @@ func TestRunContainer_wrongData(t *testing.T) { registry.WithHtpasswdFile(filepath.Join("testdata", "auth", "htpasswd")), registry.WithData(filepath.Join("testdata", "wrongdata")), ) - terminateContainerOnEnd(t, ctx, registryContainer) + testcontainers.CleanupContainer(t, registryContainer) require.NoError(t, err) registryHost, err := registryContainer.HostAddress(ctx) @@ -206,7 +206,7 @@ func TestRunContainer_wrongData(t *testing.T) { }, Started: true, }) - terminateContainerOnEnd(t, ctx, redisC) + testcontainers.CleanupContainer(t, redisC) require.Error(t, err) require.Contains(t, err.Error(), "manifest unknown") } @@ -223,16 +223,3 @@ func setAuthConfig(t *testing.T, host, username, password string) { t.Setenv("DOCKER_AUTH_CONFIG", string(auth)) } - -// terminateContainerOnEnd terminates the container when the test ends if it is not nil. -func terminateContainerOnEnd(tb testing.TB, ctx context.Context, container testcontainers.Container) { - tb.Helper() - - if container == nil { - return - } - - tb.Cleanup(func() { - require.NoError(tb, container.Terminate(ctx)) - }) -} diff --git a/modules/surrealdb/examples_test.go b/modules/surrealdb/examples_test.go index 2063903d420..7d5c13a598f 100644 --- a/modules/surrealdb/examples_test.go +++ b/modules/surrealdb/examples_test.go @@ -5,6 +5,7 @@ import ( "fmt" "log" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/surrealdb" ) @@ -13,21 +14,21 @@ func ExampleRun() { ctx := context.Background() surrealdbContainer, err := surrealdb.Run(ctx, "surrealdb/surrealdb:v1.1.1") - if err != nil { - log.Fatal(err) - } - - // Clean up the container defer func() { - if err := surrealdbContainer.Terminate(ctx); err != nil { - log.Fatal(err) + if err := testcontainers.TerminateContainer(surrealdbContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Print(err) + return + } // } state, err := surrealdbContainer.State(ctx) if err != nil { - log.Fatal(err) // nolint:gocritic + log.Print(err) + return } fmt.Println(state.Running) diff --git a/modules/surrealdb/go.mod b/modules/surrealdb/go.mod index d3d2d049d62..8d740ce836f 100644 --- a/modules/surrealdb/go.mod +++ b/modules/surrealdb/go.mod @@ -3,6 +3,7 @@ module github.com/testcontainers/testcontainers-go/modules/surrealdb go 1.22 require ( + github.com/stretchr/testify v1.9.0 github.com/surrealdb/surrealdb.go v0.2.1 github.com/testcontainers/testcontainers-go v0.33.0 ) @@ -16,6 +17,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect @@ -28,6 +30,7 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/klauspost/compress v1.17.4 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -39,6 +42,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -55,6 +59,7 @@ require ( golang.org/x/sys v0.21.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/surrealdb/go.sum b/modules/surrealdb/go.sum index d39dac7fa49..ffb535bb9f3 100644 --- a/modules/surrealdb/go.sum +++ b/modules/surrealdb/go.sum @@ -16,6 +16,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -54,6 +55,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -80,6 +85,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -178,6 +185,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/surrealdb/surrealdb.go b/modules/surrealdb/surrealdb.go index 1968ca5d987..cc9ae744dc2 100644 --- a/modules/surrealdb/surrealdb.go +++ b/modules/surrealdb/surrealdb.go @@ -116,9 +116,14 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *SurrealDBContainer + if container != nil { + c = &SurrealDBContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &SurrealDBContainer{Container: container}, nil + return c, nil } diff --git a/modules/surrealdb/surrealdb_test.go b/modules/surrealdb/surrealdb_test.go index 7a3de29bfd5..5823ca9e2f4 100644 --- a/modules/surrealdb/surrealdb_test.go +++ b/modules/surrealdb/surrealdb_test.go @@ -4,133 +4,87 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" "github.com/surrealdb/surrealdb.go" + + "github.com/testcontainers/testcontainers-go" ) func TestSurrealDBSelect(t *testing.T) { ctx := context.Background() - container, err := Run(ctx, "surrealdb/surrealdb:v1.1.1") - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := Run(ctx, "surrealdb/surrealdb:v1.1.1") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) - url, err := container.URL(ctx) - if err != nil { - t.Fatal(err) - } + url, err := ctr.URL(ctx) + require.NoError(t, err) db, err := surrealdb.New(url) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if _, err := db.Use("test", "test"); err != nil { - t.Fatal(err) - } + _, err = db.Use("test", "test") + require.NoError(t, err) - if _, err := db.Create("person.tobie", map[string]any{ + _, err = db.Create("person.tobie", map[string]any{ "title": "Founder & CEO", "name": map[string]string{ "first": "Tobie", "last": "Morgan Hitchcock", }, "marketing": true, - }); err != nil { - t.Fatal(err) - } + }) + require.NoError(t, err) result, err := db.Select("person.tobie") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) resultData := result.([]any)[0].(map[string]interface{}) - if resultData["title"] != "Founder & CEO" { - t.Fatal("title is not Founder & CEO") - } - if resultData["name"].(map[string]interface{})["first"] != "Tobie" { - t.Fatal("name.first is not Tobie") - } - if resultData["name"].(map[string]interface{})["last"] != "Morgan Hitchcock" { - t.Fatal("name.last is not Morgan Hitchcock") - } - if resultData["marketing"] != true { - t.Fatal("marketing is not true") - } + require.Equal(t, "Founder & CEO", resultData["title"]) + require.Equal(t, "Tobie", resultData["name"].(map[string]interface{})["first"]) + require.Equal(t, "Morgan Hitchcock", resultData["name"].(map[string]interface{})["last"]) + require.Equal(t, true, resultData["marketing"]) } func TestSurrealDBWithAuth(t *testing.T) { ctx := context.Background() - container, err := Run(ctx, "surrealdb/surrealdb:v1.1.1", WithAuthentication()) - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := Run(ctx, "surrealdb/surrealdb:v1.1.1", WithAuthentication()) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) // websocketURL { - url, err := container.URL(ctx) + url, err := ctr.URL(ctx) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) db, err := surrealdb.New(url) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer db.Close() - if _, err := db.Signin(map[string]string{"user": "root", "pass": "root"}); err != nil { - t.Fatal(err) - } + _, err = db.Signin(map[string]string{"user": "root", "pass": "root"}) + require.NoError(t, err) - if _, err := db.Use("test", "test"); err != nil { - t.Fatal(err) - } + _, err = db.Use("test", "test") + require.NoError(t, err) - if _, err := db.Create("person.tobie", map[string]any{ + _, err = db.Create("person.tobie", map[string]any{ "title": "Founder & CEO", "name": map[string]string{ "first": "Tobie", "last": "Morgan Hitchcock", }, "marketing": true, - }); err != nil { - t.Fatal(err) - } + }) + require.NoError(t, err) result, err := db.Select("person.tobie") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) resultData := result.([]any)[0].(map[string]interface{}) - if resultData["title"] != "Founder & CEO" { - t.Fatal("title is not Founder & CEO") - } - if resultData["name"].(map[string]interface{})["first"] != "Tobie" { - t.Fatal("name.first is not Tobie") - } - if resultData["name"].(map[string]interface{})["last"] != "Morgan Hitchcock" { - t.Fatal("name.last is not Morgan Hitchcock") - } - if resultData["marketing"] != true { - t.Fatal("marketing is not true") - } + require.Equal(t, "Founder & CEO", resultData["title"]) + require.Equal(t, "Tobie", resultData["name"].(map[string]interface{})["first"]) + require.Equal(t, "Morgan Hitchcock", resultData["name"].(map[string]interface{})["last"]) + require.Equal(t, true, resultData["marketing"]) } diff --git a/modules/valkey/examples_test.go b/modules/valkey/examples_test.go index b302fc63268..7e1d2618509 100644 --- a/modules/valkey/examples_test.go +++ b/modules/valkey/examples_test.go @@ -6,6 +6,7 @@ import ( "log" "path/filepath" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/valkey" ) @@ -19,21 +20,21 @@ func ExampleRun() { valkey.WithLogLevel(valkey.LogLevelVerbose), valkey.WithConfigFile(filepath.Join("testdata", "valkey7.conf")), ) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := valkeyContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(valkeyContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := valkeyContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/valkey/valkey.go b/modules/valkey/valkey.go index e3b3728ddd9..00f4e911868 100644 --- a/modules/valkey/valkey.go +++ b/modules/valkey/valkey.go @@ -71,11 +71,16 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *ValkeyContainer + if container != nil { + c = &ValkeyContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &ValkeyContainer{Container: container}, nil + return c, nil } // WithConfigFile sets the config file to be used for the valkey container, and sets the command to run the valkey server diff --git a/modules/valkey/valkey_test.go b/modules/valkey/valkey_test.go index c440f791792..ed4fd75d120 100644 --- a/modules/valkey/valkey_test.go +++ b/modules/valkey/valkey_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/valkey-io/valkey-go" + "github.com/testcontainers/testcontainers-go" tcvalkey "github.com/testcontainers/testcontainers-go/modules/valkey" ) @@ -18,12 +19,8 @@ func TestIntegrationSetGet(t *testing.T) { ctx := context.Background() valkeyContainer, err := tcvalkey.Run(ctx, "docker.io/valkey/valkey:7.2.5") + testcontainers.CleanupContainer(t, valkeyContainer) require.NoError(t, err) - t.Cleanup(func() { - if err := valkeyContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) assertSetsGets(t, ctx, valkeyContainer, 1) } @@ -32,12 +29,8 @@ func TestValkeyWithConfigFile(t *testing.T) { ctx := context.Background() valkeyContainer, err := tcvalkey.Run(ctx, "docker.io/valkey/valkey:7.2.5", tcvalkey.WithConfigFile(filepath.Join("testdata", "valkey7.conf"))) + testcontainers.CleanupContainer(t, valkeyContainer) require.NoError(t, err) - t.Cleanup(func() { - if err := valkeyContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) assertSetsGets(t, ctx, valkeyContainer, 1) } @@ -59,12 +52,8 @@ func TestValkeyWithImage(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { valkeyContainer, err := tcvalkey.Run(ctx, tt.image, tcvalkey.WithConfigFile(filepath.Join("testdata", "valkey7.conf"))) + testcontainers.CleanupContainer(t, valkeyContainer) require.NoError(t, err) - t.Cleanup(func() { - if err := valkeyContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) assertSetsGets(t, ctx, valkeyContainer, 1) }) @@ -75,12 +64,8 @@ func TestValkeyWithLogLevel(t *testing.T) { ctx := context.Background() valkeyContainer, err := tcvalkey.Run(ctx, "docker.io/valkey/valkey:7.2.5", tcvalkey.WithLogLevel(tcvalkey.LogLevelVerbose)) + testcontainers.CleanupContainer(t, valkeyContainer) require.NoError(t, err) - t.Cleanup(func() { - if err := valkeyContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) assertSetsGets(t, ctx, valkeyContainer, 10) } @@ -89,12 +74,8 @@ func TestValkeyWithSnapshotting(t *testing.T) { ctx := context.Background() valkeyContainer, err := tcvalkey.Run(ctx, "docker.io/valkey/valkey:7.2.5", tcvalkey.WithSnapshotting(10, 1)) + testcontainers.CleanupContainer(t, valkeyContainer) require.NoError(t, err) - t.Cleanup(func() { - if err := valkeyContainer.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) assertSetsGets(t, ctx, valkeyContainer, 10) } diff --git a/modules/vault/examples_test.go b/modules/vault/examples_test.go index 75dc908f6b1..0b3d257c068 100644 --- a/modules/vault/examples_test.go +++ b/modules/vault/examples_test.go @@ -5,6 +5,7 @@ import ( "fmt" "log" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/exec" "github.com/testcontainers/testcontainers-go/modules/vault" ) @@ -14,21 +15,21 @@ func ExampleRun() { ctx := context.Background() vaultContainer, err := vault.Run(ctx, "hashicorp/vault:1.13.0") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := vaultContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(vaultContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := vaultContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -42,21 +43,21 @@ func ExampleRun_withToken() { ctx := context.Background() vaultContainer, err := vault.Run(ctx, "hashicorp/vault:1.13.0", vault.WithToken("MyToKeN")) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := vaultContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(vaultContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := vaultContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -66,7 +67,8 @@ func ExampleRun_withToken() { } exitCode, _, err := vaultContainer.Exec(ctx, cmds, exec.Multiplexed()) if err != nil { - log.Fatalf("failed to execute command: %s", err) + log.Printf("failed to execute command: %s", err) + return } fmt.Println(exitCode) @@ -87,21 +89,21 @@ func ExampleRun_withInitCommand() { "write --force auth/approle/role/myrole", // Create a role "write secret/testing top_secret=password123", // Create a secret )) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := vaultContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(vaultContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := vaultContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/vault/vault.go b/modules/vault/vault.go index 37237748edd..b679d45c21e 100644 --- a/modules/vault/vault.go +++ b/modules/vault/vault.go @@ -52,11 +52,16 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *VaultContainer + if container != nil { + c = &VaultContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &VaultContainer{container}, nil + return c, nil } // WithToken is a container option function that sets the root token for the Vault diff --git a/modules/vault/vault_test.go b/modules/vault/vault_test.go index 23a52cee576..c55f792c2c4 100644 --- a/modules/vault/vault_test.go +++ b/modules/vault/vault_test.go @@ -3,7 +3,6 @@ package vault_test import ( "context" "io" - "log" "net/http" "testing" "time" @@ -35,6 +34,7 @@ func TestVault(t *testing.T) { } vaultContainer, err := testcontainervault.Run(ctx, "hashicorp/vault:1.13.0", opts...) + testcontainers.CleanupContainer(t, vaultContainer) require.NoError(t, err) // httpHostAddress { @@ -118,11 +118,4 @@ func TestVault(t *testing.T) { assert.Equal(t, "bar", s.Data.Data["foo"]) }) }) - - t.Cleanup(func() { - // Clean up the vault after the test is complete - if err := vaultContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate vault: %s", err) - } - }) } diff --git a/modules/vearch/examples_test.go b/modules/vearch/examples_test.go index d75bd8e66ab..97ef8d8fe43 100644 --- a/modules/vearch/examples_test.go +++ b/modules/vearch/examples_test.go @@ -5,6 +5,7 @@ import ( "fmt" "log" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/vearch" ) @@ -13,21 +14,21 @@ func ExampleRun() { ctx := context.Background() vearchContainer, err := vearch.Run(ctx, "vearch/vearch:3.5.1") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := vearchContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(vearchContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := vearchContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) diff --git a/modules/vearch/go.mod b/modules/vearch/go.mod index eaad237db70..18363db5177 100644 --- a/modules/vearch/go.mod +++ b/modules/vearch/go.mod @@ -2,7 +2,10 @@ module github.com/testcontainers/testcontainers-go/modules/vearch go 1.22.0 -require github.com/testcontainers/testcontainers-go v0.33.0 +require ( + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 +) require ( dario.cat/mergo v1.0.0 // indirect @@ -13,6 +16,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect @@ -24,6 +28,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.4 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -35,6 +40,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -51,6 +57,7 @@ require ( golang.org/x/sys v0.21.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/testcontainers/testcontainers-go => ../.. diff --git a/modules/vearch/go.sum b/modules/vearch/go.sum index ed514ea5ef8..28367d00203 100644 --- a/modules/vearch/go.sum +++ b/modules/vearch/go.sum @@ -16,6 +16,7 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -52,6 +53,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -78,6 +83,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -174,6 +181,8 @@ google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvy google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/modules/vearch/vearch.go b/modules/vearch/vearch.go index dccedacad1f..3338eac0fe8 100644 --- a/modules/vearch/vearch.go +++ b/modules/vearch/vearch.go @@ -52,11 +52,16 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *VearchContainer + if container != nil { + c = &VearchContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &VearchContainer{Container: container}, nil + return c, nil } // RESTEndpoint returns the REST endpoint of the Vearch container diff --git a/modules/vearch/vearch_test.go b/modules/vearch/vearch_test.go index f73abda7ded..43c0f7e1f59 100644 --- a/modules/vearch/vearch_test.go +++ b/modules/vearch/vearch_test.go @@ -5,40 +5,30 @@ import ( "net/http" "testing" + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/vearch" ) func TestVearch(t *testing.T) { ctx := context.Background() - container, err := vearch.Run(ctx, "vearch/vearch:3.5.1") - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := vearch.Run(ctx, "vearch/vearch:3.5.1") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) t.Run("REST Endpoint", func(tt *testing.T) { // restEndpoint { - restEndpoint, err := container.RESTEndpoint(ctx) + restEndpoint, err := ctr.RESTEndpoint(ctx) // } - if err != nil { - tt.Fatalf("failed to get REST endpoint: %s", err) - } + require.NoError(t, err) cli := &http.Client{} resp, err := cli.Get(restEndpoint) - if err != nil { - tt.Fatalf("failed to perform GET request: %s", err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - tt.Fatalf("unexpected status code: %d", resp.StatusCode) - } + + require.Equal(t, http.StatusOK, resp.StatusCode) }) } diff --git a/modules/weaviate/examples_test.go b/modules/weaviate/examples_test.go index d6c8f509889..2f8e4f84a4a 100644 --- a/modules/weaviate/examples_test.go +++ b/modules/weaviate/examples_test.go @@ -20,21 +20,21 @@ func ExampleRun() { ctx := context.Background() weaviateContainer, err := tcweaviate.Run(ctx, "semitechnologies/weaviate:1.24.5") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - - // Clean up the container defer func() { - if err := weaviateContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(weaviateContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } // } state, err := weaviateContainer.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -48,24 +48,26 @@ func ExampleRun_connectWithClient() { ctx := context.Background() weaviateContainer, err := tcweaviate.Run(ctx, "semitechnologies/weaviate:1.23.9") - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := weaviateContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(weaviateContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } scheme, host, err := weaviateContainer.HttpHostAddress(ctx) if err != nil { - log.Fatalf("failed to get http schema and host: %s", err) // nolint:gocritic + log.Printf("failed to get http schema and host: %s", err) + return } grpcHost, err := weaviateContainer.GrpcHostAddress(ctx) if err != nil { - log.Fatalf("failed to get gRPC host: %s", err) // nolint:gocritic + log.Printf("failed to get gRPC host: %s", err) + return } connectionClient := &http.Client{} @@ -115,24 +117,26 @@ func ExampleRun_connectWithClientWithModules() { } weaviateContainer, err := tcweaviate.Run(ctx, "semitechnologies/weaviate:1.25.5", opts...) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := weaviateContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) // nolint:gocritic + if err := testcontainers.TerminateContainer(weaviateContainer); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } scheme, host, err := weaviateContainer.HttpHostAddress(ctx) if err != nil { - log.Fatalf("failed to get http schema and host: %s", err) // nolint:gocritic + log.Printf("failed to get http schema and host: %s", err) + return } grpcHost, err := weaviateContainer.GrpcHostAddress(ctx) if err != nil { - log.Fatalf("failed to get gRPC host: %s", err) // nolint:gocritic + log.Printf("failed to get gRPC host: %s", err) + return } connectionClient := &http.Client{} diff --git a/modules/weaviate/go.mod b/modules/weaviate/go.mod index 6572fb3607b..f8b7d6895a2 100644 --- a/modules/weaviate/go.mod +++ b/modules/weaviate/go.mod @@ -3,7 +3,8 @@ module github.com/testcontainers/testcontainers-go/modules/weaviate go 1.22 require ( - github.com/testcontainers/testcontainers-go v0.33.0 + github.com/stretchr/testify v1.9.0 + github.com/testcontainers/testcontainers-go v0.32.0 github.com/weaviate/weaviate-go-client/v4 v4.13.1 google.golang.org/grpc v1.64.1 ) @@ -20,6 +21,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect @@ -56,6 +58,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect diff --git a/modules/weaviate/weaviate.go b/modules/weaviate/weaviate.go index 93665efc66b..3e0830fe3b2 100644 --- a/modules/weaviate/weaviate.go +++ b/modules/weaviate/weaviate.go @@ -54,11 +54,16 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom } container, err := testcontainers.GenericContainer(ctx, genericContainerReq) + var c *WeaviateContainer + if container != nil { + c = &WeaviateContainer{Container: container} + } + if err != nil { - return nil, err + return c, fmt.Errorf("generic container: %w", err) } - return &WeaviateContainer{Container: container}, nil + return c, nil } // HttpHostAddress returns the schema and host of the Weaviate container. diff --git a/modules/weaviate/weaviate_test.go b/modules/weaviate/weaviate_test.go index bb44e7a90a9..d6dae679e61 100644 --- a/modules/weaviate/weaviate_test.go +++ b/modules/weaviate/weaviate_test.go @@ -6,53 +6,41 @@ import ( "net/http" "testing" + "github.com/stretchr/testify/require" wvt "github.com/weaviate/weaviate-go-client/v4/weaviate" wvtgrpc "github.com/weaviate/weaviate-go-client/v4/weaviate/grpc" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/health/grpc_health_v1" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/weaviate" ) func TestWeaviate(t *testing.T) { ctx := context.Background() - container, err := weaviate.Run(ctx, "semitechnologies/weaviate:1.25.5") - if err != nil { - t.Fatal(err) - } - - // Clean up the container after the test is complete - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := weaviate.Run(ctx, "semitechnologies/weaviate:1.25.5") + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) t.Run("HttpHostAddress", func(tt *testing.T) { // httpHostAddress { - schema, host, err := container.HttpHostAddress(ctx) + schema, host, err := ctr.HttpHostAddress(ctx) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) cli := &http.Client{} resp, err := cli.Get(fmt.Sprintf("%s://%s", schema, host)) - if err != nil { - tt.Fatalf("failed to perform GET request: %s", err) - } + require.NoError(t, err) defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - tt.Fatalf("unexpected status code: %d", resp.StatusCode) - } + require.Equal(t, http.StatusOK, resp.StatusCode) }) t.Run("GrpcHostAddress", func(tt *testing.T) { // gRPCHostAddress { - host, err := container.GrpcHostAddress(ctx) + host, err := ctr.GrpcHostAddress(ctx) // } if err != nil { t.Fatal(err) @@ -76,11 +64,11 @@ func TestWeaviate(t *testing.T) { }) t.Run("Weaviate client", func(tt *testing.T) { - httpScheme, httpHost, err := container.HttpHostAddress(ctx) + httpScheme, httpHost, err := ctr.HttpHostAddress(ctx) if err != nil { tt.Fatal(err) } - grpcHost, err := container.GrpcHostAddress(ctx) + grpcHost, err := ctr.GrpcHostAddress(ctx) if err != nil { tt.Fatal(err) } diff --git a/mounts_test.go b/mounts_test.go index 533b584feb7..b1ac51d3052 100644 --- a/mounts_test.go +++ b/mounts_test.go @@ -171,13 +171,14 @@ func TestContainerMounts_PrepareMounts(t *testing.T) { } func TestCreateContainerWithVolume(t *testing.T) { + volumeName := "test-volume" // volumeMounts { req := testcontainers.ContainerRequest{ Image: "alpine", Mounts: testcontainers.ContainerMounts{ { Source: testcontainers.GenericVolumeMountSource{ - Name: "test-volume", + Name: volumeName, }, Target: "/data", }, @@ -190,8 +191,8 @@ func TestCreateContainerWithVolume(t *testing.T) { ContainerRequest: req, Started: true, }) + testcontainers.CleanupContainer(t, c, testcontainers.RemoveVolumes(volumeName)) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c) // Check if volume is created client, err := testcontainers.NewDockerClientWithOpts(ctx) @@ -204,12 +205,13 @@ func TestCreateContainerWithVolume(t *testing.T) { } func TestMountsReceiveRyukLabels(t *testing.T) { + volumeName := "app-data" req := testcontainers.ContainerRequest{ Image: "alpine", Mounts: testcontainers.ContainerMounts{ { Source: testcontainers.GenericVolumeMountSource{ - Name: "app-data", + Name: volumeName, }, Target: "/data", }, @@ -223,18 +225,18 @@ func TestMountsReceiveRyukLabels(t *testing.T) { // Ensure the volume is removed before creating the container // otherwise the volume will be reused and the labels won't be set. - err = client.VolumeRemove(ctx, "app-data", true) + err = client.VolumeRemove(ctx, volumeName, true) require.NoError(t, err) c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) + testcontainers.CleanupContainer(t, c, testcontainers.RemoveVolumes(volumeName)) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, c) // Check if volume is created with the expected labels. - volume, err := client.VolumeInspect(ctx, "app-data") + volume, err := client.VolumeInspect(ctx, volumeName) require.NoError(t, err) require.Equal(t, testcontainers.GenericLabels(), volume.Labels) } diff --git a/network/examples_test.go b/network/examples_test.go index 7335b855648..a6b6bec4956 100644 --- a/network/examples_test.go +++ b/network/examples_test.go @@ -21,7 +21,7 @@ func ExampleNew() { } defer func() { if err := net.Remove(ctx); err != nil { - log.Fatalf("failed to remove network: %s", err) + log.Printf("failed to remove network: %s", err) } }() // } @@ -62,7 +62,7 @@ func ExampleNew_withOptions() { } defer func() { if err := net.Remove(ctx); err != nil { - log.Fatalf("failed to remove network: %s", err) + log.Printf("failed to remove network: %s", err) } }() // } diff --git a/network/network_test.go b/network/network_test.go index 4ff80e2bedb..5cee817eccf 100644 --- a/network/network_test.go +++ b/network/network_test.go @@ -7,7 +7,6 @@ import ( "github.com/docker/docker/api/types/filters" dockernetwork "github.com/docker/docker/api/types/network" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" @@ -26,21 +25,17 @@ func TestNew(t *testing.T) { net, err := network.New(ctx, network.WithAttachable(), - // Makes the network internal only, meaning the host machine cannot access it. - // Remove or use `network.WithDriver("bridge")` to change the network's mode. - network.WithInternal(), + network.WithDriver("bridge"), network.WithLabels(map[string]string{"this-is-a-test": "value"}), ) require.NoError(t, err) defer func() { - if err := net.Remove(ctx); err != nil { - t.Fatalf("failed to remove network: %s", err) - } + require.NoError(t, net.Remove(ctx)) }() networkName := net.Name - nginxC, _ := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + nginxC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ Image: "nginx:alpine", ExposedPorts: []string{ @@ -52,11 +47,8 @@ func TestNew(t *testing.T) { }, Started: true, }) - defer func() { - if err := nginxC.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }() + testcontainers.CleanupContainer(t, nginxC) + require.NoError(t, err) client, err := testcontainers.NewDockerClientWithOpts(context.Background()) require.NoError(t, err) @@ -65,19 +57,16 @@ func TestNew(t *testing.T) { Filters: filters.NewArgs(filters.Arg("name", networkName)), }) require.NoError(t, err) - - assert.Len(t, resources, 1) + require.Len(t, resources, 1) newNetwork := resources[0] - expectedLabels := testcontainers.GenericLabels() expectedLabels["this-is-a-test"] = "true" - assert.True(t, newNetwork.Attachable) - assert.True(t, newNetwork.Internal) - assert.Equal(t, "value", newNetwork.Labels["this-is-a-test"]) - - require.NoError(t, err) + require.True(t, newNetwork.Attachable) + require.False(t, newNetwork.Internal) + require.Equal(t, "value", newNetwork.Labels["this-is-a-test"]) + require.NoError(t, testcontainers.TerminateContainer(nginxC)) } // testNetworkAliases { @@ -85,12 +74,8 @@ func TestContainerAttachedToNewNetwork(t *testing.T) { ctx := context.Background() newNetwork, err := network.New(ctx) - if err != nil { - t.Fatal(err) - } - t.Cleanup(func() { - require.NoError(t, newNetwork.Remove(ctx)) - }) + require.NoError(t, err) + testcontainers.CleanupNetwork(t, newNetwork) networkName := newNetwork.Name @@ -113,33 +98,21 @@ func TestContainerAttachedToNewNetwork(t *testing.T) { } nginx, err := testcontainers.GenericContainer(ctx, gcr) + testcontainers.CleanupContainer(t, nginx) require.NoError(t, err) - defer func() { - require.NoError(t, nginx.Terminate(ctx)) - }() networks, err := nginx.Networks(ctx) - if err != nil { - t.Fatal(err) - } - if len(networks) != 1 { - t.Errorf("Expected networks 1. Got '%d'.", len(networks)) - } + require.NoError(t, err) + require.Len(t, networks, 1) + nw := networks[0] - if nw != networkName { - t.Errorf("Expected network name '%s'. Got '%s'.", networkName, nw) - } + require.Equal(t, networkName, nw) networkAliases, err := nginx.NetworkAliases(ctx) - if err != nil { - t.Fatal(err) - } - if len(networkAliases) != 1 { - t.Errorf("Expected network aliases for 1 network. Got '%d'.", len(networkAliases)) - } + require.NoError(t, err) + require.Len(t, networkAliases, 1) networkAlias := networkAliases[networkName] - require.NotEmpty(t, networkAlias) for _, alias := range aliases { @@ -147,12 +120,8 @@ func TestContainerAttachedToNewNetwork(t *testing.T) { } networkIP, err := nginx.ContainerIP(ctx) - if err != nil { - t.Fatal(err) - } - if len(networkIP) == 0 { - t.Errorf("Expected an IP address, got %v", networkIP) - } + require.NoError(t, err) + require.NotEmpty(t, networkIP) } // } @@ -161,12 +130,8 @@ func TestContainerIPs(t *testing.T) { ctx := context.Background() newNetwork, err := network.New(ctx) - if err != nil { - t.Fatal(err) - } - t.Cleanup(func() { - require.NoError(t, newNetwork.Remove(ctx)) - }) + require.NoError(t, err) + testcontainers.CleanupNetwork(t, newNetwork) networkName := newNetwork.Name @@ -184,19 +149,12 @@ func TestContainerIPs(t *testing.T) { }, Started: true, }) + testcontainers.CleanupContainer(t, nginx) require.NoError(t, err) - defer func() { - require.NoError(t, nginx.Terminate(ctx)) - }() ips, err := nginx.ContainerIPs(ctx) - if err != nil { - t.Fatal(err) - } - - if len(ips) != 2 { - t.Errorf("Expected two IP addresses, got %v", len(ips)) - } + require.NoError(t, err) + require.Len(t, ips, 2) } func TestContainerWithReaperNetwork(t *testing.T) { @@ -212,10 +170,7 @@ func TestContainerWithReaperNetwork(t *testing.T) { for i := 0; i < maxNetworksCount; i++ { n, err := network.New(ctx) require.NoError(t, err) - // use t.Cleanup to run after terminateContainerOnEnd - t.Cleanup(func() { - require.NoError(t, n.Remove(ctx)) - }) + testcontainers.CleanupNetwork(t, n) networks = append(networks, n.Name) } @@ -232,11 +187,8 @@ func TestContainerWithReaperNetwork(t *testing.T) { }, Started: true, }) - + testcontainers.CleanupContainer(t, nginx) require.NoError(t, err) - defer func() { - require.NoError(t, nginx.Terminate(ctx)) - }() containerId := nginx.GetContainerID() @@ -246,21 +198,17 @@ func TestContainerWithReaperNetwork(t *testing.T) { cnt, err := cli.ContainerInspect(ctx, containerId) require.NoError(t, err) - assert.Len(t, cnt.NetworkSettings.Networks, maxNetworksCount) - assert.NotNil(t, cnt.NetworkSettings.Networks[networks[0]]) - assert.NotNil(t, cnt.NetworkSettings.Networks[networks[1]]) + require.Len(t, cnt.NetworkSettings.Networks, maxNetworksCount) + require.NotNil(t, cnt.NetworkSettings.Networks[networks[0]]) + require.NotNil(t, cnt.NetworkSettings.Networks[networks[1]]) } func TestMultipleContainersInTheNewNetwork(t *testing.T) { ctx := context.Background() net, err := network.New(ctx, network.WithDriver("bridge")) - if err != nil { - t.Fatal("cannot create network") - } - defer func() { - require.NoError(t, net.Remove(ctx)) - }() + require.NoError(t, err) + testcontainers.CleanupNetwork(t, net) networkName := net.Name @@ -271,12 +219,8 @@ func TestMultipleContainersInTheNewNetwork(t *testing.T) { }, Started: true, }) - if err != nil { - t.Fatal(err) - } - defer func() { - require.NoError(t, c1.Terminate(ctx)) - }() + testcontainers.CleanupContainer(t, c1) + require.NoError(t, err) c2, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ @@ -285,13 +229,8 @@ func TestMultipleContainersInTheNewNetwork(t *testing.T) { }, Started: true, }) - if err != nil { - t.Fatal(err) - return - } - defer func() { - require.NoError(t, c2.Terminate(ctx)) - }() + testcontainers.CleanupContainer(t, c2) + require.NoError(t, err) pNets, err := c1.Networks(ctx) require.NoError(t, err) @@ -299,11 +238,11 @@ func TestMultipleContainersInTheNewNetwork(t *testing.T) { rNets, err := c2.Networks(ctx) require.NoError(t, err) - assert.Len(t, pNets, 1) - assert.Len(t, rNets, 1) + require.Len(t, pNets, 1) + require.Len(t, rNets, 1) - assert.Equal(t, networkName, pNets[0]) - assert.Equal(t, networkName, rNets[0]) + require.Equal(t, networkName, pNets[0]) + require.Equal(t, networkName, rNets[0]) } func TestNew_withOptions(t *testing.T) { @@ -329,12 +268,8 @@ func TestNew_withOptions(t *testing.T) { network.WithDriver("bridge"), ) // } - if err != nil { - t.Fatal("cannot create network: ", err) - } - defer func() { - require.NoError(t, net.Remove(ctx)) - }() + require.NoError(t, err) + testcontainers.CleanupNetwork(t, net) networkName := net.Name @@ -349,32 +284,24 @@ func TestNew_withOptions(t *testing.T) { }, }, }) + testcontainers.CleanupContainer(t, nginx) require.NoError(t, err) - defer func() { - require.NoError(t, nginx.Terminate(ctx)) - }() provider, err := testcontainers.ProviderDocker.GetProvider() - if err != nil { - t.Fatal("Cannot get Provider") - } + require.NoError(t, err) defer provider.Close() //nolint:staticcheck foundNetwork, err := provider.GetNetwork(ctx, testcontainers.NetworkRequest{Name: networkName}) - if err != nil { - t.Fatal("Cannot get created network by name") - } - assert.Equal(t, ipamConfig, foundNetwork.IPAM) + require.NoError(t, err) + require.Equal(t, ipamConfig, foundNetwork.IPAM) } func TestWithNetwork(t *testing.T) { // first create the network to be reused nw, err := network.New(context.Background(), network.WithLabels(map[string]string{"network-type": "unique"})) require.NoError(t, err) - defer func() { - require.NoError(t, nw.Remove(context.Background())) - }() + testcontainers.CleanupNetwork(t, nw) networkName := nw.Name @@ -387,11 +314,11 @@ func TestWithNetwork(t *testing.T) { err := network.WithNetwork([]string{"alias"}, nw)(&req) require.NoError(t, err) - assert.Len(t, req.Networks, 1) - assert.Equal(t, networkName, req.Networks[0]) + require.Len(t, req.Networks, 1) + require.Equal(t, networkName, req.Networks[0]) - assert.Len(t, req.NetworkAliases, 1) - assert.Equal(t, map[string][]string{networkName: {"alias"}}, req.NetworkAliases) + require.Len(t, req.NetworkAliases, 1) + require.Equal(t, map[string][]string{networkName: {"alias"}}, req.NetworkAliases) } // verify that the network is created only once @@ -402,17 +329,17 @@ func TestWithNetwork(t *testing.T) { Filters: filters.NewArgs(filters.Arg("name", networkName)), }) require.NoError(t, err) - assert.Len(t, resources, 1) + require.Len(t, resources, 1) newNetwork := resources[0] expectedLabels := testcontainers.GenericLabels() expectedLabels["network-type"] = "unique" - assert.Equal(t, networkName, newNetwork.Name) - assert.False(t, newNetwork.Attachable) - assert.False(t, newNetwork.Internal) - assert.Equal(t, expectedLabels, newNetwork.Labels) + require.Equal(t, networkName, newNetwork.Name) + require.False(t, newNetwork.Attachable) + require.False(t, newNetwork.Internal) + require.Equal(t, expectedLabels, newNetwork.Labels) } func TestWithSyntheticNetwork(t *testing.T) { @@ -431,11 +358,11 @@ func TestWithSyntheticNetwork(t *testing.T) { err := network.WithNetwork([]string{"alias"}, nw)(&req) require.NoError(t, err) - assert.Len(t, req.Networks, 1) - assert.Equal(t, networkName, req.Networks[0]) + require.Len(t, req.Networks, 1) + require.Equal(t, networkName, req.Networks[0]) - assert.Len(t, req.NetworkAliases, 1) - assert.Equal(t, map[string][]string{networkName: {"alias"}}, req.NetworkAliases) + require.Len(t, req.NetworkAliases, 1) + require.Equal(t, map[string][]string{networkName: {"alias"}}, req.NetworkAliases) // verify that the network is created only once client, err := testcontainers.NewDockerClientWithOpts(context.Background()) @@ -445,14 +372,12 @@ func TestWithSyntheticNetwork(t *testing.T) { Filters: filters.NewArgs(filters.Arg("name", networkName)), }) require.NoError(t, err) - assert.Empty(t, resources) // no Docker network was created + require.Empty(t, resources) // no Docker network was created c, err := testcontainers.GenericContainer(context.Background(), req) + testcontainers.CleanupContainer(t, c) require.NoError(t, err) - assert.NotNil(t, c) - defer func() { - require.NoError(t, c.Terminate(context.Background())) - }() + require.NotNil(t, c) } func TestWithNewNetwork(t *testing.T) { @@ -466,13 +391,12 @@ func TestWithNewNetwork(t *testing.T) { network.WithLabels(map[string]string{"this-is-a-test": "value"}), )(&req) require.NoError(t, err) - - assert.Len(t, req.Networks, 1) + require.Len(t, req.Networks, 1) networkName := req.Networks[0] - assert.Len(t, req.NetworkAliases, 1) - assert.Equal(t, map[string][]string{networkName: {"alias"}}, req.NetworkAliases) + require.Len(t, req.NetworkAliases, 1) + require.Equal(t, map[string][]string{networkName: {"alias"}}, req.NetworkAliases) client, err := testcontainers.NewDockerClientWithOpts(context.Background()) require.NoError(t, err) @@ -481,7 +405,7 @@ func TestWithNewNetwork(t *testing.T) { Filters: filters.NewArgs(filters.Arg("name", networkName)), }) require.NoError(t, err) - assert.Len(t, resources, 1) + require.Len(t, resources, 1) newNetwork := resources[0] defer func() { @@ -491,10 +415,10 @@ func TestWithNewNetwork(t *testing.T) { expectedLabels := testcontainers.GenericLabels() expectedLabels["this-is-a-test"] = "value" - assert.Equal(t, networkName, newNetwork.Name) - assert.True(t, newNetwork.Attachable) - assert.True(t, newNetwork.Internal) - assert.Equal(t, expectedLabels, newNetwork.Labels) + require.Equal(t, networkName, newNetwork.Name) + require.True(t, newNetwork.Attachable) + require.True(t, newNetwork.Internal) + require.Equal(t, expectedLabels, newNetwork.Labels) } func TestWithNewNetworkContextTimeout(t *testing.T) { @@ -513,6 +437,6 @@ func TestWithNewNetworkContextTimeout(t *testing.T) { require.Error(t, err) // we do not want to fail, just skip the network creation - assert.Empty(t, req.Networks) - assert.Empty(t, req.NetworkAliases) + require.Empty(t, req.Networks) + require.Empty(t, req.NetworkAliases) } diff --git a/options_test.go b/options_test.go index e19ecde96e7..c8a67b0b062 100644 --- a/options_test.go +++ b/options_test.go @@ -93,7 +93,7 @@ func TestWithLogConsumers(t *testing.T) { ctx := context.Background() c, err := testcontainers.GenericContainer(ctx, req) - terminateContainerOnEnd(t, ctx, c) + testcontainers.CleanupContainer(t, c) // we expect an error because the MySQL environment variables are not set // but this is expected because we just want to test the log consumer require.Error(t, err) @@ -119,11 +119,8 @@ func TestWithStartupCommand(t *testing.T) { assert.Len(t, req.LifecycleHooks[0].PostStarts, 1) c, err := testcontainers.GenericContainer(context.Background(), req) + testcontainers.CleanupContainer(t, c) require.NoError(t, err) - defer func() { - err = c.Terminate(context.Background()) - require.NoError(t, err) - }() _, reader, err := c.Exec(context.Background(), []string{"ls", "/tmp/.testcontainers"}, exec.Multiplexed()) require.NoError(t, err) @@ -151,11 +148,8 @@ func TestWithAfterReadyCommand(t *testing.T) { assert.Len(t, req.LifecycleHooks[0].PostReadies, 1) c, err := testcontainers.GenericContainer(context.Background(), req) + testcontainers.CleanupContainer(t, c) require.NoError(t, err) - defer func() { - err = c.Terminate(context.Background()) - require.NoError(t, err) - }() _, reader, err := c.Exec(context.Background(), []string{"ls", "/tmp/.testcontainers"}, exec.Multiplexed()) require.NoError(t, err) diff --git a/parallel.go b/parallel.go index 34740eeaf44..0027619b4cd 100644 --- a/parallel.go +++ b/parallel.go @@ -2,6 +2,7 @@ package testcontainers import ( "context" + "errors" "fmt" "sync" ) @@ -34,22 +35,22 @@ func (gpe ParallelContainersError) Error() string { func parallelContainersRunner( ctx context.Context, requests <-chan GenericContainerRequest, - errors chan<- ParallelContainersRequestError, + errorsCh chan<- ParallelContainersRequestError, containers chan<- Container, wg *sync.WaitGroup, ) { + defer wg.Done() for req := range requests { c, err := GenericContainer(ctx, req) if err != nil { - errors <- ParallelContainersRequestError{ + errorsCh <- ParallelContainersRequestError{ Request: req, - Error: err, + Error: errors.Join(err, TerminateContainer(c)), } continue } containers <- c } - wg.Done() } // ParallelContainers creates a generic containers with parameters and run it in parallel mode diff --git a/parallel_test.go b/parallel_test.go index 122f59a4f79..f937b1e56db 100644 --- a/parallel_test.go +++ b/parallel_test.go @@ -110,7 +110,7 @@ func TestParallelContainers(t *testing.T) { for _, c := range res { c := c - terminateContainerOnEnd(t, context.Background(), c) + CleanupContainer(t, c) } if len(res) != tc.resLen { @@ -163,5 +163,5 @@ func TestParallelContainersWithReuse(t *testing.T) { t.Fatalf("expected errors: %d, got: %d\n", 0, len(e.Errors)) } // Container is reused, only terminate first container - terminateContainerOnEnd(t, ctx, res[0]) + CleanupContainer(t, res[0]) } diff --git a/port_forwarding.go b/port_forwarding.go index 1d86a8cdd58..88f14f2d72b 100644 --- a/port_forwarding.go +++ b/port_forwarding.go @@ -38,9 +38,7 @@ var sshPassword = uuid.NewString() // 1. Create a new SSHD container. // 2. Expose the host ports to the container after the container is ready. // 3. Close the SSH sessions before killing the container. -func exposeHostPorts(ctx context.Context, req *ContainerRequest, ports ...int) (ContainerLifecycleHooks, error) { - var sshdConnectHook ContainerLifecycleHooks - +func exposeHostPorts(ctx context.Context, req *ContainerRequest, ports ...int) (sshdConnectHook ContainerLifecycleHooks, err error) { //nolint:nonamedreturns // Required for error check. if len(ports) == 0 { return sshdConnectHook, fmt.Errorf("no ports to expose") } @@ -91,6 +89,12 @@ func exposeHostPorts(ctx context.Context, req *ContainerRequest, ports ...int) ( // start the SSHD container with the provided options sshdContainer, err := newSshdContainer(ctx, opts...) + // Ensure the SSHD container is stopped and removed in case of error. + defer func() { + if err != nil { + err = errors.Join(err, TerminateContainer(sshdContainer)) + } + }() if err != nil { return sshdConnectHook, fmt.Errorf("new sshd container: %w", err) } @@ -129,6 +133,20 @@ func exposeHostPorts(ctx context.Context, req *ContainerRequest, ports ...int) ( originalHCM(hostConfig) } + stopHooks := []ContainerHook{ + func(ctx context.Context, _ Container) error { + if ctx.Err() != nil { + // Context already canceled, need to create a new one to ensure + // the SSH session is closed. + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + } + + return TerminateContainer(sshdContainer, StopContext(ctx)) + }, + } + // after the container is ready, create the SSH tunnel // for each exposed port from the host. sshdConnectHook = ContainerLifecycleHooks{ @@ -137,12 +155,8 @@ func exposeHostPorts(ctx context.Context, req *ContainerRequest, ports ...int) ( return sshdContainer.exposeHostPort(ctx, req.HostAccessPorts...) }, }, - PreTerminates: []ContainerHook{ - func(ctx context.Context, _ Container) error { - // before killing the container, close the SSH sessions - return sshdContainer.Terminate(ctx) - }, - }, + PreStops: stopHooks, + PreTerminates: stopHooks, } return sshdConnectHook, nil @@ -168,17 +182,13 @@ func newSshdContainer(ctx context.Context, opts ...ContainerCustomizer) (*sshdCo } c, err := GenericContainer(ctx, req) - if err != nil { - return nil, err + var sshd *sshdContainer + if c != nil { + sshd = &sshdContainer{Container: c} } - // force a type assertion to return a concrete type, - // because GenericContainer returns a Container interface. - dc := c.(*DockerContainer) - - sshd := &sshdContainer{ - DockerContainer: dc, - portForwarders: []PortForwarder{}, + if err != nil { + return sshd, fmt.Errorf("generic container: %w", err) } sshClientConfig, err := configureSSHConfig(ctx, sshd) @@ -195,7 +205,7 @@ func newSshdContainer(ctx context.Context, opts ...ContainerCustomizer) (*sshdCo // sshdContainer represents the SSHD container type used for the port forwarding container. // It's an internal type that extends the DockerContainer type, to add the SSH tunneling capabilities. type sshdContainer struct { - *DockerContainer + Container port string sshConfig *ssh.ClientConfig portForwarders []PortForwarder @@ -203,17 +213,30 @@ type sshdContainer struct { // Terminate stops the container and closes the SSH session func (sshdC *sshdContainer) Terminate(ctx context.Context) error { + sshdC.closePorts(ctx) + + return sshdC.Container.Terminate(ctx) +} + +// Stop stops the container and closes the SSH session +func (sshdC *sshdContainer) Stop(ctx context.Context, timeout *time.Duration) error { + sshdC.closePorts(ctx) + + return sshdC.Container.Stop(ctx, timeout) +} + +// closePorts closes all port forwarders. +func (sshdC *sshdContainer) closePorts(ctx context.Context) { for _, pfw := range sshdC.portForwarders { pfw.Close(ctx) } - - return sshdC.DockerContainer.Terminate(ctx) + sshdC.portForwarders = nil // Ensure the port forwarders are not used after closing. } func configureSSHConfig(ctx context.Context, sshdC *sshdContainer) (*ssh.ClientConfig, error) { mappedPort, err := sshdC.MappedPort(ctx, sshPort) if err != nil { - return nil, err + return nil, fmt.Errorf("mapped port: %w", err) } sshdC.port = mappedPort.Port() diff --git a/port_forwarding_test.go b/port_forwarding_test.go index 471736150b5..7a82158147b 100644 --- a/port_forwarding_test.go +++ b/port_forwarding_test.go @@ -80,10 +80,7 @@ func TestExposeHostPorts(t *testing.T) { var err error nw, err = network.New(context.Background()) require.NoError(tt, err) - - tt.Cleanup(func() { - require.NoError(tt, nw.Remove(context.Background())) - }) + testcontainers.CleanupNetwork(t, nw) req.Networks = []string{nw.Name} req.NetworkAliases = map[string][]string{nw.Name: {"myalpine"}} @@ -97,10 +94,8 @@ func TestExposeHostPorts(t *testing.T) { } c, err := testcontainers.GenericContainer(ctx, req) + testcontainers.CleanupContainer(t, c) require.NoError(tt, err) - tt.Cleanup(func() { - require.NoError(tt, c.Terminate(context.Background())) - }) if tc.hasHostAccess { // create a container that has host access, which will @@ -124,15 +119,11 @@ func httpRequest(t *testing.T, c testcontainers.Container, port int) (int, strin tcexec.Multiplexed(), ) // } - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // read the response bs, err := io.ReadAll(reader) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) return code, string(bs) } diff --git a/reaper_test.go b/reaper_test.go index e526e8ec9a1..f421c2686d4 100644 --- a/reaper_test.go +++ b/reaper_test.go @@ -119,7 +119,7 @@ func TestContainerStartsWithoutTheReaper(t *testing.T) { ctx := context.Background() - container, err := GenericContainer(ctx, GenericContainerRequest{ + ctr, err := GenericContainer(ctx, GenericContainerRequest{ ProviderType: providerType, ContainerRequest: ContainerRequest{ Image: nginxAlpineImage, @@ -129,9 +129,8 @@ func TestContainerStartsWithoutTheReaper(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, ctr) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, container) sessionID := core.SessionID() @@ -163,10 +162,10 @@ func TestContainerStartsWithTheReaper(t *testing.T) { }, Started: true, }) + CleanupContainer(t, c) if err != nil { t.Fatal(err) } - terminateContainerOnEnd(t, ctx, c) sessionID := core.SessionID() @@ -198,9 +197,8 @@ func TestContainerStopWithReaper(t *testing.T) { }, Started: true, }) - + CleanupContainer(t, nginxA) require.NoError(t, err) - terminateContainerOnEnd(t, ctx, nginxA) state, err := nginxA.State(ctx) if err != nil { @@ -246,25 +244,18 @@ func TestContainerTerminationWithReaper(t *testing.T) { }, Started: true, }) - if err != nil { - t.Fatal(err) - } + CleanupContainer(t, nginxA) + require.NoError(t, err) state, err := nginxA.State(ctx) - if err != nil { - t.Fatal(err) - } - if state.Running != true { - t.Fatal("The container shoud be in running state") - } + require.NoError(t, err) + require.True(t, state.Running) + err = nginxA.Terminate(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + _, err = nginxA.State(ctx) - if err == nil { - t.Fatal("expected error from container inspect.") - } + require.Error(t, err) } func TestContainerTerminationWithoutReaper(t *testing.T) { @@ -286,6 +277,7 @@ func TestContainerTerminationWithoutReaper(t *testing.T) { }, Started: true, }) + CleanupContainer(t, nginxA) if err != nil { t.Fatal(err) } @@ -493,7 +485,7 @@ func Test_ReaperReusedIfHealthy(t *testing.T) { require.NoError(t, err, "connecting to Reaper should be successful") if !wasReaperRunning { - terminateContainerOnEnd(t, ctx, reaper.container) + CleanupContainer(t, reaper.container) } } @@ -530,7 +522,7 @@ func Test_RecreateReaperIfTerminated(t *testing.T) { term <- true }(terminate) require.NoError(t, err, "connecting to Reaper should be successful") - terminateContainerOnEnd(t, ctx, recreatedReaper.container) + CleanupContainer(t, recreatedReaper.container) } func TestReaper_reuseItFromOtherTestProgramUsingDocker(t *testing.T) { @@ -582,7 +574,7 @@ func TestReaper_reuseItFromOtherTestProgramUsingDocker(t *testing.T) { require.NoError(t, err, "connecting to Reaper should be successful") if !wasReaperRunning { - terminateContainerOnEnd(t, ctx, reaper.container) + CleanupContainer(t, reaper.container) } } diff --git a/testcontainers_test.go b/testcontainers_test.go index fe5af71e892..5ff914051ce 100644 --- a/testcontainers_test.go +++ b/testcontainers_test.go @@ -1,7 +1,6 @@ package testcontainers import ( - "fmt" "os" "os/exec" "regexp" @@ -81,5 +80,5 @@ func TestSessionIDHelper(t *testing.T) { t.Skip("Not a real test, used as a test helper") } - fmt.Printf(">>>%s<<<\n", SessionID()) + t.Logf(">>>%s<<<\n", SessionID()) } diff --git a/testdata/echoserver.go b/testdata/echoserver.go index a62c783f5d5..1222b7045fb 100644 --- a/testdata/echoserver.go +++ b/testdata/echoserver.go @@ -36,7 +36,8 @@ func main() { ln, err := net.Listen("tcp", ":8080") if err != nil { - log.Fatal(err) + log.Println(err) + return } fmt.Println("ready") diff --git a/testhelpers_test.go b/testhelpers_test.go index 3be3b7c50d2..4f268a8aee3 100644 --- a/testhelpers_test.go +++ b/testhelpers_test.go @@ -1,25 +1,6 @@ package testcontainers_test -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/testcontainers/testcontainers-go" -) - const ( nginxAlpineImage = "docker.io/nginx:alpine" nginxDefaultPort = "80/tcp" ) - -func terminateContainerOnEnd(tb testing.TB, ctx context.Context, ctr testcontainers.Container) { - tb.Helper() - if ctr == nil { - return - } - tb.Cleanup(func() { - require.NoError(tb, ctr.Terminate(ctx)) - }) -} diff --git a/testing.go b/testing.go index eab23cb8054..41391519deb 100644 --- a/testing.go +++ b/testing.go @@ -3,9 +3,17 @@ package testcontainers import ( "context" "fmt" + "regexp" "testing" + + "github.com/docker/docker/errdefs" + "github.com/stretchr/testify/require" ) +// errAlreadyInProgress is a regular expression that matches the error for a container +// removal that is already in progress. +var errAlreadyInProgress = regexp.MustCompile(`removal of container .* is already in progress`) + // SkipIfProviderIsNotHealthy is a utility function capable of skipping tests // if the provider is not healthy, or running at all. // This is a function designed to be used in your test, when Docker is not mandatory for CI/CD. @@ -51,3 +59,93 @@ func (lc *StdoutLogConsumer) Accept(l Log) { } // } + +// CleanupContainer is a helper function that schedules the container +// to be stopped / terminated when the test ends. +// +// This should be called as a defer directly after (before any error check) +// of [GenericContainer](...) or a modules Run(...) in a test to ensure the +// container is stopped when the function ends. +// +// before any error check. If container is nil, its a no-op. +func CleanupContainer(tb testing.TB, container Container, options ...TerminateOption) { + tb.Helper() + + tb.Cleanup(func() { + noErrorOrIgnored(tb, TerminateContainer(container, options...)) + }) +} + +// CleanupNetwork is a helper function that schedules the network to be +// removed when the test ends. +// This should be the first call after NewNetwork(...) in a test before +// any error check. If network is nil, its a no-op. +func CleanupNetwork(tb testing.TB, network Network) { + tb.Helper() + + tb.Cleanup(func() { + noErrorOrIgnored(tb, network.Remove(context.Background())) + }) +} + +// noErrorOrIgnored is a helper function that checks if the error is nil or an error +// we can ignore. +func noErrorOrIgnored(tb testing.TB, err error) { + tb.Helper() + + if isCleanupSafe(err) { + return + } + + require.NoError(tb, err) +} + +// causer is an interface that allows to get the cause of an error. +type causer interface { + Cause() error +} + +// wrapErr is an interface that allows to unwrap an error. +type wrapErr interface { + Unwrap() error +} + +// unwrapErrs is an interface that allows to unwrap multiple errors. +type unwrapErrs interface { + Unwrap() []error +} + +// isCleanupSafe reports whether all errors in err's tree are one of the +// following, so can safely be ignored: +// - nil +// - not found +// - already in progress +func isCleanupSafe(err error) bool { + if err == nil { + return true + } + + switch x := err.(type) { //nolint:errorlint // We need to check for interfaces. + case errdefs.ErrNotFound: + return true + case errdefs.ErrConflict: + // Terminating a container that is already terminating. + if errAlreadyInProgress.MatchString(err.Error()) { + return true + } + return false + case causer: + return isCleanupSafe(x.Cause()) + case wrapErr: + return isCleanupSafe(x.Unwrap()) + case unwrapErrs: + for _, e := range x.Unwrap() { + if !isCleanupSafe(e) { + return false + } + } + return true + default: + return false + } +} diff --git a/testing_test.go b/testing_test.go index 56817d655a3..6c50738220c 100644 --- a/testing_test.go +++ b/testing_test.go @@ -1,7 +1,86 @@ package testcontainers -import "testing" +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) func ExampleSkipIfProviderIsNotHealthy() { SkipIfProviderIsNotHealthy(&testing.T{}) } + +type notFoundError struct{} + +func (notFoundError) NotFound() {} + +func (notFoundError) Error() string { + return "not found" +} + +func Test_isNotFound(t *testing.T) { + tests := map[string]struct { + err error + want bool + }{ + "nil": { + err: nil, + want: true, + }, + "join-nils": { + err: errors.Join(nil, nil), + want: true, + }, + "join-nil-not-found": { + err: errors.Join(nil, notFoundError{}), + want: true, + }, + "not-found": { + err: notFoundError{}, + want: true, + }, + "other": { + err: errors.New("other"), + want: false, + }, + "join-other": { + err: errors.Join(nil, notFoundError{}, errors.New("other")), + want: false, + }, + "warp": { + err: fmt.Errorf("wrap: %w", notFoundError{}), + want: true, + }, + "multi-warp": { + err: fmt.Errorf("wrap: %w", fmt.Errorf("wrap: %w", notFoundError{})), + want: true, + }, + "multi-warp-other": { + err: fmt.Errorf("wrap: %w", fmt.Errorf("wrap: %w", errors.New("other"))), + want: false, + }, + "multi-warp-other-not-found": { + err: fmt.Errorf("wrap: %w", fmt.Errorf("wrap: %w %w", errors.New("other"), notFoundError{})), + want: false, + }, + "multi-warp-not-found-nil": { + err: fmt.Errorf("wrap: %w", fmt.Errorf("wrap: %w %w", nil, notFoundError{})), + want: true, + }, + "multi-join-not-found-other": { + err: errors.Join(nil, fmt.Errorf("wrap: %w", errors.Join(notFoundError{}, errors.New("other")))), + want: false, + }, + "multi-join-not-found-nil": { + err: errors.Join(nil, fmt.Errorf("wrap: %w", errors.Join(notFoundError{}, nil))), + want: true, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + require.Equal(t, tc.want, isCleanupSafe(tc.err)) + }) + } +} diff --git a/wait/exec_test.go b/wait/exec_test.go index 13e3e47511c..224f7d99d95 100644 --- a/wait/exec_test.go +++ b/wait/exec_test.go @@ -12,6 +12,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/go-connections/nat" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" tcexec "github.com/testcontainers/testcontainers-go/exec" @@ -29,19 +30,20 @@ func ExampleExecStrategy() { ContainerRequest: req, Started: true, }) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := localstack.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(localstack); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } state, err := localstack.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -203,14 +205,8 @@ func TestExecStrategyWaitUntilReady_CustomResponseMatcher(t *testing.T) { // } ctx := context.Background() - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ContainerRequest: dockerReq, Started: true}) - if err != nil { - t.Error(err) - return - } - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ContainerRequest: dockerReq, Started: true}) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + // } } diff --git a/wait/file.go b/wait/file.go index 148907f3dbf..d9cab7a6e44 100644 --- a/wait/file.go +++ b/wait/file.go @@ -97,7 +97,7 @@ func (ws *FileStrategy) matchFile(ctx context.Context, target StrategyTarget) er if err != nil { return fmt.Errorf("copy from container: %w", err) } - defer rc.Close() //nolint: errcheck // Read close error can't tell us anything useful. + defer rc.Close() if ws.matcher == nil { // No matcher, just check if the file exists. diff --git a/wait/host_port.go b/wait/host_port.go index b349cc03717..a3e9137006f 100644 --- a/wait/host_port.go +++ b/wait/host_port.go @@ -130,31 +130,33 @@ func (hp *HostPortStrategy) WaitUntilReady(ctx context.Context, target StrategyT select { case <-ctx.Done(): - return fmt.Errorf("%w: %w", ctx.Err(), err) + return fmt.Errorf("mapped port: retries: %d, port: %q, last err: %w, ctx err: %w", i, port, err, ctx.Err()) case <-time.After(waitInterval): if err := checkTarget(ctx, target); err != nil { - return err + return fmt.Errorf("check target: retries: %d, port: %q, last err: %w", i, port, err) } port, err = target.MappedPort(ctx, internalPort) if err != nil { - log.Printf("(%d) [%s] %s\n", i, port, err) + log.Printf("mapped port: retries: %d, port: %q, err: %s\n", i, port, err) } } } if err := externalCheck(ctx, ipAddress, port, target, waitInterval); err != nil { - return err + return fmt.Errorf("external check: %w", err) } if hp.skipInternalCheck { return nil } - err = internalCheck(ctx, internalPort, target) - if err != nil && errors.Is(errShellNotExecutable, err) { - log.Println("Shell not executable in container, only external port check will be performed") - } else { - return err + if err = internalCheck(ctx, internalPort, target); err != nil { + if errors.Is(errShellNotExecutable, err) { + log.Println("Shell not executable in container, only external port validated") + return nil + } + + return fmt.Errorf("internal check: %w", err) } return nil @@ -167,9 +169,9 @@ func externalCheck(ctx context.Context, ipAddress string, port nat.Port, target dialer := net.Dialer{} address := net.JoinHostPort(ipAddress, portString) - for { + for i := 0; ; i++ { if err := checkTarget(ctx, target); err != nil { - return err + return fmt.Errorf("check target: retries: %d address: %s: %w", i, address, err) } conn, err := dialer.DialContext(ctx, proto, address) if err != nil { @@ -183,7 +185,7 @@ func externalCheck(ctx context.Context, ipAddress string, port nat.Port, target } } } - return err + return fmt.Errorf("dial: %w", err) } conn.Close() diff --git a/wait/host_port_test.go b/wait/host_port_test.go index c31c3dabc96..bf349f05a71 100644 --- a/wait/host_port_test.go +++ b/wait/host_port_test.go @@ -10,6 +10,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/go-connections/nat" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go/exec" ) @@ -152,14 +153,10 @@ func TestHostPortStrategyFailsWhileGettingPortDueToOOMKilledContainer(t *testing { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } + require.Error(t, err) expected := "container crashed with out-of-memory (OOMKilled)" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.Contains(t, err.Error(), expected) } } @@ -190,14 +187,10 @@ func TestHostPortStrategyFailsWhileGettingPortDueToExitedContainer(t *testing.T) { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } + require.Error(t, err) expected := "container exited with code 1" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.Contains(t, err.Error(), expected) } } @@ -227,14 +220,10 @@ func TestHostPortStrategyFailsWhileGettingPortDueToUnexpectedContainerStatus(t * { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } + require.Error(t, err) expected := "unexpected container status \"dead\"" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.Contains(t, err.Error(), expected) } } @@ -259,14 +248,10 @@ func TestHostPortStrategyFailsWhileExternalCheckingDueToOOMKilledContainer(t *te { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } + require.Error(t, err) expected := "container crashed with out-of-memory (OOMKilled)" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.Contains(t, err.Error(), expected) } } @@ -292,14 +277,10 @@ func TestHostPortStrategyFailsWhileExternalCheckingDueToExitedContainer(t *testi { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } + require.Error(t, err) expected := "container exited with code 1" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.Contains(t, err.Error(), expected) } } @@ -324,14 +305,10 @@ func TestHostPortStrategyFailsWhileExternalCheckingDueToUnexpectedContainerStatu { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } + require.Error(t, err) expected := "unexpected container status \"dead\"" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.Contains(t, err.Error(), expected) } } @@ -375,14 +352,10 @@ func TestHostPortStrategyFailsWhileInternalCheckingDueToOOMKilledContainer(t *te { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } + require.Error(t, err) expected := "container crashed with out-of-memory (OOMKilled)" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.Contains(t, err.Error(), expected) } } @@ -427,14 +400,10 @@ func TestHostPortStrategyFailsWhileInternalCheckingDueToExitedContainer(t *testi { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } + require.Error(t, err) expected := "container exited with code 1" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.Contains(t, err.Error(), expected) } } @@ -478,14 +447,10 @@ func TestHostPortStrategyFailsWhileInternalCheckingDueToUnexpectedContainerStatu { err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } + require.Error(t, err) expected := "unexpected container status \"dead\"" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } + require.Contains(t, err.Error(), expected) } } @@ -540,7 +505,6 @@ func TestHostPortStrategySucceedsGivenShellIsNotInstalled(t *testing.T) { WithStartupTimeout(5 * time.Second). WithPollInterval(100 * time.Millisecond) - if err := wg.WaitUntilReady(context.Background(), target); err != nil { - t.Fatal(err) - } + err = wg.WaitUntilReady(context.Background(), target) + require.NoError(t, err) } diff --git a/wait/http_test.go b/wait/http_test.go index 54610f46860..8e30210065a 100644 --- a/wait/http_test.go +++ b/wait/http_test.go @@ -17,6 +17,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/go-connections/nat" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" @@ -36,20 +37,21 @@ func ExampleHTTPStrategy() { ContainerRequest: req, Started: true, }) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - // } - defer func() { - if err := c.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(c); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + // } state, err := c.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -62,12 +64,14 @@ func ExampleHTTPStrategy_WithHeaders() { capath := filepath.Join("testdata", "root.pem") cafile, err := os.ReadFile(capath) if err != nil { - log.Fatalf("can't load ca file: %v", err) + log.Printf("can't load ca file: %v", err) + return } certpool := x509.NewCertPool() if !certpool.AppendCertsFromPEM(cafile) { - log.Fatalf("the ca file isn't valid") + log.Printf("the ca file isn't valid") + return } ctx := context.Background() @@ -94,19 +98,20 @@ func ExampleHTTPStrategy_WithHeaders() { ContainerRequest: req, Started: true, }) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := c.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(c); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } state, err := c.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -128,20 +133,21 @@ func ExampleHTTPStrategy_WithPort() { ContainerRequest: req, Started: true, }) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - // } - defer func() { - if err := c.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(c); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + // } state, err := c.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -162,19 +168,20 @@ func ExampleHTTPStrategy_WithForcedIPv4LocalHost() { ContainerRequest: req, Started: true, }) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - defer func() { - if err := c.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(c); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } state, err := c.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -196,20 +203,21 @@ func ExampleHTTPStrategy_WithBasicAuth() { ContainerRequest: req, Started: true, }) - if err != nil { - log.Fatalf("failed to start container: %s", err) - } - // } - defer func() { - if err := gogs.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) + if err := testcontainers.TerminateContainer(gogs); err != nil { + log.Printf("failed to terminate container: %s", err) } }() + if err != nil { + log.Printf("failed to start container: %s", err) + return + } + // } state, err := gogs.State(ctx) if err != nil { - log.Fatalf("failed to get container state: %s", err) // nolint:gocritic + log.Printf("failed to get container state: %s", err) + return } fmt.Println(state.Running) @@ -220,17 +228,11 @@ func ExampleHTTPStrategy_WithBasicAuth() { func TestHTTPStrategyWaitUntilReady(t *testing.T) { workdir, err := os.Getwd() - if err != nil { - t.Error(err) - return - } + require.NoError(t, err) capath := filepath.Join(workdir, "testdata", "root.pem") cafile, err := os.ReadFile(capath) - if err != nil { - t.Errorf("can't load ca file: %v", err) - return - } + require.NoError(t, err) certpool := x509.NewCertPool() if !certpool.AppendCertsFromPEM(cafile) { @@ -254,24 +256,17 @@ func TestHTTPStrategyWaitUntilReady(t *testing.T) { WithMethod(http.MethodPost).WithBody(bytes.NewReader([]byte("ping"))), } - container, err := testcontainers.GenericContainer(context.Background(), + ctr, err := testcontainers.GenericContainer(context.Background(), testcontainers.GenericContainerRequest{ContainerRequest: dockerReq, Started: true}) - if err != nil { - t.Error(err) - return - } - defer container.Terminate(context.Background()) // nolint: errcheck + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + host, err := ctr.Host(context.Background()) + require.NoError(t, err) + + port, err := ctr.MappedPort(context.Background(), "6443/tcp") + require.NoError(t, err) - host, err := container.Host(context.Background()) - if err != nil { - t.Error(err) - return - } - port, err := container.MappedPort(context.Background(), "6443/tcp") - if err != nil { - t.Error(err) - return - } client := http.Client{ Transport: &http.Transport{ TLSClientConfig: tlsconfig, @@ -289,30 +284,19 @@ func TestHTTPStrategyWaitUntilReady(t *testing.T) { }, } resp, err := client.Get(fmt.Sprintf("https://%s:%s", host, port.Port())) - if err != nil { - t.Error(err) - return - } + require.NoError(t, err) + defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Errorf("status code isn't ok: %s", resp.Status) - return - } + require.Equal(t, http.StatusOK, resp.StatusCode) } func TestHTTPStrategyWaitUntilReadyWithQueryString(t *testing.T) { workdir, err := os.Getwd() - if err != nil { - t.Error(err) - return - } + require.NoError(t, err) capath := filepath.Join(workdir, "testdata", "root.pem") cafile, err := os.ReadFile(capath) - if err != nil { - t.Errorf("can't load ca file: %v", err) - return - } + require.NoError(t, err) certpool := x509.NewCertPool() if !certpool.AppendCertsFromPEM(cafile) { @@ -335,24 +319,17 @@ func TestHTTPStrategyWaitUntilReadyWithQueryString(t *testing.T) { }), } - container, err := testcontainers.GenericContainer(context.Background(), + ctr, err := testcontainers.GenericContainer(context.Background(), testcontainers.GenericContainerRequest{ContainerRequest: dockerReq, Started: true}) - if err != nil { - t.Error(err) - return - } - defer container.Terminate(context.Background()) // nolint: errcheck + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + host, err := ctr.Host(context.Background()) + require.NoError(t, err) + + port, err := ctr.MappedPort(context.Background(), "6443/tcp") + require.NoError(t, err) - host, err := container.Host(context.Background()) - if err != nil { - t.Error(err) - return - } - port, err := container.MappedPort(context.Background(), "6443/tcp") - if err != nil { - t.Error(err) - return - } client := http.Client{ Transport: &http.Transport{ TLSClientConfig: tlsconfig, @@ -370,30 +347,19 @@ func TestHTTPStrategyWaitUntilReadyWithQueryString(t *testing.T) { }, } resp, err := client.Get(fmt.Sprintf("https://%s:%s", host, port.Port())) - if err != nil { - t.Error(err) - return - } + require.NoError(t, err) + defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Errorf("status code isn't ok: %s", resp.Status) - return - } + require.Equal(t, http.StatusOK, resp.StatusCode) } func TestHTTPStrategyWaitUntilReadyNoBasicAuth(t *testing.T) { workdir, err := os.Getwd() - if err != nil { - t.Error(err) - return - } + require.NoError(t, err) capath := filepath.Join(workdir, "testdata", "root.pem") cafile, err := os.ReadFile(capath) - if err != nil { - t.Errorf("can't load ca file: %v", err) - return - } + require.NoError(t, err) certpool := x509.NewCertPool() if !certpool.AppendCertsFromPEM(cafile) { @@ -424,27 +390,16 @@ func TestHTTPStrategyWaitUntilReadyNoBasicAuth(t *testing.T) { // } ctx := context.Background() - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ContainerRequest: dockerReq, Started: true}) - if err != nil { - t.Error(err) - return - } - t.Cleanup(func() { - if err := container.Terminate(ctx); err != nil { - t.Fatalf("failed to terminate container: %s", err) - } - }) + ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ContainerRequest: dockerReq, Started: true}) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + + host, err := ctr.Host(ctx) + require.NoError(t, err) + + port, err := ctr.MappedPort(ctx, "6443/tcp") + require.NoError(t, err) - host, err := container.Host(ctx) - if err != nil { - t.Error(err) - return - } - port, err := container.MappedPort(ctx, "6443/tcp") - if err != nil { - t.Error(err) - return - } client := http.Client{ Transport: &http.Transport{ TLSClientConfig: tlsconfig, @@ -462,15 +417,10 @@ func TestHTTPStrategyWaitUntilReadyNoBasicAuth(t *testing.T) { }, } resp, err := client.Get(fmt.Sprintf("https://%s:%s", host, port.Port())) - if err != nil { - t.Error(err) - return - } + require.NoError(t, err) + defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Errorf("status code isn't ok: %s", resp.Status) - return - } + require.Equal(t, http.StatusOK, resp.StatusCode) } func TestHttpStrategyFailsWhileGettingPortDueToOOMKilledContainer(t *testing.T) { @@ -513,17 +463,9 @@ func TestHttpStrategyFailsWhileGettingPortDueToOOMKilledContainer(t *testing.T) WithStartupTimeout(500 * time.Millisecond). WithPollInterval(100 * time.Millisecond) - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "container crashed with out-of-memory (OOMKilled)" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "container crashed with out-of-memory (OOMKilled)" + require.EqualError(t, err, expected) } func TestHttpStrategyFailsWhileGettingPortDueToExitedContainer(t *testing.T) { @@ -567,17 +509,9 @@ func TestHttpStrategyFailsWhileGettingPortDueToExitedContainer(t *testing.T) { WithStartupTimeout(500 * time.Millisecond). WithPollInterval(100 * time.Millisecond) - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "container exited with code 1" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "container exited with code 1" + require.EqualError(t, err, expected) } func TestHttpStrategyFailsWhileGettingPortDueToUnexpectedContainerStatus(t *testing.T) { @@ -620,17 +554,9 @@ func TestHttpStrategyFailsWhileGettingPortDueToUnexpectedContainerStatus(t *test WithStartupTimeout(500 * time.Millisecond). WithPollInterval(100 * time.Millisecond) - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "unexpected container status \"dead\"" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "unexpected container status \"dead\"" + require.EqualError(t, err, expected) } func TestHTTPStrategyFailsWhileRequestSendingDueToOOMKilledContainer(t *testing.T) { @@ -668,17 +594,9 @@ func TestHTTPStrategyFailsWhileRequestSendingDueToOOMKilledContainer(t *testing. WithStartupTimeout(500 * time.Millisecond). WithPollInterval(100 * time.Millisecond) - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "container crashed with out-of-memory (OOMKilled)" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "container crashed with out-of-memory (OOMKilled)" + require.EqualError(t, err, expected) } func TestHttpStrategyFailsWhileRequestSendingDueToExitedContainer(t *testing.T) { @@ -717,17 +635,9 @@ func TestHttpStrategyFailsWhileRequestSendingDueToExitedContainer(t *testing.T) WithStartupTimeout(500 * time.Millisecond). WithPollInterval(100 * time.Millisecond) - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "container exited with code 1" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "container exited with code 1" + require.EqualError(t, err, expected) } func TestHttpStrategyFailsWhileRequestSendingDueToUnexpectedContainerStatus(t *testing.T) { @@ -765,17 +675,9 @@ func TestHttpStrategyFailsWhileRequestSendingDueToUnexpectedContainerStatus(t *t WithStartupTimeout(500 * time.Millisecond). WithPollInterval(100 * time.Millisecond) - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "unexpected container status \"dead\"" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "unexpected container status \"dead\"" + require.EqualError(t, err, expected) } func TestHttpStrategyFailsWhileGettingPortDueToNoExposedPorts(t *testing.T) { @@ -812,17 +714,9 @@ func TestHttpStrategyFailsWhileGettingPortDueToNoExposedPorts(t *testing.T) { WithStartupTimeout(500 * time.Millisecond). WithPollInterval(100 * time.Millisecond) - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "No exposed tcp ports or mapped ports - cannot wait for status" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "No exposed tcp ports or mapped ports - cannot wait for status" + require.EqualError(t, err, expected) } func TestHttpStrategyFailsWhileGettingPortDueToOnlyUDPPorts(t *testing.T) { @@ -866,17 +760,9 @@ func TestHttpStrategyFailsWhileGettingPortDueToOnlyUDPPorts(t *testing.T) { WithStartupTimeout(500 * time.Millisecond). WithPollInterval(100 * time.Millisecond) - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "No exposed tcp ports or mapped ports - cannot wait for status" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "No exposed tcp ports or mapped ports - cannot wait for status" + require.EqualError(t, err, expected) } func TestHttpStrategyFailsWhileGettingPortDueToExposedPortNoBindings(t *testing.T) { @@ -915,15 +801,7 @@ func TestHttpStrategyFailsWhileGettingPortDueToExposedPortNoBindings(t *testing. WithStartupTimeout(500 * time.Millisecond). WithPollInterval(100 * time.Millisecond) - { - err := wg.WaitUntilReady(context.Background(), target) - if err == nil { - t.Fatal("no error") - } - - expected := "No exposed tcp ports or mapped ports - cannot wait for status" - if err.Error() != expected { - t.Fatalf("expected %q, got %q", expected, err.Error()) - } - } + err := wg.WaitUntilReady(context.Background(), target) + expected := "No exposed tcp ports or mapped ports - cannot wait for status" + require.EqualError(t, err, expected) } diff --git a/wait/testdata/main.go b/wait/testdata/main.go index f6f965fe6bb..523278ba0b9 100644 --- a/wait/testdata/main.go +++ b/wait/testdata/main.go @@ -83,7 +83,7 @@ func run() error { go func() { log.Println("serving...") if err := server.ListenAndServeTLS("tls.pem", "tls-key.pem"); err != nil && !errors.Is(err, http.ErrServerClosed) { - log.Fatal(err) + log.Println(err) } }()