mirror of
https://github.com/docker/compose.git
synced 2026-10-12 02:13:08 +00:00
test(e2e): expose, container_name, ipc and wait scenarios
- 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 <[email protected]>
This commit is contained in:
@@ -20,24 +20,37 @@ package e2e
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/icmd"
|
||||
)
|
||||
|
||||
func TestUpContainerNameConflict(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
const projectName = "e2e-container_name_conflict"
|
||||
s := NewScenario(t, "two services claiming the same container_name must be rejected together, but each must run alone")
|
||||
name := s.Project() + "-fixed"
|
||||
s.Compose(`
|
||||
services:
|
||||
test:
|
||||
image: alpine
|
||||
container_name: ${FIXED_NAME}
|
||||
command: /bin/true
|
||||
|
||||
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")
|
||||
another_test:
|
||||
image: alpine
|
||||
container_name: ${FIXED_NAME}
|
||||
command: /bin/true
|
||||
`).
|
||||
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"))
|
||||
}
|
||||
|
||||
+7
-17
@@ -19,30 +19,20 @@
|
||||
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
|
||||
// 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").
|
||||
Compose(`
|
||||
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")
|
||||
`).
|
||||
Step("up creates and runs the service",
|
||||
ComposeCmd("up"))
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
services:
|
||||
test:
|
||||
image: alpine
|
||||
container_name: test
|
||||
command: /bin/true
|
||||
|
||||
another_test:
|
||||
image: alpine
|
||||
container_name: test
|
||||
command: /bin/true
|
||||
@@ -1,13 +0,0 @@
|
||||
services:
|
||||
service:
|
||||
image: alpine
|
||||
command: top
|
||||
ipc: "service:shareable"
|
||||
container:
|
||||
image: alpine
|
||||
command: top
|
||||
ipc: "container:ipc_mode_container"
|
||||
shareable:
|
||||
image: alpine
|
||||
command: top
|
||||
ipc: shareable
|
||||
+36
-37
@@ -17,48 +17,47 @@
|
||||
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.Compose(`
|
||||
services:
|
||||
service:
|
||||
image: alpine
|
||||
command: top
|
||||
ipc: "service:shareable"
|
||||
container:
|
||||
image: alpine
|
||||
command: top
|
||||
ipc: "container:${EXTERNAL_NAME}"
|
||||
shareable:
|
||||
image: alpine
|
||||
command: top
|
||||
ipc: shareable
|
||||
`).
|
||||
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))
|
||||
}
|
||||
|
||||
+39
-37
@@ -17,7 +17,6 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -25,32 +24,40 @@ import (
|
||||
"gotest.tools/v3/icmd"
|
||||
)
|
||||
|
||||
const waitCompose = `
|
||||
services:
|
||||
faster:
|
||||
image: alpine
|
||||
command: sleep 2
|
||||
slower:
|
||||
image: alpine
|
||||
command: sleep 5
|
||||
infinity:
|
||||
image: alpine
|
||||
command: sleep infinity
|
||||
`
|
||||
|
||||
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").
|
||||
Compose(waitCompose).
|
||||
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").
|
||||
Compose(waitCompose).
|
||||
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 +96,13 @@ 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").
|
||||
Compose(waitCompose).
|
||||
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"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user