From 53f6ee9fd6504eb6629552273c9628a0d1a65401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Fri, 28 Oct 2022 17:58:33 +0200 Subject: [PATCH] fix: support parallel execution of reusable containers (#593) * chore: add a test demonstrating the bug * fix: protect reusable containers with a mutex * fix: typo --- generic.go | 7 +++++++ parallel_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/generic.go b/generic.go index cbd239f826..ce79aa1100 100644 --- a/generic.go +++ b/generic.go @@ -4,9 +4,11 @@ import ( "context" "errors" "fmt" + "sync" ) var ( + reuseContainerMx sync.Mutex ErrReuseEmptyName = errors.New("with reuse option a container name mustn't be empty") ) @@ -56,6 +58,11 @@ func GenericContainer(ctx context.Context, req GenericContainerRequest) (Contain var c Container if req.Reuse { + // we must protect the reusability of the container in the case it's invoked + // in a parallel execution, via ParallelContainers or t.Parallel() + reuseContainerMx.Lock() + defer reuseContainerMx.Unlock() + c, err = provider.ReuseOrCreateContainer(ctx, req.ContainerRequest) } else { c, err = provider.CreateContainer(ctx, req.ContainerRequest) diff --git a/parallel_test.go b/parallel_test.go index 3ff6738a1b..84d86552be 100644 --- a/parallel_test.go +++ b/parallel_test.go @@ -2,9 +2,12 @@ package testcontainers import ( "context" + "fmt" "testing" + "time" "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go/wait" ) func TestParallelContainers(t *testing.T) { @@ -120,3 +123,50 @@ func TestParallelContainers(t *testing.T) { }) } } + +func TestParallelContainersWithReuse(t *testing.T) { + const ( + postgresPort = 5432 + postgresPassword = "test" + postgresUser = "test" + postgresDb = "test" + ) + + natPort := fmt.Sprintf("%d/tcp", postgresPort) + + req := GenericContainerRequest{ + ContainerRequest: ContainerRequest{ + Image: "postgis/postgis", + Name: "test-postgres", + ExposedPorts: []string{natPort}, + Env: map[string]string{ + "POSTGRES_PASSWORD": postgresPassword, + "POSTGRES_USER": postgresUser, + "POSTGRES_DATABASE": postgresDb, + }, + WaitingFor: wait.ForLog("database system is ready to accept connections"). + WithPollInterval(100 * time.Millisecond). + WithOccurrence(2), + }, + Started: true, + Reuse: true, + } + + parallelRequest := ParallelContainerRequest{ + req, + req, + req, + } + + ctx := context.Background() + + res, err := ParallelContainers(ctx, parallelRequest, ParallelContainersOptions{}) + if err != nil { + e, _ := err.(ParallelContainersError) + t.Fatalf("expected errors: %d, got: %d\n", 0, len(e.Errors)) + } + + for _, c := range res { + defer c.Terminate(ctx) + } +}