diff --git a/.gitignore b/.gitignore index 4b420b86b4..e529356359 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 1791b9caac..861e0cbc5c 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 0000000000..e2d52440b9 --- /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 3cb14ac296..074a818569 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 5f6c415627..dcd962ffc8 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 7e42ff83b9..d494d6d12e 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 11f187c226..65f9e71e07 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 6fcfc92a0b..6a767e6163 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 402d944dce..fdc3196dae 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 d559a3ee7f..18d0e4b007 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 30264a05da..ec33bdb014 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 5660e35757..ed6bbfcd4a 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 a6f67e793c..4b4830eaa3 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 ed514ea5ef..28367d0020 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 fd0e530ea6..6a5718c3e0 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 3d7b8ada48..fe662daf07 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 04be7036c1..7cdb477e6c 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 91a27d9fc8..8173198d1b 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 ead526773d..c66e52550f 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 e7903a9f99..1a226e8c61 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 0cb3f05320..c372d739b8 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 fc1d4052ea..f6f7512ae3 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 9116fd0f65..99c13f5bfa 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 8054ebf96c..4a41f31ce6 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 795a521b29..2344c5063d 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 40360a4c0b..c38e60240d 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 f4b0a2ae37..66c4ad8a8a 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 6265f0a578..855a849914 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 b81cf22c58..f02ab36021 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 fe988afada..31e50981d0 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 2f7774ad7a..351ba5c8d5 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 2c1ddbd8e9..322cb25920 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 9edfcaa527..a8721ce189 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 c2767790ad..01097463b2 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 e1c21f3afc..78f8c2d13d 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 1dcdae4709..c3172fd58a 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 8fe5946e2f..618fc28b0b 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 d2fc9965f0..567685891f 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 826a457a9a..99eaff2d54 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 0d2267c5a4..4dd10ea119 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 c5dbfc61d6..e63d1c7e97 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 a878db4f6f..3c8cd1e918 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 f80cb3f666..68a80589ea 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 d0d633f390..d0919e899f 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 0e33d059f7..5bf44b3282 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 1828c1eef4..a44125b242 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 88b8b82d4b..43b41110b8 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 a581e8d4c5..c14814d3fe 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 d63c440541..bc031f134d 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 e53ef08c6a..4d412f04d3 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 1f5a6df0ad..cc355e9168 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 b427846d4b..c06c97596b 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 d1b18ec3b6..9f21d09e87 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 fab3c5b29d..c374938c32 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 2b24457785..f89c785874 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 a65a30c066..32d323fe3d 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 5bd73a4eab..e061ecf3a4 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 9fee51317e..c873115898 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 518b270613..cc1a09a9db 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 cc14840737..d07defe4ae 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 7f40c3b63f..f7b1f393fd 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 d819e18f5c..87713233d8 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 a511549c78..61787e237b 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 73e430d871..ddbf81b079 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 804efb8483..a4c2376a9b 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 e2bb93b49d..dcc1a8d165 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 10a863c589..5fab503b36 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 1bc7d79456..69c4149fc4 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 db578a46ee..f4ada5df60 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 54363dc2f2..ce8ff1fcbd 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 a39347a23f..957b17e3e5 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 4bea521ff1..134f14d1d6 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 15409e2b0e..553581bcc4 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 92ab671842..caf53e9879 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 0cf04780ef..fa056bbf63 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 7f9ced72f7..297b47f80c 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 54e03e4522..83ccd0464c 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 a5886dc743..7f0e7cdffb 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 00a723f58c..2aac027255 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 07f1f7f404..7283a49502 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 a2a4e74a1c..d57ea35c16 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 e0718a4d03..151df3a546 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 d57154ab1d..8b306db4ce 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 10fdec441f..0e976c3dff 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 c13f1bbd62..a31a6b873f 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 73a94c714b..26c4b8c34d 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 1eced8de79..d80ed84f72 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 1e2f33adba..3cee949938 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 b0a4960616..c6c6d9f0d8 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 7680a9563a..c10f8b5b58 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 beae784557..565bc4253e 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 eb6dc3c493..1cb53cedd7 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 2d37117993..30c892537f 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 d359c8cbbc..609b11467b 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 6ec5ec7399..e04a800dc6 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 da3a23fd92..724d10e24a 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 f6cfb055c4..509719bd64 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 eef8f87280..ef2ca36538 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 7a5fe0d94b..f3970a25da 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 468d113450..c842814d4c 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 58f96d04aa..4dcd9395d9 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 ed514ea5ef..28367d0020 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 591cd48f0c..ea99db3a88 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 ead72dcac4..f2cc39ef72 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 2b547970fc..c275924ecc 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 1148bd00f8..7595e2d7a0 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 9310807a50..9dddd03e0b 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 c0c02890d4..d4f070b8a1 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 1e2e009b58..42fca78650 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 65a55c317b..d503ecee6f 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 961527cd3e..5cfe054a85 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 70797fe3cd..5991c52fbf 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 be643228f6..87eba46080 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 2b5308ddd8..477549fb9c 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 d33970df14..59e168d3e4 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 e8039caf58..a3d2ee8f70 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 e2bb93b49d..dcc1a8d165 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 fae71c7871..1d0b553e41 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 f1863f472c..8e55609ec4 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 79ca1b9812..a8242f15b2 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 9b944f6160..b35cc99335 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 c49f37c92f..c1ad0a070e 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 c13e679388..a1e50b6c84 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 c50c958c21..84d98587fd 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 e837e4f48e..5afc27ffd9 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 c7e2898d9f..6907b1372b 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 60bf8034b3..d8ca857cb3 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 17f4cfffea..a93c8bcbf0 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 e2ba76d72d..36f5772800 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 acf88d134a..16335ff9dd 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 5e8cbe8009..98a31d61fa 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 e7e8c724bc..66311410d7 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 bc45e6d333..7f2154a1b2 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 188c55e85b..4923473593 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 ead2b1818b..663f05cff6 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 10363d9b48..f5e7ebd04d 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 83411fb7aa..80bd7ba219 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 2f512564ff..452ef25661 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 ca30d02385..57f634c02c 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 4e2050385a..602778f8a0 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 bf203c9018..61ee33113d 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 f3f00dcb8b..9e0da8e36a 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 e2bb93b49d..dcc1a8d165 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 7bc6bf7e25..4eee6e654e 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 e40ce9bf58..364f2a97a8 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 56ade42187..b88fba4c4a 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 0a4863c41e..066f3d976f 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 61daa84ab4..ec68d27737 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 0ded01dd09..cd040c09e2 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 e223f0aa24..660473c2e5 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 375184a1d6..6e8c42936a 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 13f310eeb5..50da4a55d4 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 8cccf1cd87..54b5d9c6d5 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 e4f7fc3314..36cac5e4be 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 20bd17188b..466bd3ddf7 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 3e2a273854..46d65ebe0a 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 5e586e858e..b338de0b0d 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 d2ff261f5b..9e9bf8b2c1 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 b8a2fc1de6..203d80103f 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 b60538835b..0f6614a51e 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 38609451ef..cb0443b863 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 388b9c040c..3f5a238a82 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 1ab48ebfe2..a24767f45f 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 ccdaab71cb..d6a3930a1d 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 ec0a16bf1b..85e1966198 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 f3bf2f40f5..757385cc92 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 2f13ed78cc..e096880ed6 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 eb7f8b79e3..0723cad762 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 dc215226c1..940897f4dd 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 40639b9932..b73a8dce36 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 89bc9cb7c6..d2e4c5807d 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 50146f5964..7fe56f884b 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 ed514ea5ef..28367d0020 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 83177c8471..fcc5a1f714 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 64d0db37a5..3829ea2dfb 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 d579068686..8b7f562ea9 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 0ab4d889d1..a6bc418ff3 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 adef0defe3..825a5bdc6f 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 2ee1f3c38e..ad871f1494 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 c1c5c94517..26c57b5742 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 6d911bf9e5..cd04284913 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 eca3adb4ad..85bd2763de 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 85db7229b3..05c20884ee 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 c38866d982..b33ba47727 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 e934403f96..5a2b6d365f 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 2b9bcdbb75..63b95d30ea 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 4471d512d4..bc6a849456 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 e9d4a267fa..46f915a46d 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 bf809122da..5994d5e5b7 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 bcc1a849d7..32d18c09a9 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 9b024d7b5a..f0b82ca2db 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 9fb7c2cf11..e5e83a01a1 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 33ce823994..df75ad7311 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 f98079685f..0fd0a960f9 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 68cb314418..69fb0c9d6a 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 ae15e33001..180edcc48f 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 3ed3932066..686d310d9f 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 09bad2c0d0..09d391f794 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 ada7e33b85..8742456eef 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 b554f8dcc7..6cfa3d537b 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 d40f75125e..6b90349ff3 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 2063903d42..7d5c13a598 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 d3d2d049d6..8d740ce836 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 d39dac7fa4..ffb535bb9f 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 1968ca5d98..cc9ae744dc 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 7a3de29bfd..5823ca9e2f 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 b302fc6326..7e1d261850 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 e3b3728ddd..00f4e91186 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 c440f79179..ed4fd75d12 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 75dc908f6b..0b3d257c06 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 37237748ed..b679d45c21 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 23a52cee57..c55f792c2c 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 d75bd8e66a..97ef8d8fe4 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 eaad237db7..18363db517 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 ed514ea5ef..28367d0020 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 dccedacad1..3338eac0fe 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 f73abda7de..43c0f7e1f5 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 d6c8f50988..2f8e4f84a4 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 6572fb3607..f8b7d6895a 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 93665efc66..3e0830fe3b 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 bb44e7a90a..d6dae679e6 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 533b584feb..b1ac51d305 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 7335b85564..a6b6bec495 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 4ff80e2bed..5cee817ecc 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 e19ecde96e..c8a67b0b06 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 34740eeaf4..0027619b4c 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 122f59a4f7..f937b1e56d 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 1d86a8cdd5..88f14f2d72 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 471736150b..7a82158147 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 e526e8ec9a..f421c2686d 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 fe5af71e89..5ff914051c 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 a62c783f5d..1222b7045f 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 3be3b7c50d..4f268a8aee 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 eab23cb805..41391519de 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 56817d655a..6c50738220 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 13e3e47511..224f7d99d9 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 148907f3db..d9cab7a6e4 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 b349cc0371..a3e9137006 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 c31c3dabc9..bf349f05a7 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 54610f4686..8e30210065 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 f6f965fe6b..523278ba0b 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) } }()