From 58e24a5ce70cfb71290535a8918aad9163e70f05 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Wed, 28 Feb 2024 14:29:36 -0500 Subject: [PATCH 1/5] Move the docker tests into a test package This work has been extracted from #2202 and is related to #2180. See the original PR for the full context and reasoning. This will help with the documentation, since all examples will now have the module prefixes. --- docker.go | 16 +-- docker_test.go | 340 +++++++++++++++++++++++----------------------- lifecycle.go | 20 +-- lifecycle_test.go | 266 +++++++++++++----------------------- provider.go | 14 +- provider_test.go | 39 +++--- reaper_test.go | 28 ++++ 7 files changed, 342 insertions(+), 381 deletions(-) diff --git a/docker.go b/docker.go index a559b8d7ea..032d7322be 100644 --- a/docker.go +++ b/docker.go @@ -919,7 +919,7 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque // If default network is not bridge make sure it is attached to the request // as container won't be attached to it automatically // in case of Podman the bridge network is called 'podman' as 'bridge' would conflict - if p.DefaultNetwork != p.defaultBridgeNetworkName { + if p.DefaultNetwork != p.DefaultBridgeNetworkName { isAttached := false for _, net := range req.Networks { if net == p.DefaultNetwork { @@ -1073,13 +1073,13 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque // default hooks include logger hook and pre-create hook defaultHooks := []ContainerLifecycleHooks{ DefaultLoggingHook(p.Logger), - defaultPreCreateHook(ctx, p, req, dockerInput, hostConfig, networkingConfig), - defaultCopyFileToContainerHook(req.Files), - defaultLogConsumersHook(req.LogConsumerCfg), - defaultReadinessHook(), + DefaultPreCreateHook(p, dockerInput, hostConfig, networkingConfig), + DefaultCopyFileToContainerHook(req.Files), + DefaultLogConsumersHook(req.LogConsumerCfg), + DefaultReadinessHook(), } - req.LifecycleHooks = []ContainerLifecycleHooks{combineContainerHooks(defaultHooks, req.LifecycleHooks)} + req.LifecycleHooks = []ContainerLifecycleHooks{CombineContainerHooks(defaultHooks, req.LifecycleHooks)} err = req.creatingHook(ctx) if err != nil { @@ -1430,8 +1430,8 @@ func (p *DockerProvider) getDefaultNetwork(ctx context.Context, cli client.APICl reaperNetworkExists := false for _, net := range networkResources { - if net.Name == p.defaultBridgeNetworkName { - return p.defaultBridgeNetworkName, nil + if net.Name == p.DefaultBridgeNetworkName { + return p.DefaultBridgeNetworkName, nil } if net.Name == reaperNetwork { diff --git a/docker_test.go b/docker_test.go index 45e276b1ed..1641c320cb 100644 --- a/docker_test.go +++ b/docker_test.go @@ -1,4 +1,4 @@ -package testcontainers +package testcontainers_test import ( "context" @@ -23,6 +23,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) @@ -36,11 +37,11 @@ const ( daemonMaxVersion = "1.41" ) -var providerType = ProviderDocker +var providerType = testcontainers.ProviderDocker func init() { if strings.Contains(os.Getenv("DOCKER_HOST"), "podman.sock") { - providerType = ProviderPodman + providerType = testcontainers.ProviderPodman } } @@ -50,18 +51,18 @@ func TestContainerWithHostNetworkOptions(t *testing.T) { } ctx := context.Background() - SkipIfDockerDesktop(t, ctx) + testcontainers.SkipIfDockerDesktop(t, ctx) absPath, err := filepath.Abs(filepath.Join("testdata", "nginx-highport.conf")) if err != nil { t.Fatal(err) } - gcr := GenericContainerRequest{ + gcr := testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxAlpineImage, - Files: []ContainerFile{ + Files: []testcontainers.ContainerFile{ { HostFilePath: absPath, ContainerFilePath: "/etc/nginx/conf.d/default.conf", @@ -79,7 +80,7 @@ func TestContainerWithHostNetworkOptions(t *testing.T) { Started: true, } - nginxC, err := GenericContainer(ctx, gcr) + nginxC, err := testcontainers.GenericContainer(ctx, gcr) require.NoError(t, err) terminateContainerOnEnd(t, ctx, nginxC) @@ -101,8 +102,8 @@ func TestContainerWithHostNetworkOptions(t *testing.T) { func TestContainerWithHostNetworkOptions_UseExposePortsFromImageConfigs(t *testing.T) { ctx := context.Background() - gcr := GenericContainerRequest{ - ContainerRequest: ContainerRequest{ + gcr := testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: "nginx", Privileged: true, WaitingFor: wait.ForExposedPort(), @@ -110,7 +111,7 @@ func TestContainerWithHostNetworkOptions_UseExposePortsFromImageConfigs(t *testi Started: true, } - nginxC, err := GenericContainer(ctx, gcr) + nginxC, err := testcontainers.GenericContainer(ctx, gcr) if err != nil { t.Fatal(err) } @@ -140,12 +141,12 @@ func TestContainerWithNetworkModeAndNetworkTogether(t *testing.T) { // skipIfDockerDesktop { ctx := context.Background() - SkipIfDockerDesktop(t, ctx) + testcontainers.SkipIfDockerDesktop(t, ctx) // } - gcr := GenericContainerRequest{ + gcr := testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxImage, Networks: []string{"new-network"}, HostConfigModifier: func(hc *container.HostConfig) { @@ -155,7 +156,7 @@ func TestContainerWithNetworkModeAndNetworkTogether(t *testing.T) { Started: true, } - nginx, err := GenericContainer(ctx, gcr) + nginx, err := testcontainers.GenericContainer(ctx, gcr) if err != nil { // Error when NetworkMode = host and Network = []string{"bridge"} t.Logf("Can't use Network and NetworkMode together, %s\n", err) @@ -169,19 +170,19 @@ func TestContainerWithHostNetwork(t *testing.T) { } ctx := context.Background() - SkipIfDockerDesktop(t, ctx) + testcontainers.SkipIfDockerDesktop(t, ctx) absPath, err := filepath.Abs(filepath.Join("testdata", "nginx-highport.conf")) if err != nil { t.Fatal(err) } - gcr := GenericContainerRequest{ + gcr := testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxAlpineImage, WaitingFor: wait.ForListeningPort(nginxHighPort), - Files: []ContainerFile{ + Files: []testcontainers.ContainerFile{ { HostFilePath: absPath, ContainerFilePath: "/etc/nginx/conf.d/default.conf", @@ -194,7 +195,7 @@ func TestContainerWithHostNetwork(t *testing.T) { Started: true, } - nginxC, err := GenericContainer(ctx, gcr) + nginxC, err := testcontainers.GenericContainer(ctx, gcr) require.NoError(t, err) terminateContainerOnEnd(t, ctx, nginxC) @@ -223,9 +224,9 @@ func TestContainerWithHostNetwork(t *testing.T) { func TestContainerReturnItsContainerID(t *testing.T) { ctx := context.Background() - nginxA, err := GenericContainer(ctx, GenericContainerRequest{ + nginxA, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxAlpineImage, ExposedPorts: []string{ nginxDefaultPort, @@ -244,9 +245,9 @@ func TestContainerReturnItsContainerID(t *testing.T) { func TestContainerTerminationResetsState(t *testing.T) { ctx := context.Background() - nginxA, err := GenericContainer(ctx, GenericContainerRequest{ + nginxA, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxAlpineImage, ExposedPorts: []string{ nginxDefaultPort, @@ -272,10 +273,10 @@ func TestContainerTerminationResetsState(t *testing.T) { } func TestContainerStateAfterTermination(t *testing.T) { - createContainerFn := func(ctx context.Context) (Container, error) { - return GenericContainer(ctx, GenericContainerRequest{ + createContainerFn := func(ctx context.Context) (testcontainers.Container, error) { + return testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxAlpineImage, ExposedPorts: []string{ nginxDefaultPort, @@ -332,15 +333,15 @@ func TestContainerStateAfterTermination(t *testing.T) { func TestContainerTerminationRemovesDockerImage(t *testing.T) { t.Run("if not built from Dockerfile", func(t *testing.T) { ctx := context.Background() - client, err := NewDockerClientWithOpts(ctx) + client, err := testcontainers.NewDockerClientWithOpts(ctx) if err != nil { t.Fatal(err) } defer client.Close() - container, err := GenericContainer(ctx, GenericContainerRequest{ + container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxAlpineImage, ExposedPorts: []string{ nginxDefaultPort, @@ -363,20 +364,20 @@ func TestContainerTerminationRemovesDockerImage(t *testing.T) { t.Run("if built from Dockerfile", func(t *testing.T) { ctx := context.Background() - client, err := NewDockerClientWithOpts(ctx) + client, err := testcontainers.NewDockerClientWithOpts(ctx) if err != nil { t.Fatal(err) } defer client.Close() - req := ContainerRequest{ - FromDockerfile: FromDockerfile{ + req := testcontainers.ContainerRequest{ + FromDockerfile: testcontainers.FromDockerfile{ Context: filepath.Join(".", "testdata"), }, ExposedPorts: []string{"6379/tcp"}, WaitingFor: wait.ForLog("Ready to accept connections"), } - container, err := GenericContainer(ctx, GenericContainerRequest{ + container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, @@ -405,9 +406,9 @@ func TestContainerTerminationRemovesDockerImage(t *testing.T) { func TestTwoContainersExposingTheSamePort(t *testing.T) { ctx := context.Background() - nginxA, err := GenericContainer(ctx, GenericContainerRequest{ + nginxA, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxAlpineImage, ExposedPorts: []string{ nginxDefaultPort, @@ -419,9 +420,9 @@ func TestTwoContainersExposingTheSamePort(t *testing.T) { require.NoError(t, err) terminateContainerOnEnd(t, ctx, nginxA) - nginxB, err := GenericContainer(ctx, GenericContainerRequest{ + nginxB, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxAlpineImage, ExposedPorts: []string{ nginxDefaultPort, @@ -466,9 +467,9 @@ func TestTwoContainersExposingTheSamePort(t *testing.T) { func TestContainerCreation(t *testing.T) { ctx := context.Background() - nginxC, err := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxAlpineImage, ExposedPorts: []string{ nginxDefaultPort, @@ -520,9 +521,9 @@ func TestContainerCreationWithName(t *testing.T) { creationName := fmt.Sprintf("%s_%d", "test_container", time.Now().Unix()) expectedName := "/" + creationName // inspect adds '/' in the beginning - nginxC, err := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxAlpineImage, ExposedPorts: []string{ nginxDefaultPort, @@ -553,13 +554,13 @@ func TestContainerCreationWithName(t *testing.T) { } network := networks[0] switch providerType { - case ProviderDocker: - if network != Bridge { - t.Errorf("Expected network name '%s'. Got '%s'.", Bridge, network) + case testcontainers.ProviderDocker: + if network != testcontainers.Bridge { + t.Errorf("Expected network name '%s'. Got '%s'.", testcontainers.Bridge, network) } - case ProviderPodman: - if network != Podman { - t.Errorf("Expected network name '%s'. Got '%s'.", Podman, network) + case testcontainers.ProviderPodman: + if network != testcontainers.Podman { + t.Errorf("Expected network name '%s'. Got '%s'.", testcontainers.Podman, network) } } @@ -581,9 +582,9 @@ func TestContainerCreationAndWaitForListeningPortLongEnough(t *testing.T) { ctx := context.Background() // delayed-nginx will wait 2s before opening port - nginxC, err := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxDelayedImage, ExposedPorts: []string{ nginxDefaultPort, @@ -614,9 +615,9 @@ func TestContainerCreationAndWaitForListeningPortLongEnough(t *testing.T) { func TestContainerCreationTimesOut(t *testing.T) { ctx := context.Background() // delayed-nginx will wait 2s before opening port - nginxC, err := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxDelayedImage, ExposedPorts: []string{ nginxDefaultPort, @@ -636,9 +637,9 @@ func TestContainerCreationTimesOut(t *testing.T) { func TestContainerRespondsWithHttp200ForIndex(t *testing.T) { ctx := context.Background() - nginxC, err := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxAlpineImage, ExposedPorts: []string{ nginxDefaultPort, @@ -669,9 +670,9 @@ func TestContainerRespondsWithHttp200ForIndex(t *testing.T) { func TestContainerCreationTimesOutWithHttp(t *testing.T) { ctx := context.Background() // delayed-nginx will wait 2s before opening port - nginxC, err := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxDelayedImage, ExposedPorts: []string{ nginxDefaultPort, @@ -689,7 +690,7 @@ func TestContainerCreationTimesOutWithHttp(t *testing.T) { func TestContainerCreationWaitsForLogContextTimeout(t *testing.T) { ctx := context.Background() - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: mysqlImage, ExposedPorts: []string{"3306/tcp", "33060/tcp"}, Env: map[string]string{ @@ -698,7 +699,7 @@ func TestContainerCreationWaitsForLogContextTimeout(t *testing.T) { }, WaitingFor: wait.ForLog("test context timeout").WithStartupTimeout(1 * time.Second), } - c, err := GenericContainer(ctx, GenericContainerRequest{ + c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, @@ -712,7 +713,7 @@ func TestContainerCreationWaitsForLogContextTimeout(t *testing.T) { func TestContainerCreationWaitsForLog(t *testing.T) { ctx := context.Background() - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: mysqlImage, ExposedPorts: []string{"3306/tcp", "33060/tcp"}, Env: map[string]string{ @@ -721,7 +722,7 @@ func TestContainerCreationWaitsForLog(t *testing.T) { }, WaitingFor: wait.ForLog("port: 3306 MySQL Community Server - GPL"), } - mysqlC, err := GenericContainer(ctx, GenericContainerRequest{ + mysqlC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, @@ -740,8 +741,8 @@ func Test_BuildContainerFromDockerfileWithBuildArgs(t *testing.T) { // fromDockerfileWithBuildArgs { ba := "build args value" - req := ContainerRequest{ - FromDockerfile: FromDockerfile{ + req := testcontainers.ContainerRequest{ + FromDockerfile: testcontainers.FromDockerfile{ Context: filepath.Join(".", "testdata"), Dockerfile: "args.Dockerfile", BuildArgs: map[string]*string{ @@ -753,13 +754,13 @@ func Test_BuildContainerFromDockerfileWithBuildArgs(t *testing.T) { } // } - genContainerReq := GenericContainerRequest{ + genContainerReq := testcontainers.GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, } - c, err := GenericContainer(ctx, genContainerReq) + c, err := testcontainers.GenericContainer(ctx, genContainerReq) require.NoError(t, err) terminateContainerOnEnd(t, ctx, c) @@ -794,8 +795,8 @@ func Test_BuildContainerFromDockerfileWithBuildLog(t *testing.T) { t.Log("got ctx, creating container request") // fromDockerfile { - req := ContainerRequest{ - FromDockerfile: FromDockerfile{ + req := testcontainers.ContainerRequest{ + FromDockerfile: testcontainers.FromDockerfile{ Context: filepath.Join(".", "testdata"), Dockerfile: "buildlog.Dockerfile", PrintBuildLog: true, @@ -803,13 +804,13 @@ func Test_BuildContainerFromDockerfileWithBuildLog(t *testing.T) { } // } - genContainerReq := GenericContainerRequest{ + genContainerReq := testcontainers.GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, } - c, err := GenericContainer(ctx, genContainerReq) + c, err := testcontainers.GenericContainer(ctx, genContainerReq) require.NoError(t, err) terminateContainerOnEnd(t, ctx, c) @@ -826,7 +827,7 @@ func Test_BuildContainerFromDockerfileWithBuildLog(t *testing.T) { func TestContainerCreationWaitsForLogAndPortContextTimeout(t *testing.T) { ctx := context.Background() - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: mysqlImage, ExposedPorts: []string{"3306/tcp", "33060/tcp"}, Env: map[string]string{ @@ -838,7 +839,7 @@ func TestContainerCreationWaitsForLogAndPortContextTimeout(t *testing.T) { wait.ForListeningPort("3306/tcp"), ), } - c, err := GenericContainer(ctx, GenericContainerRequest{ + c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, @@ -853,13 +854,13 @@ func TestContainerCreationWaitsForLogAndPortContextTimeout(t *testing.T) { func TestContainerCreationWaitingForHostPort(t *testing.T) { ctx := context.Background() // exposePorts { - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: nginxAlpineImage, ExposedPorts: []string{nginxDefaultPort}, WaitingFor: wait.ForListeningPort(nginxDefaultPort), } // } - nginx, err := GenericContainer(ctx, GenericContainerRequest{ + nginx, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, @@ -871,12 +872,12 @@ func TestContainerCreationWaitingForHostPort(t *testing.T) { func TestContainerCreationWaitingForHostPortWithoutBashThrowsAnError(t *testing.T) { ctx := context.Background() - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: nginxAlpineImage, ExposedPorts: []string{nginxDefaultPort}, WaitingFor: wait.ForListeningPort(nginxDefaultPort), } - nginx, err := GenericContainer(ctx, GenericContainerRequest{ + nginx, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, @@ -895,7 +896,7 @@ func TestCMD(t *testing.T) { ctx := context.Background() - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: "docker.io/alpine", WaitingFor: wait.ForAll( wait.ForLog("command override!"), @@ -903,7 +904,7 @@ func TestCMD(t *testing.T) { Cmd: []string{"echo", "command override!"}, } - c, err := GenericContainer(ctx, GenericContainerRequest{ + c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, @@ -922,7 +923,7 @@ func TestEntrypoint(t *testing.T) { ctx := context.Background() - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: "docker.io/alpine", WaitingFor: wait.ForAll( wait.ForLog("entrypoint override!"), @@ -930,7 +931,7 @@ func TestEntrypoint(t *testing.T) { Entrypoint: []string{"echo", "entrypoint override!"}, } - c, err := GenericContainer(ctx, GenericContainerRequest{ + c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, @@ -949,7 +950,7 @@ func TestWorkingDir(t *testing.T) { ctx := context.Background() - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: "docker.io/alpine", WaitingFor: wait.ForAll( wait.ForLog("/var/tmp/test"), @@ -958,7 +959,7 @@ func TestWorkingDir(t *testing.T) { WorkingDir: "/var/tmp/test", } - c, err := GenericContainer(ctx, GenericContainerRequest{ + c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, @@ -970,12 +971,12 @@ func TestWorkingDir(t *testing.T) { func ExampleDockerProvider_CreateContainer() { ctx := context.Background() - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: "docker.io/nginx:alpine", ExposedPorts: []string{"80/tcp"}, WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second), } - nginxC, _ := GenericContainer(ctx, GenericContainerRequest{ + nginxC, _ := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) @@ -998,12 +999,12 @@ func ExampleDockerProvider_CreateContainer() { func ExampleContainer_Host() { ctx := context.Background() - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: "docker.io/nginx:alpine", ExposedPorts: []string{"80/tcp"}, WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second), } - nginxC, _ := GenericContainer(ctx, GenericContainerRequest{ + nginxC, _ := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) @@ -1030,12 +1031,12 @@ func ExampleContainer_Host() { func ExampleContainer_Start() { ctx := context.Background() - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: "docker.io/nginx:alpine", ExposedPorts: []string{"80/tcp"}, WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second), } - nginxC, _ := GenericContainer(ctx, GenericContainerRequest{ + nginxC, _ := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, }) defer func() { @@ -1058,12 +1059,12 @@ func ExampleContainer_Start() { func ExampleContainer_Stop() { ctx := context.Background() - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: "docker.io/nginx:alpine", ExposedPorts: []string{"80/tcp"}, WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second), } - nginxC, _ := GenericContainer(ctx, GenericContainerRequest{ + nginxC, _ := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, }) defer func() { @@ -1087,12 +1088,12 @@ func ExampleContainer_Stop() { func ExampleContainer_MappedPort() { ctx := context.Background() - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: "docker.io/nginx:alpine", ExposedPorts: []string{"80/tcp"}, WaitingFor: wait.ForHTTP("/").WithStartupTimeout(10 * time.Second), } - nginxC, _ := GenericContainer(ctx, GenericContainerRequest{ + nginxC, _ := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) @@ -1130,17 +1131,17 @@ func TestContainerCreationWithVolumeAndFileWritingToIt(t *testing.T) { volumeName := "volumeName" // Create the container that writes into the mounted volume. - bashC, err := GenericContainer(ctx, GenericContainerRequest{ + bashC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: "docker.io/bash", - Files: []ContainerFile{ + Files: []testcontainers.ContainerFile{ { HostFilePath: absPath, ContainerFilePath: "/hello.sh", }, }, - Mounts: Mounts(VolumeMount(volumeName, "/data")), + Mounts: testcontainers.Mounts(testcontainers.VolumeMount(volumeName, "/data")), Cmd: []string{"bash", "/hello.sh"}, WaitingFor: wait.ForLog("done"), }, @@ -1153,13 +1154,13 @@ func TestContainerCreationWithVolumeAndFileWritingToIt(t *testing.T) { func TestContainerWithTmpFs(t *testing.T) { ctx := context.Background() - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: "docker.io/busybox", Cmd: []string{"sleep", "10"}, Tmpfs: map[string]string{"/testtmpfs": "rw"}, } - container, err := GenericContainer(ctx, GenericContainerRequest{ + container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, @@ -1210,8 +1211,8 @@ 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{ - ContainerRequest: ContainerRequest{ + _, err := testcontainers.GenericContainer(context.Background(), testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: "postgres:nonexistent-version", }, Started: true, @@ -1226,9 +1227,9 @@ func TestContainerNonExistentImage(t *testing.T) { t.Run("the context cancellation is propagated to container creation", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() - c, err := GenericContainer(ctx, GenericContainerRequest{ + c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: "docker.io/postgres:12", WaitingFor: wait.ForLog("log"), }, @@ -1243,7 +1244,7 @@ func TestContainerNonExistentImage(t *testing.T) { } func TestContainerCustomPlatformImage(t *testing.T) { - if providerType == ProviderPodman { + if providerType == testcontainers.ProviderPodman { t.Skip("Incompatible Docker API version for Podman") } t.Run("error with a non-existent platform", func(t *testing.T) { @@ -1251,9 +1252,9 @@ func TestContainerCustomPlatformImage(t *testing.T) { nonExistentPlatform := "windows/arm12" ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() - c, err := GenericContainer(ctx, GenericContainerRequest{ + c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: "docker.io/redis:latest", ImagePlatform: nonExistentPlatform, }, @@ -1269,9 +1270,9 @@ func TestContainerCustomPlatformImage(t *testing.T) { t.Parallel() ctx := context.Background() - c, err := GenericContainer(ctx, GenericContainerRequest{ + c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: "docker.io/mysql:8.0.36", ImagePlatform: "linux/amd64", }, @@ -1281,7 +1282,7 @@ func TestContainerCustomPlatformImage(t *testing.T) { require.NoError(t, err) terminateContainerOnEnd(t, ctx, c) - dockerCli, err := NewDockerClientWithOpts(ctx) + dockerCli, err := testcontainers.NewDockerClientWithOpts(ctx) require.NoError(t, err) defer dockerCli.Close() @@ -1299,12 +1300,12 @@ func TestContainerWithCustomHostname(t *testing.T) { ctx := context.Background() name := fmt.Sprintf("some-nginx-%s-%d", t.Name(), rand.Int()) hostname := fmt.Sprintf("my-nginx-%s-%d", t.Name(), rand.Int()) - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Name: name, Image: nginxImage, Hostname: hostname, } - container, err := GenericContainer(ctx, GenericContainerRequest{ + container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, @@ -1319,7 +1320,7 @@ func TestContainerWithCustomHostname(t *testing.T) { } func readHostname(tb testing.TB, containerId string) string { - containerClient, err := NewDockerClientWithOpts(context.Background()) + containerClient, err := testcontainers.NewDockerClientWithOpts(context.Background()) if err != nil { tb.Fatalf("Failed to create Docker client: %v", err) } @@ -1352,9 +1353,9 @@ func TestDockerContainerCopyFileToContainer(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - nginxC, err := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxImage, ExposedPorts: []string{nginxDefaultPort}, WaitingFor: wait.ForListeningPort(nginxDefaultPort), @@ -1380,9 +1381,9 @@ func TestDockerContainerCopyFileToContainer(t *testing.T) { func TestDockerContainerCopyDirToContainer(t *testing.T) { ctx := context.Background() - nginxC, err := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxImage, ExposedPorts: []string{nginxDefaultPort}, WaitingFor: wait.ForListeningPort(nginxDefaultPort), @@ -1412,12 +1413,12 @@ func TestDockerCreateContainerWithFiles(t *testing.T) { copiedFileName := "/hello_copy.sh" tests := []struct { name string - files []ContainerFile + files []testcontainers.ContainerFile errMsg string }{ { name: "success copy", - files: []ContainerFile{ + files: []testcontainers.ContainerFile{ { HostFilePath: hostFileName, ContainerFilePath: copiedFileName, @@ -1427,7 +1428,7 @@ func TestDockerCreateContainerWithFiles(t *testing.T) { }, { name: "host file not found", - files: []ContainerFile{ + files: []testcontainers.ContainerFile{ { HostFilePath: hostFileName + "123", ContainerFilePath: copiedFileName, @@ -1441,8 +1442,8 @@ func TestDockerCreateContainerWithFiles(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - nginxC, err := GenericContainer(ctx, GenericContainerRequest{ - ContainerRequest: ContainerRequest{ + nginxC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: "nginx:1.17.6", ExposedPorts: []string{"80/tcp"}, WaitingFor: wait.ForListeningPort("80/tcp"), @@ -1483,12 +1484,12 @@ func TestDockerCreateContainerWithDirs(t *testing.T) { tests := []struct { name string - dir ContainerFile + dir testcontainers.ContainerFile hasError bool }{ { name: "success copy directory with full path", - dir: ContainerFile{ + dir: testcontainers.ContainerFile{ HostFilePath: abs, ContainerFilePath: "/tmp/" + hostDirName, // the parent dir must exist FileMode: 700, @@ -1497,7 +1498,7 @@ func TestDockerCreateContainerWithDirs(t *testing.T) { }, { name: "success copy directory", - dir: ContainerFile{ + dir: testcontainers.ContainerFile{ HostFilePath: filepath.Join(".", hostDirName), ContainerFilePath: "/tmp/" + hostDirName, // the parent dir must exist FileMode: 700, @@ -1506,7 +1507,7 @@ func TestDockerCreateContainerWithDirs(t *testing.T) { }, { name: "host dir not found", - dir: ContainerFile{ + dir: testcontainers.ContainerFile{ HostFilePath: filepath.Join(".", "testdata123"), // does not exist ContainerFilePath: "/tmp/" + hostDirName, // the parent dir must exist FileMode: 700, @@ -1515,7 +1516,7 @@ func TestDockerCreateContainerWithDirs(t *testing.T) { }, { name: "container dir not found", - dir: ContainerFile{ + dir: testcontainers.ContainerFile{ HostFilePath: filepath.Join(".", hostDirName), ContainerFilePath: "/parent-does-not-exist/testdata123", // does not exist FileMode: 700, @@ -1526,12 +1527,12 @@ func TestDockerCreateContainerWithDirs(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - nginxC, err := GenericContainer(ctx, GenericContainerRequest{ - ContainerRequest: ContainerRequest{ + nginxC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: "nginx:1.17.6", ExposedPorts: []string{"80/tcp"}, WaitingFor: wait.ForListeningPort("80/tcp"), - Files: []ContainerFile{tc.dir}, + Files: []testcontainers.ContainerFile{tc.dir}, }, Started: false, }) @@ -1566,9 +1567,9 @@ func TestDockerContainerCopyToContainer(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - nginxC, err := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxImage, ExposedPorts: []string{nginxDefaultPort}, WaitingFor: wait.ForListeningPort(nginxDefaultPort), @@ -1605,9 +1606,9 @@ func TestDockerContainerCopyFileFromContainer(t *testing.T) { } ctx := context.Background() - nginxC, err := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxImage, ExposedPorts: []string{nginxDefaultPort}, WaitingFor: wait.ForListeningPort(nginxDefaultPort), @@ -1644,9 +1645,9 @@ func TestDockerContainerCopyFileFromContainer(t *testing.T) { func TestDockerContainerCopyEmptyFileFromContainer(t *testing.T) { ctx := context.Background() - nginxC, err := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxImage, ExposedPorts: []string{nginxDefaultPort}, WaitingFor: wait.ForListeningPort(nginxDefaultPort), @@ -1681,7 +1682,7 @@ func TestDockerContainerCopyEmptyFileFromContainer(t *testing.T) { } func TestDockerContainerResources(t *testing.T) { - if providerType == ProviderPodman { + if providerType == testcontainers.ProviderPodman { t.Skip("Rootless Podman does not support setting rlimit") } if os.Getenv("XDG_RUNTIME_DIR") != "" { @@ -1703,9 +1704,9 @@ func TestDockerContainerResources(t *testing.T) { }, } - nginxC, err := GenericContainer(ctx, GenericContainerRequest{ + nginxC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxAlpineImage, ExposedPorts: []string{nginxDefaultPort}, WaitingFor: wait.ForListeningPort(nginxDefaultPort), @@ -1721,7 +1722,7 @@ func TestDockerContainerResources(t *testing.T) { require.NoError(t, err) terminateContainerOnEnd(t, ctx, nginxC) - c, err := NewDockerClientWithOpts(ctx) + c, err := testcontainers.NewDockerClientWithOpts(ctx) require.NoError(t, err) defer c.Close() @@ -1734,7 +1735,7 @@ func TestDockerContainerResources(t *testing.T) { } func TestContainerCapAdd(t *testing.T) { - if providerType == ProviderPodman { + if providerType == testcontainers.ProviderPodman { t.Skip("Rootless Podman does not support setting cap-add/cap-drop") } @@ -1742,9 +1743,9 @@ func TestContainerCapAdd(t *testing.T) { expected := "IPC_LOCK" - nginx, err := GenericContainer(ctx, GenericContainerRequest{ + nginx, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxAlpineImage, ExposedPorts: []string{nginxDefaultPort}, WaitingFor: wait.ForListeningPort(nginxDefaultPort), @@ -1757,7 +1758,7 @@ func TestContainerCapAdd(t *testing.T) { require.NoError(t, err) terminateContainerOnEnd(t, ctx, nginx) - dockerClient, err := NewDockerClientWithOpts(ctx) + dockerClient, err := testcontainers.NewDockerClientWithOpts(ctx) require.NoError(t, err) defer dockerClient.Close() @@ -1770,7 +1771,7 @@ func TestContainerCapAdd(t *testing.T) { func TestContainerRunningCheckingStatusCode(t *testing.T) { ctx := context.Background() - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: "influxdb:1.8.10-alpine", ExposedPorts: []string{"8086/tcp"}, ImagePlatform: "linux/amd64", // influxdb doesn't provide an alpine+arm build (https://github.com/influxdata/influxdata-docker/issues/335) @@ -1783,7 +1784,7 @@ func TestContainerRunningCheckingStatusCode(t *testing.T) { ), } - influx, err := GenericContainer(ctx, GenericContainerRequest{ + influx, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) @@ -1796,13 +1797,13 @@ func TestContainerRunningCheckingStatusCode(t *testing.T) { func TestContainerWithUserID(t *testing.T) { ctx := context.Background() - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: "docker.io/alpine:latest", User: "60125", Cmd: []string{"sh", "-c", "id -u"}, WaitingFor: wait.ForExit(), } - container, err := GenericContainer(ctx, GenericContainerRequest{ + container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, @@ -1826,12 +1827,12 @@ func TestContainerWithUserID(t *testing.T) { func TestContainerWithNoUserID(t *testing.T) { ctx := context.Background() - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: "docker.io/alpine:latest", Cmd: []string{"sh", "-c", "id -u"}, WaitingFor: wait.ForExit(), } - container, err := GenericContainer(ctx, GenericContainerRequest{ + container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Started: true, @@ -1856,13 +1857,13 @@ func TestContainerWithNoUserID(t *testing.T) { func TestGetGatewayIP(t *testing.T) { // When using docker-compose with DinD mode, and using host port or http wait strategy // It's need to invoke GetGatewayIP for get the host - provider, err := providerType.GetProvider(WithLogger(TestLogger(t))) + provider, err := providerType.GetProvider(testcontainers.WithLogger(testcontainers.TestLogger(t))) if err != nil { t.Fatal(err) } defer provider.Close() - ip, err := provider.(*DockerProvider).GetGatewayIP(context.Background()) + ip, err := provider.(*testcontainers.DockerProvider).GetGatewayIP(context.Background()) if err != nil { t.Fatal(err) } @@ -1872,7 +1873,7 @@ func TestGetGatewayIP(t *testing.T) { } func TestProviderHasConfig(t *testing.T) { - provider, err := NewDockerProvider(WithLogger(TestLogger(t))) + provider, err := testcontainers.NewDockerProvider(testcontainers.WithLogger(testcontainers.TestLogger(t))) if err != nil { t.Fatal(err) } @@ -1883,9 +1884,9 @@ func TestProviderHasConfig(t *testing.T) { func TestNetworkModeWithContainerReference(t *testing.T) { ctx := context.Background() - nginxA, err := GenericContainer(ctx, GenericContainerRequest{ + nginxA, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxAlpineImage, }, Started: true, @@ -1895,9 +1896,9 @@ func TestNetworkModeWithContainerReference(t *testing.T) { terminateContainerOnEnd(t, ctx, nginxA) networkMode := fmt.Sprintf("container:%v", nginxA.GetContainerID()) - nginxB, err := GenericContainer(ctx, GenericContainerRequest{ + nginxB, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Image: nginxAlpineImage, HostConfigModifier: func(hc *container.HostConfig) { hc.NetworkMode = container.NetworkMode(networkMode) @@ -1911,7 +1912,7 @@ func TestNetworkModeWithContainerReference(t *testing.T) { } // 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 -func assertExtractedFiles(t *testing.T, ctx context.Context, container Container, hostFilePath string, containerFilePath string) { +func assertExtractedFiles(t *testing.T, ctx context.Context, container testcontainers.Container, hostFilePath string, containerFilePath string) { // create all copied files into a temporary dir tmpDir := t.TempDir() @@ -1955,7 +1956,7 @@ func assertExtractedFiles(t *testing.T, ctx context.Context, container Container } } -func terminateContainerOnEnd(tb testing.TB, ctx context.Context, ctr Container) { +func terminateContainerOnEnd(tb testing.TB, ctx context.Context, ctr testcontainers.Container) { tb.Helper() if ctr == nil { return @@ -1966,15 +1967,15 @@ func terminateContainerOnEnd(tb testing.TB, ctx context.Context, ctr Container) }) } -func TestDockerProviderFindContainerByName(t *testing.T) { +func TestDockerProviderReuseOrCreateContainer(t *testing.T) { ctx := context.Background() - provider, err := NewDockerProvider(WithLogger(TestLogger(t))) + provider, err := testcontainers.NewDockerProvider(testcontainers.WithLogger(testcontainers.TestLogger(t))) require.NoError(t, err) defer provider.Close() - c1, err := GenericContainer(ctx, GenericContainerRequest{ + c1, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Name: "test", Image: "nginx:1.17.6", WaitingFor: wait.ForExposedPort(), @@ -1986,9 +1987,9 @@ func TestDockerProviderFindContainerByName(t *testing.T) { require.NoError(t, err) terminateContainerOnEnd(t, ctx, c1) - c2, err := GenericContainer(ctx, GenericContainerRequest{ + c2, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ Name: "test2", Image: "nginx:1.17.6", WaitingFor: wait.ForExposedPort(), @@ -1998,10 +1999,15 @@ func TestDockerProviderFindContainerByName(t *testing.T) { require.NoError(t, err) terminateContainerOnEnd(t, ctx, c2) - c, err := provider.findContainerByName(ctx, "test") + c, err := provider.ReuseOrCreateContainer(ctx, testcontainers.ContainerRequest{ + Name: "test", + }) + require.NoError(t, err) + + foundName, err := c.Name(ctx) require.NoError(t, err) require.NotNil(t, c) - assert.Contains(t, c.Names, c1Name) + assert.Contains(t, foundName, c1Name) } func TestImageBuiltFromDockerfile_KeepBuiltImage(t *testing.T) { @@ -2016,15 +2022,15 @@ func TestImageBuiltFromDockerfile_KeepBuiltImage(t *testing.T) { t.Run(fmt.Sprintf("Keep built image: %t", tt.keepBuiltImage), func(t *testing.T) { ctx := context.Background() // Set up CLI. - provider, err := NewDockerProvider() + provider, err := testcontainers.NewDockerProvider() require.NoError(t, err, "get docker provider should not fail") defer func() { _ = provider.Close() }() cli := provider.Client() // Create container. - c, err := GenericContainer(ctx, GenericContainerRequest{ + c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, - ContainerRequest: ContainerRequest{ - FromDockerfile: FromDockerfile{ + ContainerRequest: testcontainers.ContainerRequest{ + FromDockerfile: testcontainers.FromDockerfile{ Context: "testdata", Dockerfile: "echo.Dockerfile", KeepImage: tt.keepBuiltImage, diff --git a/lifecycle.go b/lifecycle.go index fc1b28e17e..25bfeda087 100644 --- a/lifecycle.go +++ b/lifecycle.go @@ -112,8 +112,8 @@ var DefaultLoggingHook = func(logger Logging) ContainerLifecycleHooks { } } -// defaultPreCreateHook is a hook that will apply the default configuration to the container -var defaultPreCreateHook = func(ctx context.Context, p *DockerProvider, req ContainerRequest, dockerInput *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig) ContainerLifecycleHooks { +// DefaultPreCreateHook is a hook that will apply the default configuration to the container +func DefaultPreCreateHook(p *DockerProvider, dockerInput *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig) ContainerLifecycleHooks { return ContainerLifecycleHooks{ PreCreates: []ContainerRequestHook{ func(ctx context.Context, req ContainerRequest) error { @@ -123,9 +123,9 @@ var defaultPreCreateHook = func(ctx context.Context, p *DockerProvider, req Cont } } -// defaultCopyFileToContainerHook is a hook that will copy files to the container after it's created +// DefaultCopyFileToContainerHook is a hook that will copy files to the container after it's created // but before it's started -var defaultCopyFileToContainerHook = func(files []ContainerFile) ContainerLifecycleHooks { +func DefaultCopyFileToContainerHook(files []ContainerFile) ContainerLifecycleHooks { return ContainerLifecycleHooks{ PostCreates: []ContainerHook{ // copy files to container after it's created @@ -143,8 +143,8 @@ var defaultCopyFileToContainerHook = func(files []ContainerFile) ContainerLifecy } } -// defaultLogConsumersHook is a hook that will start log consumers after the container is started -var defaultLogConsumersHook = func(cfg *LogConsumerConfig) ContainerLifecycleHooks { +// DefaultLogConsumersHook is a hook that will start log consumers after the container is started +func DefaultLogConsumersHook(cfg *LogConsumerConfig) ContainerLifecycleHooks { return ContainerLifecycleHooks{ PostStarts: []ContainerHook{ // first post-start hook is to produce logs and start log consumers @@ -180,8 +180,8 @@ var defaultLogConsumersHook = func(cfg *LogConsumerConfig) ContainerLifecycleHoo } } -// defaultReadinessHook is a hook that will wait for the container to be ready -var defaultReadinessHook = func() ContainerLifecycleHooks { +// DefaultReadinessHook is a hook that will wait for the container to be ready +func DefaultReadinessHook() ContainerLifecycleHooks { return ContainerLifecycleHooks{ PostStarts: []ContainerHook{ // wait for the container to be ready @@ -475,13 +475,13 @@ func (p *DockerProvider) preCreateContainerHook(ctx context.Context, req Contain return nil } -// combineContainerHooks it returns just one ContainerLifecycle hook, as the result of combining +// CombineContainerHooks it returns just one ContainerLifecycle hook, as the result of combining // the default hooks with the user-defined hooks. The function will loop over all the default hooks, // storing each of the hooks in a slice, and then it will loop over all the user-defined hooks, // appending or prepending them to the slice of hooks. The order of hooks is the following: // - for Pre-hooks, always run the default hooks first, then append the user-defined hooks // - for Post-hooks, always run the user-defined hooks first, then the default hooks -func combineContainerHooks(defaultHooks, userDefinedHooks []ContainerLifecycleHooks) ContainerLifecycleHooks { +func CombineContainerHooks(defaultHooks, userDefinedHooks []ContainerLifecycleHooks) ContainerLifecycleHooks { preCreates := []ContainerRequestHook{} postCreates := []ContainerHook{} preStarts := []ContainerHook{} diff --git a/lifecycle_test.go b/lifecycle_test.go index 6316df739e..e5f6ee2d7b 100644 --- a/lifecycle_test.go +++ b/lifecycle_test.go @@ -1,4 +1,4 @@ -package testcontainers +package testcontainers_test import ( "bufio" @@ -16,29 +16,30 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) func TestPreCreateModifierHook(t *testing.T) { ctx := context.Background() - provider, err := NewDockerProvider() + provider, err := testcontainers.NewDockerProvider() require.NoError(t, err) defer provider.Close() t.Run("No exposed ports", func(t *testing.T) { // reqWithModifiers { - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: nginxAlpineImage, // alpine image does expose port 80 ConfigModifier: func(config *container.Config) { config.Env = []string{"a=b"} }, - Mounts: ContainerMounts{ + Mounts: testcontainers.ContainerMounts{ { - Source: DockerVolumeMountSource{ + Source: testcontainers.DockerVolumeMountSource{ Name: "appdata", VolumeOptions: &mount.VolumeOptions{ - Labels: GenericLabels(), + Labels: testcontainers.GenericLabels(), }, }, Target: "/data", @@ -70,7 +71,8 @@ func TestPreCreateModifierHook(t *testing.T) { inputHostConfig := &container.HostConfig{} inputNetworkingConfig := &network.NetworkingConfig{} - err = provider.preCreateContainerHook(ctx, req, inputConfig, inputHostConfig, inputNetworkingConfig) + hooks := testcontainers.DefaultPreCreateHook(provider, inputConfig, inputHostConfig, inputNetworkingConfig) + err = hooks.Creating(ctx)(req) require.NoError(t, err) // assertions @@ -94,7 +96,7 @@ func TestPreCreateModifierHook(t *testing.T) { Source: "appdata", Target: "/data", VolumeOptions: &mount.VolumeOptions{ - Labels: GenericLabels(), + Labels: testcontainers.GenericLabels(), }, }, }, @@ -128,7 +130,7 @@ func TestPreCreateModifierHook(t *testing.T) { }) t.Run("No exposed ports and network mode IsContainer", func(t *testing.T) { - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: nginxAlpineImage, // alpine image does expose port 80 HostConfigModifier: func(hostConfig *container.HostConfig) { hostConfig.PortBindings = nat.PortMap{ @@ -150,7 +152,8 @@ func TestPreCreateModifierHook(t *testing.T) { inputHostConfig := &container.HostConfig{} inputNetworkingConfig := &network.NetworkingConfig{} - err = provider.preCreateContainerHook(ctx, req, inputConfig, inputHostConfig, inputNetworkingConfig) + hooks := testcontainers.DefaultPreCreateHook(provider, inputConfig, inputHostConfig, inputNetworkingConfig) + err = hooks.Creating(ctx)(req) require.NoError(t, err) // assertions @@ -169,7 +172,7 @@ func TestPreCreateModifierHook(t *testing.T) { }) t.Run("Nil hostConfigModifier should apply default host config modifier", func(t *testing.T) { - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: nginxAlpineImage, // alpine image does expose port 80 AutoRemove: true, CapAdd: []string{"addFoo", "addBar"}, @@ -191,7 +194,8 @@ func TestPreCreateModifierHook(t *testing.T) { inputHostConfig := &container.HostConfig{} inputNetworkingConfig := &network.NetworkingConfig{} - err = provider.preCreateContainerHook(ctx, req, inputConfig, inputHostConfig, inputNetworkingConfig) + hooks := testcontainers.DefaultPreCreateHook(provider, inputConfig, inputHostConfig, inputNetworkingConfig) + err = hooks.Creating(ctx)(req) require.NoError(t, err) // assertions @@ -206,7 +210,7 @@ func TestPreCreateModifierHook(t *testing.T) { t.Run("Request contains more than one network including aliases", func(t *testing.T) { networkName := "foo" - net, err := provider.CreateNetwork(ctx, NetworkRequest{ + net, err := provider.CreateNetwork(ctx, testcontainers.NetworkRequest{ Name: networkName, }) require.NoError(t, err) @@ -217,12 +221,12 @@ func TestPreCreateModifierHook(t *testing.T) { } }() - dockerNetwork, err := provider.GetNetwork(ctx, NetworkRequest{ + dockerNetwork, err := provider.GetNetwork(ctx, testcontainers.NetworkRequest{ Name: networkName, }) require.NoError(t, err) - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: nginxAlpineImage, // alpine image does expose port 80 Networks: []string{networkName, "bar"}, NetworkAliases: map[string][]string{ @@ -237,7 +241,8 @@ func TestPreCreateModifierHook(t *testing.T) { inputHostConfig := &container.HostConfig{} inputNetworkingConfig := &network.NetworkingConfig{} - err = provider.preCreateContainerHook(ctx, req, inputConfig, inputHostConfig, inputNetworkingConfig) + hooks := testcontainers.DefaultPreCreateHook(provider, inputConfig, inputHostConfig, inputNetworkingConfig) + err = hooks.Creating(ctx)(req) require.NoError(t, err) // assertions @@ -258,7 +263,7 @@ func TestPreCreateModifierHook(t *testing.T) { t.Run("Request contains more than one network without aliases", func(t *testing.T) { networkName := "foo" - net, err := provider.CreateNetwork(ctx, NetworkRequest{ + net, err := provider.CreateNetwork(ctx, testcontainers.NetworkRequest{ Name: networkName, }) require.NoError(t, err) @@ -269,12 +274,12 @@ func TestPreCreateModifierHook(t *testing.T) { } }() - dockerNetwork, err := provider.GetNetwork(ctx, NetworkRequest{ + dockerNetwork, err := provider.GetNetwork(ctx, testcontainers.NetworkRequest{ Name: networkName, }) require.NoError(t, err) - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: nginxAlpineImage, // alpine image does expose port 80 Networks: []string{networkName, "bar"}, } @@ -286,7 +291,8 @@ func TestPreCreateModifierHook(t *testing.T) { inputHostConfig := &container.HostConfig{} inputNetworkingConfig := &network.NetworkingConfig{} - err = provider.preCreateContainerHook(ctx, req, inputConfig, inputHostConfig, inputNetworkingConfig) + hooks := testcontainers.DefaultPreCreateHook(provider, inputConfig, inputHostConfig, inputNetworkingConfig) + err = hooks.Creating(ctx)(req) require.NoError(t, err) // assertions @@ -305,7 +311,7 @@ func TestPreCreateModifierHook(t *testing.T) { }) t.Run("Request contains exposed port modifiers", func(t *testing.T) { - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: nginxAlpineImage, // alpine image does expose port 80 HostConfigModifier: func(hostConfig *container.HostConfig) { hostConfig.PortBindings = nat.PortMap{ @@ -327,7 +333,8 @@ func TestPreCreateModifierHook(t *testing.T) { inputHostConfig := &container.HostConfig{} inputNetworkingConfig := &network.NetworkingConfig{} - err = provider.preCreateContainerHook(ctx, req, inputConfig, inputHostConfig, inputNetworkingConfig) + hooks := testcontainers.DefaultPreCreateHook(provider, inputConfig, inputHostConfig, inputNetworkingConfig) + err = hooks.Creating(ctx)(req) require.NoError(t, err) // assertions @@ -336,95 +343,6 @@ func TestPreCreateModifierHook(t *testing.T) { }) } -func TestMergePortBindings(t *testing.T) { - type arg struct { - configPortMap nat.PortMap - parsedPortMap nat.PortMap - exposedPorts []string - } - cases := []struct { - name string - arg arg - expected nat.PortMap - }{ - { - name: "empty ports", - arg: arg{ - configPortMap: nil, - parsedPortMap: nil, - exposedPorts: nil, - }, - expected: map[nat.Port][]nat.PortBinding{}, - }, - { - name: "config port map but not exposed", - arg: arg{ - configPortMap: map[nat.Port][]nat.PortBinding{ - "80/tcp": {{HostIP: "1", HostPort: "2"}}, - }, - parsedPortMap: nil, - exposedPorts: nil, - }, - expected: map[nat.Port][]nat.PortBinding{}, - }, - { - name: "parsed port map without config", - arg: arg{ - configPortMap: nil, - parsedPortMap: map[nat.Port][]nat.PortBinding{ - "80/tcp": {{HostIP: "", HostPort: ""}}, - }, - exposedPorts: nil, - }, - expected: map[nat.Port][]nat.PortBinding{ - "80/tcp": {{HostIP: "", HostPort: ""}}, - }, - }, - { - name: "parsed and configured but not exposed", - arg: arg{ - configPortMap: map[nat.Port][]nat.PortBinding{ - "80/tcp": {{HostIP: "1", HostPort: "2"}}, - }, - parsedPortMap: map[nat.Port][]nat.PortBinding{ - "80/tcp": {{HostIP: "", HostPort: ""}}, - }, - exposedPorts: nil, - }, - expected: map[nat.Port][]nat.PortBinding{ - "80/tcp": {{HostIP: "", HostPort: ""}}, - }, - }, - { - name: "merge both parsed and config", - arg: arg{ - configPortMap: map[nat.Port][]nat.PortBinding{ - "60/tcp": {{HostIP: "1", HostPort: "2"}}, - "70/tcp": {{HostIP: "1", HostPort: "2"}}, - "80/tcp": {{HostIP: "1", HostPort: "2"}}, - }, - parsedPortMap: map[nat.Port][]nat.PortBinding{ - "80/tcp": {{HostIP: "", HostPort: ""}}, - "90/tcp": {{HostIP: "", HostPort: ""}}, - }, - exposedPorts: []string{"70", "80"}, - }, - expected: map[nat.Port][]nat.PortBinding{ - "70/tcp": {{HostIP: "1", HostPort: "2"}}, - "80/tcp": {{HostIP: "1", HostPort: "2"}}, - "90/tcp": {{HostIP: "", HostPort: ""}}, - }, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - res := mergePortBindings(c.arg.configPortMap, c.arg.parsedPortMap, c.arg.exposedPorts) - assert.Equal(t, c.expected, res) - }) - } -} - func TestLifecycleHooks(t *testing.T) { tests := []struct { name string @@ -445,96 +363,96 @@ func TestLifecycleHooks(t *testing.T) { prints := []string{} ctx := context.Background() // reqWithLifecycleHooks { - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: nginxAlpineImage, - LifecycleHooks: []ContainerLifecycleHooks{ + LifecycleHooks: []testcontainers.ContainerLifecycleHooks{ { - PreCreates: []ContainerRequestHook{ - func(ctx context.Context, req ContainerRequest) error { + PreCreates: []testcontainers.ContainerRequestHook{ + func(ctx context.Context, req testcontainers.ContainerRequest) error { prints = append(prints, fmt.Sprintf("pre-create hook 1: %#v", req)) return nil }, - func(ctx context.Context, req ContainerRequest) error { + func(ctx context.Context, req testcontainers.ContainerRequest) error { prints = append(prints, fmt.Sprintf("pre-create hook 2: %#v", req)) return nil }, }, - PostCreates: []ContainerHook{ - func(ctx context.Context, c Container) error { + PostCreates: []testcontainers.ContainerHook{ + func(ctx context.Context, c testcontainers.Container) error { prints = append(prints, fmt.Sprintf("post-create hook 1: %#v", c)) return nil }, - func(ctx context.Context, c Container) error { + func(ctx context.Context, c testcontainers.Container) error { prints = append(prints, fmt.Sprintf("post-create hook 2: %#v", c)) return nil }, }, - PreStarts: []ContainerHook{ - func(ctx context.Context, c Container) error { + PreStarts: []testcontainers.ContainerHook{ + func(ctx context.Context, c testcontainers.Container) error { prints = append(prints, fmt.Sprintf("pre-start hook 1: %#v", c)) return nil }, - func(ctx context.Context, c Container) error { + func(ctx context.Context, c testcontainers.Container) error { prints = append(prints, fmt.Sprintf("pre-start hook 2: %#v", c)) return nil }, }, - PostStarts: []ContainerHook{ - func(ctx context.Context, c Container) error { + PostStarts: []testcontainers.ContainerHook{ + func(ctx context.Context, c testcontainers.Container) error { prints = append(prints, fmt.Sprintf("post-start hook 1: %#v", c)) return nil }, - func(ctx context.Context, c Container) error { + func(ctx context.Context, c testcontainers.Container) error { prints = append(prints, fmt.Sprintf("post-start hook 2: %#v", c)) return nil }, }, - PostReadies: []ContainerHook{ - func(ctx context.Context, c Container) error { + PostReadies: []testcontainers.ContainerHook{ + func(ctx context.Context, c testcontainers.Container) error { prints = append(prints, fmt.Sprintf("post-ready hook 1: %#v", c)) return nil }, - func(ctx context.Context, c Container) error { + func(ctx context.Context, c testcontainers.Container) error { prints = append(prints, fmt.Sprintf("post-ready hook 2: %#v", c)) return nil }, }, - PreStops: []ContainerHook{ - func(ctx context.Context, c Container) error { + PreStops: []testcontainers.ContainerHook{ + func(ctx context.Context, c testcontainers.Container) error { prints = append(prints, fmt.Sprintf("pre-stop hook 1: %#v", c)) return nil }, - func(ctx context.Context, c Container) error { + func(ctx context.Context, c testcontainers.Container) error { prints = append(prints, fmt.Sprintf("pre-stop hook 2: %#v", c)) return nil }, }, - PostStops: []ContainerHook{ - func(ctx context.Context, c Container) error { + PostStops: []testcontainers.ContainerHook{ + func(ctx context.Context, c testcontainers.Container) error { prints = append(prints, fmt.Sprintf("post-stop hook 1: %#v", c)) return nil }, - func(ctx context.Context, c Container) error { + func(ctx context.Context, c testcontainers.Container) error { prints = append(prints, fmt.Sprintf("post-stop hook 2: %#v", c)) return nil }, }, - PreTerminates: []ContainerHook{ - func(ctx context.Context, c Container) error { + PreTerminates: []testcontainers.ContainerHook{ + func(ctx context.Context, c testcontainers.Container) error { prints = append(prints, fmt.Sprintf("pre-terminate hook 1: %#v", c)) return nil }, - func(ctx context.Context, c Container) error { + func(ctx context.Context, c testcontainers.Container) error { prints = append(prints, fmt.Sprintf("pre-terminate hook 2: %#v", c)) return nil }, }, - PostTerminates: []ContainerHook{ - func(ctx context.Context, c Container) error { + PostTerminates: []testcontainers.ContainerHook{ + func(ctx context.Context, c testcontainers.Container) error { prints = append(prints, fmt.Sprintf("post-terminate hook 1: %#v", c)) return nil }, - func(ctx context.Context, c Container) error { + func(ctx context.Context, c testcontainers.Container) error { prints = append(prints, fmt.Sprintf("post-terminate hook 2: %#v", c)) return nil }, @@ -548,7 +466,7 @@ func TestLifecycleHooks(t *testing.T) { req.Name = "reuse-container" } - c, err := GenericContainer(ctx, GenericContainerRequest{ + c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Reuse: tt.reuse, Started: true, @@ -588,15 +506,15 @@ func TestLifecycleHooks_WithDefaultLogger(t *testing.T) { // reqWithDefaultLogginHook { dl := inMemoryLogger{} - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: nginxAlpineImage, - LifecycleHooks: []ContainerLifecycleHooks{ - DefaultLoggingHook(&dl), + LifecycleHooks: []testcontainers.ContainerLifecycleHooks{ + testcontainers.DefaultLoggingHook(&dl), }, } // } - c, err := GenericContainer(ctx, GenericContainerRequest{ + c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) @@ -619,51 +537,51 @@ func TestLifecycleHooks_WithDefaultLogger(t *testing.T) { func TestCombineLifecycleHooks(t *testing.T) { prints := []string{} - preCreateFunc := func(prefix string, hook string, lifecycleID int, hookID int) func(ctx context.Context, req ContainerRequest) error { - return func(ctx context.Context, _ ContainerRequest) error { + preCreateFunc := func(prefix string, hook string, lifecycleID int, hookID int) func(ctx context.Context, req testcontainers.ContainerRequest) error { + return func(ctx context.Context, _ testcontainers.ContainerRequest) error { prints = append(prints, fmt.Sprintf("[%s] pre-%s hook %d.%d", prefix, hook, lifecycleID, hookID)) return nil } } - hookFunc := func(prefix string, hookType string, hook string, lifecycleID int, hookID int) func(ctx context.Context, c Container) error { - return func(ctx context.Context, _ Container) error { + hookFunc := func(prefix string, hookType string, hook string, lifecycleID int, hookID int) func(ctx context.Context, c testcontainers.Container) error { + return func(ctx context.Context, _ testcontainers.Container) error { prints = append(prints, fmt.Sprintf("[%s] %s-%s hook %d.%d", prefix, hookType, hook, lifecycleID, hookID)) return nil } } - preFunc := func(prefix string, hook string, lifecycleID int, hookID int) func(ctx context.Context, c Container) error { + preFunc := func(prefix string, hook string, lifecycleID int, hookID int) func(ctx context.Context, c testcontainers.Container) error { return hookFunc(prefix, "pre", hook, lifecycleID, hookID) } - postFunc := func(prefix string, hook string, lifecycleID int, hookID int) func(ctx context.Context, c Container) error { + postFunc := func(prefix string, hook string, lifecycleID int, hookID int) func(ctx context.Context, c testcontainers.Container) error { return hookFunc(prefix, "post", hook, lifecycleID, hookID) } - lifecycleHookFunc := func(prefix string, lifecycleID int) ContainerLifecycleHooks { - return ContainerLifecycleHooks{ - PreCreates: []ContainerRequestHook{preCreateFunc(prefix, "create", lifecycleID, 1), preCreateFunc(prefix, "create", lifecycleID, 2)}, - PostCreates: []ContainerHook{postFunc(prefix, "create", lifecycleID, 1), postFunc(prefix, "create", lifecycleID, 2)}, - PreStarts: []ContainerHook{preFunc(prefix, "start", lifecycleID, 1), preFunc(prefix, "start", lifecycleID, 2)}, - PostStarts: []ContainerHook{postFunc(prefix, "start", lifecycleID, 1), postFunc(prefix, "start", lifecycleID, 2)}, - PostReadies: []ContainerHook{postFunc(prefix, "ready", lifecycleID, 1), postFunc(prefix, "ready", lifecycleID, 2)}, - PreStops: []ContainerHook{preFunc(prefix, "stop", lifecycleID, 1), preFunc(prefix, "stop", lifecycleID, 2)}, - PostStops: []ContainerHook{postFunc(prefix, "stop", lifecycleID, 1), postFunc(prefix, "stop", lifecycleID, 2)}, - PreTerminates: []ContainerHook{preFunc(prefix, "terminate", lifecycleID, 1), preFunc(prefix, "terminate", lifecycleID, 2)}, - PostTerminates: []ContainerHook{postFunc(prefix, "terminate", lifecycleID, 1), postFunc(prefix, "terminate", lifecycleID, 2)}, + lifecycleHookFunc := func(prefix string, lifecycleID int) testcontainers.ContainerLifecycleHooks { + return testcontainers.ContainerLifecycleHooks{ + PreCreates: []testcontainers.ContainerRequestHook{preCreateFunc(prefix, "create", lifecycleID, 1), preCreateFunc(prefix, "create", lifecycleID, 2)}, + PostCreates: []testcontainers.ContainerHook{postFunc(prefix, "create", lifecycleID, 1), postFunc(prefix, "create", lifecycleID, 2)}, + PreStarts: []testcontainers.ContainerHook{preFunc(prefix, "start", lifecycleID, 1), preFunc(prefix, "start", lifecycleID, 2)}, + PostStarts: []testcontainers.ContainerHook{postFunc(prefix, "start", lifecycleID, 1), postFunc(prefix, "start", lifecycleID, 2)}, + PostReadies: []testcontainers.ContainerHook{postFunc(prefix, "ready", lifecycleID, 1), postFunc(prefix, "ready", lifecycleID, 2)}, + PreStops: []testcontainers.ContainerHook{preFunc(prefix, "stop", lifecycleID, 1), preFunc(prefix, "stop", lifecycleID, 2)}, + PostStops: []testcontainers.ContainerHook{postFunc(prefix, "stop", lifecycleID, 1), postFunc(prefix, "stop", lifecycleID, 2)}, + PreTerminates: []testcontainers.ContainerHook{preFunc(prefix, "terminate", lifecycleID, 1), preFunc(prefix, "terminate", lifecycleID, 2)}, + PostTerminates: []testcontainers.ContainerHook{postFunc(prefix, "terminate", lifecycleID, 1), postFunc(prefix, "terminate", lifecycleID, 2)}, } } - defaultHooks := []ContainerLifecycleHooks{lifecycleHookFunc("default", 1), lifecycleHookFunc("default", 2)} - userDefinedHooks := []ContainerLifecycleHooks{lifecycleHookFunc("user-defined", 1), lifecycleHookFunc("user-defined", 2), lifecycleHookFunc("user-defined", 3)} + defaultHooks := []testcontainers.ContainerLifecycleHooks{lifecycleHookFunc("default", 1), lifecycleHookFunc("default", 2)} + userDefinedHooks := []testcontainers.ContainerLifecycleHooks{lifecycleHookFunc("user-defined", 1), lifecycleHookFunc("user-defined", 2), lifecycleHookFunc("user-defined", 3)} - hooks := combineContainerHooks(defaultHooks, userDefinedHooks) + hooks := testcontainers.CombineContainerHooks(defaultHooks, userDefinedHooks) // call all the hooks in the right order, honouring the lifecycle - req := ContainerRequest{} + req := testcontainers.ContainerRequest{} err := hooks.Creating(context.Background())(req) require.NoError(t, err) - c := &DockerContainer{} + c := &testcontainers.DockerContainer{} err = hooks.Created(context.Background())(c) require.NoError(t, err) @@ -754,15 +672,15 @@ func TestLifecycleHooks_WithMultipleHooks(t *testing.T) { dl := inMemoryLogger{} - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: nginxAlpineImage, - LifecycleHooks: []ContainerLifecycleHooks{ - DefaultLoggingHook(&dl), - DefaultLoggingHook(&dl), + LifecycleHooks: []testcontainers.ContainerLifecycleHooks{ + testcontainers.DefaultLoggingHook(&dl), + testcontainers.DefaultLoggingHook(&dl), }, } - c, err := GenericContainer(ctx, GenericContainerRequest{ + c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) @@ -793,7 +711,7 @@ func (l *linesTestLogger) Printf(format string, args ...interface{}) { func TestPrintContainerLogsOnError(t *testing.T) { ctx := context.Background() - req := ContainerRequest{ + req := testcontainers.ContainerRequest{ Image: "docker.io/alpine", Cmd: []string{"echo", "-n", "I am expecting this"}, WaitingFor: wait.ForLog("I was expecting that").WithStartupTimeout(5 * time.Second), @@ -803,7 +721,7 @@ func TestPrintContainerLogsOnError(t *testing.T) { data: []string{}, } - container, err := GenericContainer(ctx, GenericContainerRequest{ + container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ProviderType: providerType, ContainerRequest: req, Logger: &arrayOfLinesLogger, diff --git a/provider.go b/provider.go index 1be1ebe20a..7e10be4808 100644 --- a/provider.go +++ b/provider.go @@ -7,6 +7,8 @@ import ( "os" "strings" + "github.com/docker/docker/client" + "github.com/testcontainers/testcontainers-go/internal/core" ) @@ -38,7 +40,7 @@ type ( // DockerProviderOptions defines options applicable to DockerProvider DockerProviderOptions struct { - defaultBridgeNetworkName string + DefaultBridgeNetworkName string *GenericProviderOptions } @@ -74,7 +76,7 @@ func Generic2DockerOptions(opts ...GenericProviderOption) []DockerProviderOption func WithDefaultBridgeNetwork(bridgeNetworkName string) DockerProviderOption { return DockerProviderOptionFunc(func(opts *DockerProviderOptions) { - opts.defaultBridgeNetworkName = bridgeNetworkName + opts.DefaultBridgeNetworkName = bridgeNetworkName }) } @@ -128,6 +130,12 @@ func (t ProviderType) GetProvider(opts ...GenericProviderOption) (GenericProvide // NewDockerProvider creates a Docker provider with the EnvClient func NewDockerProvider(provOpts ...DockerProviderOption) (*DockerProvider, error) { + return NewDockerProviderWithClientOpts([]client.Opt{}, provOpts...) +} + +// NewDockerProviderWithClientOpts creates a Docker provider with the EnvClient using the provided client opts +// for the docker client. +func NewDockerProviderWithClientOpts(clientOpts []client.Opt, provOpts ...DockerProviderOption) (*DockerProvider, error) { o := &DockerProviderOptions{ GenericProviderOptions: &GenericProviderOptions{ Logger: Logger, @@ -139,7 +147,7 @@ func NewDockerProvider(provOpts ...DockerProviderOption) (*DockerProvider, error } ctx := context.Background() - c, err := NewDockerClientWithOpts(ctx) + c, err := NewDockerClientWithOpts(ctx, clientOpts...) if err != nil { return nil, err } diff --git a/provider_test.go b/provider_test.go index 2448d84c07..39b67f38a9 100644 --- a/provider_test.go +++ b/provider_test.go @@ -1,9 +1,10 @@ -package testcontainers +package testcontainers_test import ( "context" "testing" + "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/internal/core" ) @@ -13,52 +14,52 @@ func TestProviderTypeGetProviderAutodetect(t *testing.T) { tests := []struct { name string - tr ProviderType + tr testcontainers.ProviderType DockerHost string want string wantErr bool }{ { name: "default provider without podman.socket", - tr: ProviderDefault, + tr: testcontainers.ProviderDefault, DockerHost: dockerHost, - want: Bridge, + want: testcontainers.Bridge, }, { name: "default provider with podman.socket", - tr: ProviderDefault, + tr: testcontainers.ProviderDefault, DockerHost: podmanSocket, - want: Podman, + want: testcontainers.Podman, }, { name: "docker provider without podman.socket", - tr: ProviderDocker, + tr: testcontainers.ProviderDocker, DockerHost: dockerHost, - want: Bridge, + want: testcontainers.Bridge, }, { // Explicitly setting Docker provider should not be overridden by auto-detect name: "docker provider with podman.socket", - tr: ProviderDocker, + tr: testcontainers.ProviderDocker, DockerHost: podmanSocket, - want: Bridge, + want: testcontainers.Bridge, }, { name: "Podman provider without podman.socket", - tr: ProviderPodman, + tr: testcontainers.ProviderPodman, DockerHost: dockerHost, - want: Podman, + want: testcontainers.Podman, }, { name: "Podman provider with podman.socket", - tr: ProviderPodman, + tr: testcontainers.ProviderPodman, DockerHost: podmanSocket, - want: Podman, + want: testcontainers.Podman, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.tr == ProviderPodman && core.IsWindows() { + if tt.tr == testcontainers.ProviderPodman && core.IsWindows() { t.Skip("Podman provider is not implemented for Windows") } @@ -69,12 +70,12 @@ func TestProviderTypeGetProviderAutodetect(t *testing.T) { t.Errorf("ProviderType.GetProvider() error = %v, wantErr %v", err, tt.wantErr) return } - provider, ok := got.(*DockerProvider) + provider, ok := got.(*testcontainers.DockerProvider) if !ok { - t.Fatalf("ProviderType.GetProvider() = %T, want %T", got, &DockerProvider{}) + t.Fatalf("ProviderType.GetProvider() = %T, want %T", got, &testcontainers.DockerProvider{}) } - if provider.defaultBridgeNetworkName != tt.want { - t.Errorf("ProviderType.GetProvider() = %v, want %v", provider.defaultBridgeNetworkName, tt.want) + if provider.DefaultBridgeNetworkName != tt.want { + t.Errorf("ProviderType.GetProvider() = %v, want %v", provider.DefaultBridgeNetworkName, tt.want) } }) } diff --git a/reaper_test.go b/reaper_test.go index d0b6f4fc7c..e9d3e68452 100644 --- a/reaper_test.go +++ b/reaper_test.go @@ -1,9 +1,12 @@ +// This test is testing very internal logic that should not be exported away from this package. We'll +// leave it in the main testcontainers package. Do not use for user facing examples. package testcontainers import ( "context" "errors" "os" + "strings" "sync" "testing" "time" @@ -19,6 +22,31 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) +const ( + nginxAlpineImage = "docker.io/nginx:alpine" + nginxDefaultPort = "80/tcp" + daemonMaxVersion = "1.41" +) + +var providerType = ProviderDocker + +func init() { + if strings.Contains(os.Getenv("DOCKER_HOST"), "podman.sock") { + providerType = ProviderPodman + } +} + +func terminateContainerOnEnd(tb testing.TB, ctx context.Context, ctr Container) { + tb.Helper() + if ctr == nil { + return + } + tb.Cleanup(func() { + tb.Log("terminating container") + require.NoError(tb, ctr.Terminate(ctx)) + }) +} + // testSessionID the tests need to create a reaper in a different session, so that it does not interfere with other tests const testSessionID = "this-is-a-different-session-id" From 3b18b5fd8d5ce3f7c0077e204b3f2de9c84fe693 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Wed, 28 Feb 2024 14:46:32 -0500 Subject: [PATCH 2/5] Fix small linting issues due to renamed/moves variables --- docker.go | 6 +++--- docker_test.go | 1 - reaper_test.go | 7 ++++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docker.go b/docker.go index 6d59ac327b..e9ad6709d8 100644 --- a/docker.go +++ b/docker.go @@ -1211,8 +1211,8 @@ func (p *DockerProvider) ReuseOrCreateContainer(ctx context.Context, req Contain // default hooks include logger hook and pre-create hook defaultHooks := []ContainerLifecycleHooks{ DefaultLoggingHook(p.Logger), - defaultReadinessHook(), - defaultLogConsumersHook(req.LogConsumerCfg), + DefaultReadinessHook(), + DefaultLogConsumersHook(req.LogConsumerCfg), } dc := &DockerContainer{ @@ -1224,7 +1224,7 @@ func (p *DockerProvider) ReuseOrCreateContainer(ctx context.Context, req Contain terminationSignal: termSignal, stopLogProductionCh: nil, logger: p.Logger, - lifecycleHooks: []ContainerLifecycleHooks{combineContainerHooks(defaultHooks, req.LifecycleHooks)}, + lifecycleHooks: []ContainerLifecycleHooks{CombineContainerHooks(defaultHooks, req.LifecycleHooks)}, } err = dc.startedHook(ctx) diff --git a/docker_test.go b/docker_test.go index 1641c320cb..3cbe560429 100644 --- a/docker_test.go +++ b/docker_test.go @@ -34,7 +34,6 @@ const ( nginxAlpineImage = "docker.io/nginx:alpine" nginxDefaultPort = "80/tcp" nginxHighPort = "8080/tcp" - daemonMaxVersion = "1.41" ) var providerType = testcontainers.ProviderDocker diff --git a/reaper_test.go b/reaper_test.go index e9d3e68452..1385f7f65c 100644 --- a/reaper_test.go +++ b/reaper_test.go @@ -23,9 +23,10 @@ import ( ) const ( - nginxAlpineImage = "docker.io/nginx:alpine" - nginxDefaultPort = "80/tcp" - daemonMaxVersion = "1.41" + nginxAlpineImage = "docker.io/nginx:alpine" + nginxDelayedImage = "docker.io/menedev/delayed-nginx:1.15.2" + nginxDefaultPort = "80/tcp" + daemonMaxVersion = "1.41" ) var providerType = ProviderDocker From 2f20304bbcd6ef52418ae330efb70be4af90cef4 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Mon, 4 Mar 2024 09:42:35 -0500 Subject: [PATCH 3/5] Move everything to test helpers for now --- docker_test.go | 28 ---------------------------- testhelpers_test.go | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/docker_test.go b/docker_test.go index 3cbe560429..5eeeeb93f1 100644 --- a/docker_test.go +++ b/docker_test.go @@ -27,23 +27,6 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) -const ( - mysqlImage = "docker.io/mysql:8.0.36" - nginxDelayedImage = "docker.io/menedev/delayed-nginx:1.15.2" - nginxImage = "docker.io/nginx" - nginxAlpineImage = "docker.io/nginx:alpine" - nginxDefaultPort = "80/tcp" - nginxHighPort = "8080/tcp" -) - -var providerType = testcontainers.ProviderDocker - -func init() { - if strings.Contains(os.Getenv("DOCKER_HOST"), "podman.sock") { - providerType = testcontainers.ProviderPodman - } -} - func TestContainerWithHostNetworkOptions(t *testing.T) { if os.Getenv("XDG_RUNTIME_DIR") != "" { t.Skip("Skipping test that requires host network access when running in a container") @@ -1955,17 +1938,6 @@ func assertExtractedFiles(t *testing.T, ctx context.Context, container testconta } } -func terminateContainerOnEnd(tb testing.TB, ctx context.Context, ctr testcontainers.Container) { - tb.Helper() - if ctr == nil { - return - } - tb.Cleanup(func() { - tb.Log("terminating container") - require.NoError(tb, ctr.Terminate(ctx)) - }) -} - func TestDockerProviderReuseOrCreateContainer(t *testing.T) { ctx := context.Background() provider, err := testcontainers.NewDockerProvider(testcontainers.WithLogger(testcontainers.TestLogger(t))) diff --git a/testhelpers_test.go b/testhelpers_test.go index 480cf857a3..dc18c13ee7 100644 --- a/testhelpers_test.go +++ b/testhelpers_test.go @@ -2,6 +2,8 @@ package testcontainers_test import ( "context" + "os" + "strings" "testing" "github.com/stretchr/testify/require" @@ -9,6 +11,23 @@ import ( "github.com/testcontainers/testcontainers-go" ) +const ( + mysqlImage = "docker.io/mysql:8.0.36" + nginxDelayedImage = "docker.io/menedev/delayed-nginx:1.15.2" + nginxImage = "docker.io/nginx" + nginxAlpineImage = "docker.io/nginx:alpine" + nginxDefaultPort = "80/tcp" + nginxHighPort = "8080/tcp" +) + +var providerType = testcontainers.ProviderDocker + +func init() { + if strings.Contains(os.Getenv("DOCKER_HOST"), "podman.sock") { + providerType = testcontainers.ProviderPodman + } +} + func terminateContainerOnEnd(tb testing.TB, ctx context.Context, ctr testcontainers.Container) { tb.Helper() if ctr == nil { From b4c209cf6114e4ef46c80088a43f4cf21cba438d Mon Sep 17 00:00:00 2001 From: Guillaume Date: Mon, 18 Mar 2024 10:11:28 -0400 Subject: [PATCH 4/5] Separate the internal and external lifecycle hooks tests --- docker.go | 4 +- lifecycle.go | 4 +- lifecycle_hooks_test.go | 247 ++++++++++++++++++++++++++++++++++++++++ lifecycle_test.go | 232 ------------------------------------- 4 files changed, 251 insertions(+), 236 deletions(-) create mode 100644 lifecycle_hooks_test.go diff --git a/docker.go b/docker.go index 043f267bce..c2619e6bcc 100644 --- a/docker.go +++ b/docker.go @@ -1082,7 +1082,7 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque DefaultReadinessHook(), } - req.LifecycleHooks = []ContainerLifecycleHooks{CombineContainerHooks(defaultHooks, req.LifecycleHooks)} + req.LifecycleHooks = []ContainerLifecycleHooks{combineContainerHooks(defaultHooks, req.LifecycleHooks)} err = req.creatingHook(ctx) if err != nil { @@ -1224,7 +1224,7 @@ func (p *DockerProvider) ReuseOrCreateContainer(ctx context.Context, req Contain terminationSignal: termSignal, stopLogProductionCh: nil, logger: p.Logger, - lifecycleHooks: []ContainerLifecycleHooks{CombineContainerHooks(defaultHooks, req.LifecycleHooks)}, + lifecycleHooks: []ContainerLifecycleHooks{combineContainerHooks(defaultHooks, req.LifecycleHooks)}, } err = dc.startedHook(ctx) diff --git a/lifecycle.go b/lifecycle.go index 25bfeda087..6d21bbb68b 100644 --- a/lifecycle.go +++ b/lifecycle.go @@ -475,13 +475,13 @@ func (p *DockerProvider) preCreateContainerHook(ctx context.Context, req Contain return nil } -// CombineContainerHooks it returns just one ContainerLifecycle hook, as the result of combining +// combineContainerHooks it returns just one ContainerLifecycle hook, as the result of combining // the default hooks with the user-defined hooks. The function will loop over all the default hooks, // storing each of the hooks in a slice, and then it will loop over all the user-defined hooks, // appending or prepending them to the slice of hooks. The order of hooks is the following: // - for Pre-hooks, always run the default hooks first, then append the user-defined hooks // - for Post-hooks, always run the user-defined hooks first, then the default hooks -func CombineContainerHooks(defaultHooks, userDefinedHooks []ContainerLifecycleHooks) ContainerLifecycleHooks { +func combineContainerHooks(defaultHooks, userDefinedHooks []ContainerLifecycleHooks) ContainerLifecycleHooks { preCreates := []ContainerRequestHook{} postCreates := []ContainerHook{} preStarts := []ContainerHook{} diff --git a/lifecycle_hooks_test.go b/lifecycle_hooks_test.go new file mode 100644 index 0000000000..a5d15ba51f --- /dev/null +++ b/lifecycle_hooks_test.go @@ -0,0 +1,247 @@ +// This test is testing very internal logic that should not be exported away from this package. We'll +// leave it in the main testcontainers package. Do not use for user facing examples. +package testcontainers + +import ( + "bufio" + "context" + "fmt" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go/wait" +) + +func TestCombineLifecycleHooks(t *testing.T) { + prints := []string{} + + preCreateFunc := func(prefix string, hook string, lifecycleID int, hookID int) func(ctx context.Context, req ContainerRequest) error { + return func(ctx context.Context, _ ContainerRequest) error { + prints = append(prints, fmt.Sprintf("[%s] pre-%s hook %d.%d", prefix, hook, lifecycleID, hookID)) + return nil + } + } + hookFunc := func(prefix string, hookType string, hook string, lifecycleID int, hookID int) func(ctx context.Context, c Container) error { + return func(ctx context.Context, _ Container) error { + prints = append(prints, fmt.Sprintf("[%s] %s-%s hook %d.%d", prefix, hookType, hook, lifecycleID, hookID)) + return nil + } + } + preFunc := func(prefix string, hook string, lifecycleID int, hookID int) func(ctx context.Context, c Container) error { + return hookFunc(prefix, "pre", hook, lifecycleID, hookID) + } + postFunc := func(prefix string, hook string, lifecycleID int, hookID int) func(ctx context.Context, c Container) error { + return hookFunc(prefix, "post", hook, lifecycleID, hookID) + } + + lifecycleHookFunc := func(prefix string, lifecycleID int) ContainerLifecycleHooks { + return ContainerLifecycleHooks{ + PreCreates: []ContainerRequestHook{preCreateFunc(prefix, "create", lifecycleID, 1), preCreateFunc(prefix, "create", lifecycleID, 2)}, + PostCreates: []ContainerHook{postFunc(prefix, "create", lifecycleID, 1), postFunc(prefix, "create", lifecycleID, 2)}, + PreStarts: []ContainerHook{preFunc(prefix, "start", lifecycleID, 1), preFunc(prefix, "start", lifecycleID, 2)}, + PostStarts: []ContainerHook{postFunc(prefix, "start", lifecycleID, 1), postFunc(prefix, "start", lifecycleID, 2)}, + PostReadies: []ContainerHook{postFunc(prefix, "ready", lifecycleID, 1), postFunc(prefix, "ready", lifecycleID, 2)}, + PreStops: []ContainerHook{preFunc(prefix, "stop", lifecycleID, 1), preFunc(prefix, "stop", lifecycleID, 2)}, + PostStops: []ContainerHook{postFunc(prefix, "stop", lifecycleID, 1), postFunc(prefix, "stop", lifecycleID, 2)}, + PreTerminates: []ContainerHook{preFunc(prefix, "terminate", lifecycleID, 1), preFunc(prefix, "terminate", lifecycleID, 2)}, + PostTerminates: []ContainerHook{postFunc(prefix, "terminate", lifecycleID, 1), postFunc(prefix, "terminate", lifecycleID, 2)}, + } + } + + defaultHooks := []ContainerLifecycleHooks{lifecycleHookFunc("default", 1), lifecycleHookFunc("default", 2)} + userDefinedHooks := []ContainerLifecycleHooks{lifecycleHookFunc("user-defined", 1), lifecycleHookFunc("user-defined", 2), lifecycleHookFunc("user-defined", 3)} + + hooks := combineContainerHooks(defaultHooks, userDefinedHooks) + + // call all the hooks in the right order, honouring the lifecycle + + req := ContainerRequest{} + err := hooks.Creating(context.Background())(req) + require.NoError(t, err) + + c := &DockerContainer{} + + err = hooks.Created(context.Background())(c) + require.NoError(t, err) + err = hooks.Starting(context.Background())(c) + require.NoError(t, err) + err = hooks.Started(context.Background())(c) + require.NoError(t, err) + err = hooks.Readied(context.Background())(c) + require.NoError(t, err) + err = hooks.Stopping(context.Background())(c) + require.NoError(t, err) + err = hooks.Stopped(context.Background())(c) + require.NoError(t, err) + err = hooks.Terminating(context.Background())(c) + require.NoError(t, err) + err = hooks.Terminated(context.Background())(c) + require.NoError(t, err) + + // assertions + + // There are 2 default container lifecycle hooks and 3 user-defined container lifecycle hooks. + // Each lifecycle hook has 2 pre-create hooks and 2 post-create hooks. + // That results in 16 hooks per lifecycle (8 defaults + 12 user-defined = 20) + + // There are 5 lifecycles (create, start, ready, stop, terminate), + // but ready has only half of the hooks (it only has post), so we have 90 hooks in total. + assert.Len(t, prints, 90) + + // The order of the hooks is: + // - pre-X hooks: first default (2*2), then user-defined (3*2) + // - post-X hooks: first user-defined (3*2), then default (2*2) + + for i := 0; i < 5; i++ { + var hookType string + // this is the particular order of execution for the hooks + switch i { + case 0: + hookType = "create" + case 1: + hookType = "start" + case 2: + hookType = "ready" + case 3: + hookType = "stop" + case 4: + hookType = "terminate" + } + + initialIndex := i * 20 + if i >= 2 { + initialIndex -= 10 + } + + if hookType != "ready" { + // default pre-hooks: 4 hooks + assert.Equal(t, fmt.Sprintf("[default] pre-%s hook 1.1", hookType), prints[initialIndex]) + assert.Equal(t, fmt.Sprintf("[default] pre-%s hook 1.2", hookType), prints[initialIndex+1]) + assert.Equal(t, fmt.Sprintf("[default] pre-%s hook 2.1", hookType), prints[initialIndex+2]) + assert.Equal(t, fmt.Sprintf("[default] pre-%s hook 2.2", hookType), prints[initialIndex+3]) + + // user-defined pre-hooks: 6 hooks + assert.Equal(t, fmt.Sprintf("[user-defined] pre-%s hook 1.1", hookType), prints[initialIndex+4]) + assert.Equal(t, fmt.Sprintf("[user-defined] pre-%s hook 1.2", hookType), prints[initialIndex+5]) + assert.Equal(t, fmt.Sprintf("[user-defined] pre-%s hook 2.1", hookType), prints[initialIndex+6]) + assert.Equal(t, fmt.Sprintf("[user-defined] pre-%s hook 2.2", hookType), prints[initialIndex+7]) + assert.Equal(t, fmt.Sprintf("[user-defined] pre-%s hook 3.1", hookType), prints[initialIndex+8]) + assert.Equal(t, fmt.Sprintf("[user-defined] pre-%s hook 3.2", hookType), prints[initialIndex+9]) + } + + // user-defined post-hooks: 6 hooks + assert.Equal(t, fmt.Sprintf("[user-defined] post-%s hook 1.1", hookType), prints[initialIndex+10]) + assert.Equal(t, fmt.Sprintf("[user-defined] post-%s hook 1.2", hookType), prints[initialIndex+11]) + assert.Equal(t, fmt.Sprintf("[user-defined] post-%s hook 2.1", hookType), prints[initialIndex+12]) + assert.Equal(t, fmt.Sprintf("[user-defined] post-%s hook 2.2", hookType), prints[initialIndex+13]) + assert.Equal(t, fmt.Sprintf("[user-defined] post-%s hook 3.1", hookType), prints[initialIndex+14]) + assert.Equal(t, fmt.Sprintf("[user-defined] post-%s hook 3.2", hookType), prints[initialIndex+15]) + + // default post-hooks: 4 hooks + assert.Equal(t, fmt.Sprintf("[default] post-%s hook 1.1", hookType), prints[initialIndex+16]) + assert.Equal(t, fmt.Sprintf("[default] post-%s hook 1.2", hookType), prints[initialIndex+17]) + assert.Equal(t, fmt.Sprintf("[default] post-%s hook 2.1", hookType), prints[initialIndex+18]) + assert.Equal(t, fmt.Sprintf("[default] post-%s hook 2.2", hookType), prints[initialIndex+19]) + } +} + +func TestLifecycleHooks_WithMultipleHooks(t *testing.T) { + ctx := context.Background() + + dl := linesTestLogger{} + + req := ContainerRequest{ + Image: nginxAlpineImage, + LifecycleHooks: []ContainerLifecycleHooks{ + DefaultLoggingHook(&dl), + DefaultLoggingHook(&dl), + }, + } + + c, err := GenericContainer(ctx, GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + require.NoError(t, err) + require.NotNil(t, c) + + duration := 1 * time.Second + err = c.Stop(ctx, &duration) + require.NoError(t, err) + + err = c.Start(ctx) + require.NoError(t, err) + + err = c.Terminate(ctx) + require.NoError(t, err) + + require.Len(t, dl.data, 24) +} + +type linesTestLogger struct { + data []string +} + +func (l *linesTestLogger) Printf(format string, args ...interface{}) { + l.data = append(l.data, fmt.Sprintf(format, args...)) +} + +func TestPrintContainerLogsOnError(t *testing.T) { + ctx := context.Background() + + req := ContainerRequest{ + Image: "docker.io/alpine", + Cmd: []string{"echo", "-n", "I am expecting this"}, + WaitingFor: wait.ForLog("I was expecting that").WithStartupTimeout(5 * time.Second), + } + + arrayOfLinesLogger := linesTestLogger{ + data: []string{}, + } + + container, err := GenericContainer(ctx, GenericContainerRequest{ + ProviderType: providerType, + ContainerRequest: req, + Logger: &arrayOfLinesLogger, + Started: true, + }) + // 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) + if err != nil { + t.Fatal(err) + } + defer containerLogs.Close() + + // read container logs line by line, checking that each line is present in the stdout + rd := bufio.NewReader(containerLogs) + for { + line, err := rd.ReadString('\n') + if err != nil { + if err.Error() == "EOF" { + break + } + + t.Fatal("Read Error:", err) + } + + // the last line of the array should contain the line of interest, + // but we are checking all the lines to make sure that is present + found := false + for _, l := range arrayOfLinesLogger.data { + if strings.Contains(l, line) { + found = true + break + } + } + assert.True(t, found, "container log line not found in the output of the logger: %s", line) + } +} diff --git a/lifecycle_test.go b/lifecycle_test.go index e5f6ee2d7b..93794938dd 100644 --- a/lifecycle_test.go +++ b/lifecycle_test.go @@ -1,7 +1,6 @@ package testcontainers_test import ( - "bufio" "context" "fmt" "strings" @@ -17,7 +16,6 @@ import ( "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" - "github.com/testcontainers/testcontainers-go/wait" ) func TestPreCreateModifierHook(t *testing.T) { @@ -534,236 +532,6 @@ func TestLifecycleHooks_WithDefaultLogger(t *testing.T) { require.Len(t, dl.data, 12) } -func TestCombineLifecycleHooks(t *testing.T) { - prints := []string{} - - preCreateFunc := func(prefix string, hook string, lifecycleID int, hookID int) func(ctx context.Context, req testcontainers.ContainerRequest) error { - return func(ctx context.Context, _ testcontainers.ContainerRequest) error { - prints = append(prints, fmt.Sprintf("[%s] pre-%s hook %d.%d", prefix, hook, lifecycleID, hookID)) - return nil - } - } - hookFunc := func(prefix string, hookType string, hook string, lifecycleID int, hookID int) func(ctx context.Context, c testcontainers.Container) error { - return func(ctx context.Context, _ testcontainers.Container) error { - prints = append(prints, fmt.Sprintf("[%s] %s-%s hook %d.%d", prefix, hookType, hook, lifecycleID, hookID)) - return nil - } - } - preFunc := func(prefix string, hook string, lifecycleID int, hookID int) func(ctx context.Context, c testcontainers.Container) error { - return hookFunc(prefix, "pre", hook, lifecycleID, hookID) - } - postFunc := func(prefix string, hook string, lifecycleID int, hookID int) func(ctx context.Context, c testcontainers.Container) error { - return hookFunc(prefix, "post", hook, lifecycleID, hookID) - } - - lifecycleHookFunc := func(prefix string, lifecycleID int) testcontainers.ContainerLifecycleHooks { - return testcontainers.ContainerLifecycleHooks{ - PreCreates: []testcontainers.ContainerRequestHook{preCreateFunc(prefix, "create", lifecycleID, 1), preCreateFunc(prefix, "create", lifecycleID, 2)}, - PostCreates: []testcontainers.ContainerHook{postFunc(prefix, "create", lifecycleID, 1), postFunc(prefix, "create", lifecycleID, 2)}, - PreStarts: []testcontainers.ContainerHook{preFunc(prefix, "start", lifecycleID, 1), preFunc(prefix, "start", lifecycleID, 2)}, - PostStarts: []testcontainers.ContainerHook{postFunc(prefix, "start", lifecycleID, 1), postFunc(prefix, "start", lifecycleID, 2)}, - PostReadies: []testcontainers.ContainerHook{postFunc(prefix, "ready", lifecycleID, 1), postFunc(prefix, "ready", lifecycleID, 2)}, - PreStops: []testcontainers.ContainerHook{preFunc(prefix, "stop", lifecycleID, 1), preFunc(prefix, "stop", lifecycleID, 2)}, - PostStops: []testcontainers.ContainerHook{postFunc(prefix, "stop", lifecycleID, 1), postFunc(prefix, "stop", lifecycleID, 2)}, - PreTerminates: []testcontainers.ContainerHook{preFunc(prefix, "terminate", lifecycleID, 1), preFunc(prefix, "terminate", lifecycleID, 2)}, - PostTerminates: []testcontainers.ContainerHook{postFunc(prefix, "terminate", lifecycleID, 1), postFunc(prefix, "terminate", lifecycleID, 2)}, - } - } - - defaultHooks := []testcontainers.ContainerLifecycleHooks{lifecycleHookFunc("default", 1), lifecycleHookFunc("default", 2)} - userDefinedHooks := []testcontainers.ContainerLifecycleHooks{lifecycleHookFunc("user-defined", 1), lifecycleHookFunc("user-defined", 2), lifecycleHookFunc("user-defined", 3)} - - hooks := testcontainers.CombineContainerHooks(defaultHooks, userDefinedHooks) - - // call all the hooks in the right order, honouring the lifecycle - - req := testcontainers.ContainerRequest{} - err := hooks.Creating(context.Background())(req) - require.NoError(t, err) - - c := &testcontainers.DockerContainer{} - - err = hooks.Created(context.Background())(c) - require.NoError(t, err) - err = hooks.Starting(context.Background())(c) - require.NoError(t, err) - err = hooks.Started(context.Background())(c) - require.NoError(t, err) - err = hooks.Readied(context.Background())(c) - require.NoError(t, err) - err = hooks.Stopping(context.Background())(c) - require.NoError(t, err) - err = hooks.Stopped(context.Background())(c) - require.NoError(t, err) - err = hooks.Terminating(context.Background())(c) - require.NoError(t, err) - err = hooks.Terminated(context.Background())(c) - require.NoError(t, err) - - // assertions - - // There are 2 default container lifecycle hooks and 3 user-defined container lifecycle hooks. - // Each lifecycle hook has 2 pre-create hooks and 2 post-create hooks. - // That results in 16 hooks per lifecycle (8 defaults + 12 user-defined = 20) - - // There are 5 lifecycles (create, start, ready, stop, terminate), - // but ready has only half of the hooks (it only has post), so we have 90 hooks in total. - assert.Len(t, prints, 90) - - // The order of the hooks is: - // - pre-X hooks: first default (2*2), then user-defined (3*2) - // - post-X hooks: first user-defined (3*2), then default (2*2) - - for i := 0; i < 5; i++ { - var hookType string - // this is the particular order of execution for the hooks - switch i { - case 0: - hookType = "create" - case 1: - hookType = "start" - case 2: - hookType = "ready" - case 3: - hookType = "stop" - case 4: - hookType = "terminate" - } - - initialIndex := i * 20 - if i >= 2 { - initialIndex -= 10 - } - - if hookType != "ready" { - // default pre-hooks: 4 hooks - assert.Equal(t, fmt.Sprintf("[default] pre-%s hook 1.1", hookType), prints[initialIndex]) - assert.Equal(t, fmt.Sprintf("[default] pre-%s hook 1.2", hookType), prints[initialIndex+1]) - assert.Equal(t, fmt.Sprintf("[default] pre-%s hook 2.1", hookType), prints[initialIndex+2]) - assert.Equal(t, fmt.Sprintf("[default] pre-%s hook 2.2", hookType), prints[initialIndex+3]) - - // user-defined pre-hooks: 6 hooks - assert.Equal(t, fmt.Sprintf("[user-defined] pre-%s hook 1.1", hookType), prints[initialIndex+4]) - assert.Equal(t, fmt.Sprintf("[user-defined] pre-%s hook 1.2", hookType), prints[initialIndex+5]) - assert.Equal(t, fmt.Sprintf("[user-defined] pre-%s hook 2.1", hookType), prints[initialIndex+6]) - assert.Equal(t, fmt.Sprintf("[user-defined] pre-%s hook 2.2", hookType), prints[initialIndex+7]) - assert.Equal(t, fmt.Sprintf("[user-defined] pre-%s hook 3.1", hookType), prints[initialIndex+8]) - assert.Equal(t, fmt.Sprintf("[user-defined] pre-%s hook 3.2", hookType), prints[initialIndex+9]) - } - - // user-defined post-hooks: 6 hooks - assert.Equal(t, fmt.Sprintf("[user-defined] post-%s hook 1.1", hookType), prints[initialIndex+10]) - assert.Equal(t, fmt.Sprintf("[user-defined] post-%s hook 1.2", hookType), prints[initialIndex+11]) - assert.Equal(t, fmt.Sprintf("[user-defined] post-%s hook 2.1", hookType), prints[initialIndex+12]) - assert.Equal(t, fmt.Sprintf("[user-defined] post-%s hook 2.2", hookType), prints[initialIndex+13]) - assert.Equal(t, fmt.Sprintf("[user-defined] post-%s hook 3.1", hookType), prints[initialIndex+14]) - assert.Equal(t, fmt.Sprintf("[user-defined] post-%s hook 3.2", hookType), prints[initialIndex+15]) - - // default post-hooks: 4 hooks - assert.Equal(t, fmt.Sprintf("[default] post-%s hook 1.1", hookType), prints[initialIndex+16]) - assert.Equal(t, fmt.Sprintf("[default] post-%s hook 1.2", hookType), prints[initialIndex+17]) - assert.Equal(t, fmt.Sprintf("[default] post-%s hook 2.1", hookType), prints[initialIndex+18]) - assert.Equal(t, fmt.Sprintf("[default] post-%s hook 2.2", hookType), prints[initialIndex+19]) - } -} - -func TestLifecycleHooks_WithMultipleHooks(t *testing.T) { - ctx := context.Background() - - dl := inMemoryLogger{} - - req := testcontainers.ContainerRequest{ - Image: nginxAlpineImage, - LifecycleHooks: []testcontainers.ContainerLifecycleHooks{ - testcontainers.DefaultLoggingHook(&dl), - testcontainers.DefaultLoggingHook(&dl), - }, - } - - c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ - ContainerRequest: req, - Started: true, - }) - require.NoError(t, err) - require.NotNil(t, c) - - duration := 1 * time.Second - err = c.Stop(ctx, &duration) - require.NoError(t, err) - - err = c.Start(ctx) - require.NoError(t, err) - - err = c.Terminate(ctx) - require.NoError(t, err) - - require.Len(t, dl.data, 24) -} - -type linesTestLogger struct { - data []string -} - -func (l *linesTestLogger) Printf(format string, args ...interface{}) { - l.data = append(l.data, fmt.Sprintf(format, args...)) -} - -func TestPrintContainerLogsOnError(t *testing.T) { - ctx := context.Background() - - req := testcontainers.ContainerRequest{ - Image: "docker.io/alpine", - Cmd: []string{"echo", "-n", "I am expecting this"}, - WaitingFor: wait.ForLog("I was expecting that").WithStartupTimeout(5 * time.Second), - } - - arrayOfLinesLogger := linesTestLogger{ - data: []string{}, - } - - container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ - ProviderType: providerType, - ContainerRequest: req, - Logger: &arrayOfLinesLogger, - Started: true, - }) - // 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) - if err != nil { - t.Fatal(err) - } - defer containerLogs.Close() - - // read container logs line by line, checking that each line is present in the stdout - rd := bufio.NewReader(containerLogs) - for { - line, err := rd.ReadString('\n') - if err != nil { - if err.Error() == "EOF" { - break - } - - t.Fatal("Read Error:", err) - } - - // the last line of the array should contain the line of interest, - // but we are checking all the lines to make sure that is present - found := false - for _, l := range arrayOfLinesLogger.data { - if strings.Contains(l, line) { - found = true - break - } - } - assert.True(t, found, "container log line not found in the output of the logger: %s", line) - } -} - func lifecycleHooksIsHonouredFn(t *testing.T, ctx context.Context, prints []string) { require.Len(t, prints, 24) From d3bba42596ac355989acfc9855f2ca2aa94b433e Mon Sep 17 00:00:00 2001 From: Guillaume Date: Mon, 18 Mar 2024 10:17:13 -0400 Subject: [PATCH 5/5] Rename some default hooks so they can stay internal --- docker.go | 10 +++++----- lifecycle.go | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docker.go b/docker.go index c2619e6bcc..cdee43183c 100644 --- a/docker.go +++ b/docker.go @@ -1077,9 +1077,9 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque defaultHooks := []ContainerLifecycleHooks{ DefaultLoggingHook(p.Logger), DefaultPreCreateHook(p, dockerInput, hostConfig, networkingConfig), - DefaultCopyFileToContainerHook(req.Files), - DefaultLogConsumersHook(req.LogConsumerCfg), - DefaultReadinessHook(), + defaultCopyFileToContainerHook(req.Files), + defaultLogConsumersHook(req.LogConsumerCfg), + defaultReadinessHook(), } req.LifecycleHooks = []ContainerLifecycleHooks{combineContainerHooks(defaultHooks, req.LifecycleHooks)} @@ -1211,8 +1211,8 @@ func (p *DockerProvider) ReuseOrCreateContainer(ctx context.Context, req Contain // default hooks include logger hook and pre-create hook defaultHooks := []ContainerLifecycleHooks{ DefaultLoggingHook(p.Logger), - DefaultReadinessHook(), - DefaultLogConsumersHook(req.LogConsumerCfg), + defaultReadinessHook(), + defaultLogConsumersHook(req.LogConsumerCfg), } dc := &DockerContainer{ diff --git a/lifecycle.go b/lifecycle.go index 6d21bbb68b..ca24f5f0f4 100644 --- a/lifecycle.go +++ b/lifecycle.go @@ -123,9 +123,9 @@ func DefaultPreCreateHook(p *DockerProvider, dockerInput *container.Config, host } } -// DefaultCopyFileToContainerHook is a hook that will copy files to the container after it's created +// defaultCopyFileToContainerHook is a hook that will copy files to the container after it's created // but before it's started -func DefaultCopyFileToContainerHook(files []ContainerFile) ContainerLifecycleHooks { +func defaultCopyFileToContainerHook(files []ContainerFile) ContainerLifecycleHooks { return ContainerLifecycleHooks{ PostCreates: []ContainerHook{ // copy files to container after it's created @@ -143,8 +143,8 @@ func DefaultCopyFileToContainerHook(files []ContainerFile) ContainerLifecycleHoo } } -// DefaultLogConsumersHook is a hook that will start log consumers after the container is started -func DefaultLogConsumersHook(cfg *LogConsumerConfig) ContainerLifecycleHooks { +// defaultLogConsumersHook is a hook that will start log consumers after the container is started +func defaultLogConsumersHook(cfg *LogConsumerConfig) ContainerLifecycleHooks { return ContainerLifecycleHooks{ PostStarts: []ContainerHook{ // first post-start hook is to produce logs and start log consumers @@ -180,8 +180,8 @@ func DefaultLogConsumersHook(cfg *LogConsumerConfig) ContainerLifecycleHooks { } } -// DefaultReadinessHook is a hook that will wait for the container to be ready -func DefaultReadinessHook() ContainerLifecycleHooks { +// defaultReadinessHook is a hook that will wait for the container to be ready +func defaultReadinessHook() ContainerLifecycleHooks { return ContainerLifecycleHooks{ PostStarts: []ContainerHook{ // wait for the container to be ready