Skip to content

Commit

Permalink
bug: allow start container with reuse in different test package (#2247)
Browse files Browse the repository at this point in the history
* feat: allow start container with reuse in different test package

* feat(reuse): add defaultLogConsumersHook in reuse container

---------

Co-authored-by: Ivan Sitkin <[email protected]>
  • Loading branch information
Alviner and Alviner authored Feb 23, 2024
1 parent 3a53a7e commit 9eea435
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 5 deletions.
53 changes: 51 additions & 2 deletions docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"net/url"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -49,6 +50,8 @@ const (
logStoppedForOutOfSyncMessage = "Stopping log consumer: Headers out of sync"
)

var createContainerFailDueToNameConflictRegex = regexp.MustCompile("Conflict. The container name .* is already in use by container .*")

// DockerContainer represents a container started using Docker
type DockerContainer struct {
// Container ID from Docker
Expand Down Expand Up @@ -1153,13 +1156,40 @@ func (p *DockerProvider) findContainerByName(ctx context.Context, name string) (
return nil, nil
}

func (p *DockerProvider) waitContainerCreation(ctx context.Context, name string) (*types.Container, error) {
var container *types.Container
return container, backoff.Retry(func() error {
c, err := p.findContainerByName(ctx, name)
if err != nil {
return err
}

if c == nil {
return fmt.Errorf("container %s not found", name)
}

container = c
return nil
}, backoff.WithContext(backoff.NewExponentialBackOff(), ctx))
}

func (p *DockerProvider) ReuseOrCreateContainer(ctx context.Context, req ContainerRequest) (Container, error) {
c, err := p.findContainerByName(ctx, req.Name)
if err != nil {
return nil, err
}
if c == nil {
return p.CreateContainer(ctx, req)
createdContainer, err := p.CreateContainer(ctx, req)
if err == nil {
return createdContainer, nil
}
if !createContainerFailDueToNameConflictRegex.MatchString(err.Error()) {
return nil, err
}
c, err = p.waitContainerCreation(ctx, req.Name)
if err != nil {
return nil, err
}
}

sessionID := core.SessionID()
Expand All @@ -1178,6 +1208,13 @@ 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),
}

dc := &DockerContainer{
ID: c.ID,
WaitingFor: req.WaitingFor,
Expand All @@ -1187,7 +1224,19 @@ func (p *DockerProvider) ReuseOrCreateContainer(ctx context.Context, req Contain
terminationSignal: termSignal,
stopLogProductionCh: nil,
logger: p.Logger,
isRunning: c.State == "running",
lifecycleHooks: []ContainerLifecycleHooks{combineContainerHooks(defaultHooks, req.LifecycleHooks)},
}

err = dc.startedHook(ctx)
if err != nil {
return nil, err
}

dc.isRunning = true

err = dc.readiedHook(ctx)
if err != nil {
return nil, err
}

return dc, nil
Expand Down
72 changes: 72 additions & 0 deletions generic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ package testcontainers
import (
"context"
"errors"
"net/http"
"os"
"os/exec"
"strings"
"sync"
"testing"
"time"

Expand Down Expand Up @@ -117,3 +122,70 @@ func TestGenericContainerShouldReturnRefOnError(t *testing.T) {
require.NotNil(t, c)
terminateContainerOnEnd(t, context.Background(), c)
}

func TestGenericReusableContainerInSubprocess(t *testing.T) {
wg := sync.WaitGroup{}
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()

// create containers in subprocesses, as "go test ./..." does.
output := createReuseContainerInSubprocess(t)

// check is reuse container with WaitingFor work correctly.
require.True(t, strings.Contains(output, "🚧 Waiting for container id"))
require.True(t, strings.Contains(output, "🔔 Container is ready"))
}()
}

wg.Wait()
}

func createReuseContainerInSubprocess(t *testing.T) string {
cmd := exec.Command(os.Args[0], "-test.run=TestHelperContainerStarterProcess")
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}

output, err := cmd.CombinedOutput()
require.NoError(t, err, string(output))

return string(output)
}

// TestHelperContainerStarterProcess is a helper function
// to start a container in a subprocess. It's not a real test.
func TestHelperContainerStarterProcess(t *testing.T) {
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
t.Skip("Skipping helper test function. It's not a real test")
}

ctx := context.Background()

nginxC, err := GenericContainer(ctx, GenericContainerRequest{
ProviderType: providerType,
ContainerRequest: ContainerRequest{
Image: nginxDelayedImage,
ExposedPorts: []string{nginxDefaultPort},
WaitingFor: wait.ForListeningPort(nginxDefaultPort), // default startupTimeout is 60s
Name: reusableContainerName,
},
Started: true,
Reuse: true,
})
require.NoError(t, err)
require.True(t, nginxC.IsRunning())

origin, err := nginxC.PortEndpoint(ctx, nginxDefaultPort, "http")
require.NoError(t, err)

// check is reuse container with WaitingFor work correctly.
req, err := http.NewRequestWithContext(ctx, http.MethodGet, origin, nil)
require.NoError(t, err)
req.Close = true

resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()

require.Equal(t, http.StatusOK, resp.StatusCode)
}
3 changes: 0 additions & 3 deletions reaper.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"fmt"
"math/rand"
"net"
"regexp"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -186,8 +185,6 @@ func reuseOrCreateReaper(ctx context.Context, sessionID string, provider ReaperP
return reaperInstance, nil
}

var createContainerFailDueToNameConflictRegex = regexp.MustCompile("Conflict. The container name .* is already in use by container .*")

// reuseReaperContainer constructs a Reaper from an already running reaper
// DockerContainer.
func reuseReaperContainer(ctx context.Context, sessionID string, provider ReaperProvider, reaperContainer *DockerContainer) (*Reaper, error) {
Expand Down

0 comments on commit 9eea435

Please sign in to comment.