Skip to content
Merged
19 changes: 18 additions & 1 deletion docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -1266,7 +1266,7 @@ func (p *DockerProvider) findContainerByName(ctx context.Context, name string) (

// Note that, 'name' filter will use regex to find the containers
filter := filters.NewArgs(filters.Arg("name", fmt.Sprintf("^%s$", name)))
containers, err := p.client.ContainerList(ctx, container.ListOptions{Filters: filter})
containers, err := p.client.ContainerList(ctx, container.ListOptions{All: true, Filters: filter})
if err != nil {
return nil, fmt.Errorf("container list: %w", err)
}
Expand Down Expand Up @@ -1364,6 +1364,23 @@ func (p *DockerProvider) ReuseOrCreateContainer(ctx context.Context, req Contain
lifecycleHooks: []ContainerLifecycleHooks{combineContainerHooks(defaultHooks, req.LifecycleHooks)},
}

// If a container was stopped programmatically, we want to ensure the container
// is running again, but only if it is not paused, as it's not possible to start
// a paused container. The Docker Engine returns the "cannot start a paused container,
// try unpause instead" error.
switch c.State {
case "running":
// cannot re-start a running container, but we still need
// to call the startup hooks.
case "paused":
// TODO: we should unpause the container here.
return nil, fmt.Errorf("cannot start a paused container: %w", errors.ErrUnsupported)
default:
if err := dc.Start(ctx); err != nil {
return dc, fmt.Errorf("start container %s in state %s: %w", req.Name, c.State, err)
}
}

err = dc.startedHook(ctx)
if err != nil {
return nil, err
Expand Down
73 changes: 73 additions & 0 deletions reuse_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package testcontainers_test

import (
"context"
"errors"
"testing"

"github.com/stretchr/testify/require"

"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/internal/core"
)

func TestGenericContainer_stop_start_withReuse(t *testing.T) {
containerName := "my-nginx"

req := testcontainers.GenericContainerRequest{
ContainerRequest: testcontainers.ContainerRequest{
Image: nginxAlpineImage,
ExposedPorts: []string{"8080/tcp"},
Name: containerName,
},
Reuse: true,
Started: true,
}

ctr, err := testcontainers.GenericContainer(context.Background(), req)
testcontainers.CleanupContainer(t, ctr)
require.NoError(t, err)
require.NotNil(t, ctr)

err = ctr.Stop(context.Background(), nil)
require.NoError(t, err)

// Run another container with same container name:
// The checks for the exposed ports must not fail when restarting the container.
ctr1, err := testcontainers.GenericContainer(context.Background(), req)
testcontainers.CleanupContainer(t, ctr1)
require.NoError(t, err)
require.NotNil(t, ctr1)
}

func TestGenericContainer_pause_start_withReuse(t *testing.T) {
containerName := "my-nginx"

req := testcontainers.GenericContainerRequest{
ContainerRequest: testcontainers.ContainerRequest{
Image: nginxAlpineImage,
ExposedPorts: []string{"8080/tcp"},
Name: containerName,
},
Reuse: true,
Started: true,
}

ctr, err := testcontainers.GenericContainer(context.Background(), req)
testcontainers.CleanupContainer(t, ctr)
require.NoError(t, err)
require.NotNil(t, ctr)

// Pause the container is not supported by our API, but we can do it manually
// by using the Docker client.
cli, err := core.NewClient(context.Background())
require.NoError(t, err)

err = cli.ContainerPause(context.Background(), ctr.GetContainerID())
require.NoError(t, err)

// Because the container is paused, it should not be possible to start it again.
ctr1, err := testcontainers.GenericContainer(context.Background(), req)
testcontainers.CleanupContainer(t, ctr1)
require.ErrorIs(t, err, errors.ErrUnsupported)
}
Loading