From 6bc1b365165f2ea41baec7bca81b6030d6f9435e Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Mon, 17 Aug 2026 08:20:56 +0200 Subject: [PATCH] test(e2e): expose, container_name, ipc and wait scenarios MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TestExposeRange keeps its regression link (#13378), the invariant being simply that up accepts a port range in expose. - TestUpContainerNameConflict interpolates a project-scoped container_name instead of the fixture's global 'test', so parallel runs cannot collide on it. - TestIPC demonstrates the CLI() escape hatch: the external container the 'container:' mode points at is created eagerly before the steps, so its id is available to the declarative checks. (Runs only in CI: ipc container: mode is rejected by Docker Desktop and dind daemons — 'restricted host mount' — for the legacy test as well.) - wait scenarios observe which services exited instead of trusting the command's silence; TestWaitOnInfinity stays legacy, the DSL has no notion of a still-running command. The wait fixture stays for it; ipc-test and container_name fixtures are removed. Signed-off-by: Nicolas De Loof --- pkg/e2e/container_name_test.go | 35 +++++------ pkg/e2e/expose_test.go | 27 ++------- pkg/e2e/ipc_test.go | 58 +++++++----------- pkg/e2e/testdata/TestExposeRange/compose.yaml | 5 ++ .../TestIPC}/compose.yaml | 2 +- .../TestUpContainerNameConflict}/compose.yaml | 4 +- pkg/e2e/testdata/TestWaitAndDrop/compose.yaml | 10 ++++ .../testdata/TestWaitOnFaster/compose.yaml | 10 ++++ .../testdata/TestWaitOnSlower/compose.yaml | 10 ++++ pkg/e2e/wait_test.go | 60 +++++++------------ 10 files changed, 105 insertions(+), 116 deletions(-) create mode 100644 pkg/e2e/testdata/TestExposeRange/compose.yaml rename pkg/e2e/{fixtures/ipc-test => testdata/TestIPC}/compose.yaml (83%) rename pkg/e2e/{fixtures/container_name => testdata/TestUpContainerNameConflict}/compose.yaml (63%) create mode 100644 pkg/e2e/testdata/TestWaitAndDrop/compose.yaml create mode 100644 pkg/e2e/testdata/TestWaitOnFaster/compose.yaml create mode 100644 pkg/e2e/testdata/TestWaitOnSlower/compose.yaml diff --git a/pkg/e2e/container_name_test.go b/pkg/e2e/container_name_test.go index 99de738c2..79332ab6a 100644 --- a/pkg/e2e/container_name_test.go +++ b/pkg/e2e/container_name_test.go @@ -20,24 +20,25 @@ package e2e import ( "testing" - - "gotest.tools/v3/icmd" ) func TestUpContainerNameConflict(t *testing.T) { - c := NewParallelCLI(t) - const projectName = "e2e-container_name_conflict" - - t.Cleanup(func() { - c.RunDockerComposeCmd(t, "--project-name", projectName, "down") - }) - - res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/container_name/compose.yaml", "--project-name", projectName, "up") - res.Assert(t, icmd.Expected{ExitCode: 1, Err: `container name "test" is already in use`}) - - c.RunDockerComposeCmd(t, "--project-name", projectName, "down") - c.RunDockerComposeCmd(t, "-f", "fixtures/container_name/compose.yaml", "--project-name", projectName, "up", "test") - - c.RunDockerComposeCmd(t, "--project-name", projectName, "down") - c.RunDockerComposeCmd(t, "-f", "fixtures/container_name/compose.yaml", "--project-name", projectName, "up", "another_test") + s := NewScenario(t, "two services claiming the same container_name must be rejected together, but each must run alone") + name := s.Project() + "-fixed" + s.Env("FIXED_NAME="+name). + Step("up with both services is rejected over the name conflict", + ComposeCmd("up").MayFail(), + ExitCode(1), + OutputContains(`container name "`+name+`" is already in use`)). + Step("the failed up leaves nothing behind", + ComposeCmd("down")). + Step("the first service runs alone under the shared name", + ComposeCmd("up", "test"), + ServiceState("test", "exited")). + Step("down frees the name", + ComposeCmd("down"), + ServiceNotCreated("test")). + Step("the other service runs alone under the shared name", + ComposeCmd("up", "another_test"), + ServiceState("another_test", "exited")) } diff --git a/pkg/e2e/expose_test.go b/pkg/e2e/expose_test.go index 438f7b549..f7e7d3c2f 100644 --- a/pkg/e2e/expose_test.go +++ b/pkg/e2e/expose_test.go @@ -19,30 +19,13 @@ package e2e import ( - "os" - "path/filepath" "testing" - - "gotest.tools/v3/assert" ) -// see https://github.com/docker/compose/issues/13378 func TestExposeRange(t *testing.T) { - c := NewParallelCLI(t) - - f := filepath.Join(t.TempDir(), "compose.yaml") - err := os.WriteFile(f, []byte(` -name: test-expose-range -services: - test: - image: alpine - expose: - - "9091-9092" -`), 0o644) - assert.NilError(t, err) - - t.Cleanup(func() { - c.cleanupWithDown(t, "test-expose-range") - }) - c.RunDockerComposeCmd(t, "-f", f, "up") + // Regression test for https://github.com/docker/compose/issues/13378 + // a port range in expose used to fail container creation. + NewScenario(t, "a port range in expose must be accepted"). + Step("up creates and runs the service", + ComposeCmd("up")) } diff --git a/pkg/e2e/ipc_test.go b/pkg/e2e/ipc_test.go index 7a46192a6..010854b60 100644 --- a/pkg/e2e/ipc_test.go +++ b/pkg/e2e/ipc_test.go @@ -17,48 +17,32 @@ package e2e import ( - "fmt" "strings" "testing" - - "gotest.tools/v3/icmd" ) func TestIPC(t *testing.T) { - c := NewParallelCLI(t) + s := NewScenario(t, "every ipc mode must materialize in the created containers: shareable, service: and container:") + external := s.Project() + "-external" - const projectName = "ipc_e2e" - var cid string - t.Run("create ipc mode container", func(t *testing.T) { - res := c.RunDockerCmd(t, "run", "-d", "--rm", "--ipc=shareable", "--name", "ipc_mode_container", "alpine", - "top") - cid = strings.Trim(res.Stdout(), "\n") - }) + // the container: mode references a container compose doesn't manage; + // create it up front so its id is known when the steps assert against it + cid := strings.TrimSpace(s.CLI().RunDockerCmd(t, "run", "-d", "--rm", "--ipc=shareable", "--name", external, "alpine", "top").Stdout()) + s.Defer(DockerCmd("rm", "-f", external).MayFail()) - t.Run("up", func(t *testing.T) { - c.RunDockerComposeCmd(t, "-f", "./fixtures/ipc-test/compose.yaml", "--project-name", projectName, "up", "-d") - }) - - t.Run("check running project", func(t *testing.T) { - res := c.RunDockerComposeCmd(t, "-p", projectName, "ps") - res.Assert(t, icmd.Expected{Out: `shareable`}) - }) - - t.Run("check ipcmode in container inspect", func(t *testing.T) { - res := c.RunDockerCmd(t, "inspect", projectName+"-shareable-1") - res.Assert(t, icmd.Expected{Out: `"IpcMode": "shareable",`}) - - res = c.RunDockerCmd(t, "inspect", projectName+"-service-1") - res.Assert(t, icmd.Expected{Out: `"IpcMode": "container:`}) - - res = c.RunDockerCmd(t, "inspect", projectName+"-container-1") - res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`"IpcMode": "container:%s",`, cid)}) - }) - - t.Run("down", func(t *testing.T) { - _ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down") - }) - t.Run("remove ipc mode container", func(t *testing.T) { - _ = c.RunDockerCmd(t, "rm", "-f", "ipc_mode_container") - }) + s.Env("EXTERNAL_NAME="+external). + Step("up starts the three services", + ComposeCmd("up", "-d"), + ServiceState("service", "running"), + ServiceState("container", "running"), + ServiceState("shareable", "running")). + Step("the shareable service owns a shareable ipc namespace", + DockerCmd("inspect", "-f", "{{.HostConfig.IpcMode}}", s.Project()+"-shareable-1"), + OutputContains("shareable")). + Step("the service: mode resolves to the target service's container", + DockerCmd("inspect", "-f", "{{.HostConfig.IpcMode}}", s.Project()+"-service-1"), + OutputContains("container:")). + Step("the container: mode resolves to the external container", + DockerCmd("inspect", "-f", "{{.HostConfig.IpcMode}}", s.Project()+"-container-1"), + OutputContains("container:"+cid)) } diff --git a/pkg/e2e/testdata/TestExposeRange/compose.yaml b/pkg/e2e/testdata/TestExposeRange/compose.yaml new file mode 100644 index 000000000..903950afa --- /dev/null +++ b/pkg/e2e/testdata/TestExposeRange/compose.yaml @@ -0,0 +1,5 @@ +services: + test: + image: alpine + expose: + - "9091-9092" diff --git a/pkg/e2e/fixtures/ipc-test/compose.yaml b/pkg/e2e/testdata/TestIPC/compose.yaml similarity index 83% rename from pkg/e2e/fixtures/ipc-test/compose.yaml rename to pkg/e2e/testdata/TestIPC/compose.yaml index 0659dd96a..f7e5dc00a 100644 --- a/pkg/e2e/fixtures/ipc-test/compose.yaml +++ b/pkg/e2e/testdata/TestIPC/compose.yaml @@ -6,7 +6,7 @@ services: container: image: alpine command: top - ipc: "container:ipc_mode_container" + ipc: "container:${EXTERNAL_NAME}" shareable: image: alpine command: top diff --git a/pkg/e2e/fixtures/container_name/compose.yaml b/pkg/e2e/testdata/TestUpContainerNameConflict/compose.yaml similarity index 63% rename from pkg/e2e/fixtures/container_name/compose.yaml rename to pkg/e2e/testdata/TestUpContainerNameConflict/compose.yaml index c967b885d..4b58311a4 100644 --- a/pkg/e2e/fixtures/container_name/compose.yaml +++ b/pkg/e2e/testdata/TestUpContainerNameConflict/compose.yaml @@ -1,10 +1,10 @@ services: test: image: alpine - container_name: test + container_name: ${FIXED_NAME} command: /bin/true another_test: image: alpine - container_name: test + container_name: ${FIXED_NAME} command: /bin/true diff --git a/pkg/e2e/testdata/TestWaitAndDrop/compose.yaml b/pkg/e2e/testdata/TestWaitAndDrop/compose.yaml new file mode 100644 index 000000000..7173b8a7d --- /dev/null +++ b/pkg/e2e/testdata/TestWaitAndDrop/compose.yaml @@ -0,0 +1,10 @@ +services: + faster: + image: alpine + command: sleep 2 + slower: + image: alpine + command: sleep 5 + infinity: + image: alpine + command: sleep infinity diff --git a/pkg/e2e/testdata/TestWaitOnFaster/compose.yaml b/pkg/e2e/testdata/TestWaitOnFaster/compose.yaml new file mode 100644 index 000000000..7173b8a7d --- /dev/null +++ b/pkg/e2e/testdata/TestWaitOnFaster/compose.yaml @@ -0,0 +1,10 @@ +services: + faster: + image: alpine + command: sleep 2 + slower: + image: alpine + command: sleep 5 + infinity: + image: alpine + command: sleep infinity diff --git a/pkg/e2e/testdata/TestWaitOnSlower/compose.yaml b/pkg/e2e/testdata/TestWaitOnSlower/compose.yaml new file mode 100644 index 000000000..7173b8a7d --- /dev/null +++ b/pkg/e2e/testdata/TestWaitOnSlower/compose.yaml @@ -0,0 +1,10 @@ +services: + faster: + image: alpine + command: sleep 2 + slower: + image: alpine + command: sleep 5 + infinity: + image: alpine + command: sleep infinity diff --git a/pkg/e2e/wait_test.go b/pkg/e2e/wait_test.go index 171027fd9..23a80020d 100644 --- a/pkg/e2e/wait_test.go +++ b/pkg/e2e/wait_test.go @@ -17,7 +17,6 @@ package e2e import ( - "strings" "testing" "time" @@ -26,31 +25,24 @@ import ( ) func TestWaitOnFaster(t *testing.T) { - const projectName = "e2e-wait-faster" - c := NewParallelCLI(t) - - cleanup := func() { - c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "--timeout=0", "--remove-orphans") - } - t.Cleanup(cleanup) - cleanup() - - c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "up", "-d") - c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "faster") + NewScenario(t, "wait must return once the selected service exits, whatever the others do"). + Step("up starts all services", + ComposeCmd("up", "-d")). + Step("wait returns when the fastest service exits", + ComposeCmd("wait", "faster"), + ServiceState("faster", "exited"), + ServiceState("infinity", "running")) } func TestWaitOnSlower(t *testing.T) { - const projectName = "e2e-wait-slower" - c := NewParallelCLI(t) - - cleanup := func() { - c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "--timeout=0", "--remove-orphans") - } - t.Cleanup(cleanup) - cleanup() - - c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "up", "-d") - c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "slower") + NewScenario(t, "wait must block until the selected service exits, even if others exited first"). + Step("up starts all services", + ComposeCmd("up", "-d")). + Step("wait returns when the slower service exits", + ComposeCmd("wait", "slower"), + ServiceState("faster", "exited"), + ServiceState("slower", "exited"), + ServiceState("infinity", "running")) } func TestWaitOnInfinity(t *testing.T) { @@ -89,18 +81,12 @@ func TestWaitOnInfinity(t *testing.T) { } func TestWaitAndDrop(t *testing.T) { - const projectName = "e2e-wait-and-drop" - c := NewParallelCLI(t) - - cleanup := func() { - c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "--timeout=0", "--remove-orphans") - } - t.Cleanup(cleanup) - cleanup() - - c.RunDockerComposeCmd(t, "-f", "./fixtures/wait/compose.yaml", "--project-name", projectName, "up", "-d") - c.RunDockerComposeCmd(t, "--project-name", projectName, "wait", "--down-project", "faster") - - res := c.RunDockerCmd(t, "ps", "--all") - assert.Assert(t, !strings.Contains(res.Combined(), projectName), res.Combined()) + NewScenario(t, "wait --down-project must take the whole project down once the selected service exits"). + Step("up starts all services", + ComposeCmd("up", "-d")). + Step("wait --down-project removes every container when the service exits", + ComposeCmd("wait", "--down-project", "faster"), + ServiceNotCreated("faster"), + ServiceNotCreated("slower"), + ServiceNotCreated("infinity")) }