mirror of
https://github.com/docker/compose.git
synced 2026-10-12 02:13:08 +00:00
test(e2e): compose command scenarios — rm, compatibility, config, orphan scoping
- TestRm, TestRemoveOrphaned and TestUnnecessaryResources observe which containers exist instead of grepping docker ps. - TestCompatibility drops the heavyweight sentences fixture: underscore naming shows on any model. - TestConfig/TestConfigInterpolate build their expected rendering from the scenario's own project name and directory; OutputMatches joins the vocabulary for TestInitContainer's ordering assertion. - TestStopWithDependenciesAttached inlines its model. - Kept legacy: TestLocalComposeUp (fixed port, HTTP, shared sentences fixture), TestDownComposefileInParentFolder (project name inferred from a fixture-relative directory), TestAttachRestart (long-running attached up), the COMPOSE_FILE-from-.env family and TestNestedDotEnv (they exercise invocation without -f, which ComposeCmd always sets). - Fixtures init-container/, external/ and dependencies/ are removed. Signed-off-by: Nicolas De Loof <[email protected]>
This commit is contained in:
committed by
Nicolas De loof
parent
37f57744de
commit
a7293130c6
@@ -89,6 +89,20 @@ func StdoutContains(sub string) Check {
|
||||
}
|
||||
}
|
||||
|
||||
// OutputMatches expects the regular expression to match the command's stdout,
|
||||
// for expectations OutputContains cannot express (e.g. ordering).
|
||||
func OutputMatches(pattern string) Check {
|
||||
return Check{
|
||||
name: fmt.Sprintf("output matches %q", pattern),
|
||||
fn: func(ctx *CheckContext) error {
|
||||
if !regexp.MustCompile(pattern).MatchString(ctx.result.Stdout()) {
|
||||
return fmt.Errorf("no match in stdout")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// OutputMatchesCount expects the regular expression to match the command's
|
||||
// stdout exactly n times, e.g. counting how many times a service was built.
|
||||
func OutputMatchesCount(pattern string, n int) Check {
|
||||
|
||||
+62
-130
@@ -26,7 +26,6 @@ import (
|
||||
"time"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/icmd"
|
||||
)
|
||||
|
||||
@@ -159,88 +158,46 @@ func TestAttachRestart(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInitContainer(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "--ansi=never", "--project-directory", "./fixtures/init-container", "up", "--menu=false")
|
||||
defer c.RunDockerComposeCmd(t, "-p", "init-container", "down")
|
||||
assert.Assert(t, is.Regexp("foo-1 | hello(?m:.*)bar-1 | world", res.Stdout()))
|
||||
NewScenario(t, "a service_completed_successfully dependency must run to completion before its dependent starts").
|
||||
Step("up runs the init container first, then the dependent",
|
||||
ComposeCmd("--ansi=never", "up", "--menu=false"),
|
||||
OutputMatches("foo-1 | hello(?m:.*)bar-1 | world"))
|
||||
}
|
||||
|
||||
func TestRm(t *testing.T) {
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
const projectName = "compose-e2e-rm"
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/simple-composefile/compose.yaml", "-p", projectName, "up", "-d")
|
||||
})
|
||||
|
||||
t.Run("rm --stop --force simple", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/simple-composefile/compose.yaml", "-p", projectName, "rm",
|
||||
"--stop", "--force", "simple")
|
||||
res.Assert(t, icmd.Expected{Err: "Removed", ExitCode: 0})
|
||||
})
|
||||
|
||||
t.Run("check containers after rm", func(t *testing.T) {
|
||||
res := c.RunDockerCmd(t, "ps", "--all")
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), projectName+"-simple"), res.Combined())
|
||||
assert.Assert(t, strings.Contains(res.Combined(), projectName+"-another"), res.Combined())
|
||||
})
|
||||
|
||||
t.Run("up (again)", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/simple-composefile/compose.yaml", "-p", projectName, "up", "-d")
|
||||
})
|
||||
|
||||
t.Run("rm ---stop --force <none>", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/simple-composefile/compose.yaml", "-p", projectName, "rm",
|
||||
"--stop", "--force")
|
||||
res.Assert(t, icmd.Expected{ExitCode: 0})
|
||||
})
|
||||
|
||||
t.Run("check containers after rm", func(t *testing.T) {
|
||||
res := c.RunDockerCmd(t, "ps", "--all")
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), projectName+"-simple"), res.Combined())
|
||||
assert.Assert(t, !strings.Contains(res.Combined(), projectName+"-another"), res.Combined())
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd(t, "-p", projectName, "down")
|
||||
})
|
||||
NewScenario(t, "rm --stop --force must remove the selected service's containers, or every service's without selection").
|
||||
Step("up starts both services",
|
||||
ComposeCmd("up", "-d"),
|
||||
ServiceState("simple", "running"),
|
||||
ServiceState("another", "running")).
|
||||
Step("rm on one service removes only its container",
|
||||
ComposeCmd("rm", "--stop", "--force", "simple"),
|
||||
ServiceNotCreated("simple"),
|
||||
ServiceState("another", "running")).
|
||||
Step("up brings the removed service back",
|
||||
ComposeCmd("up", "-d"),
|
||||
ServiceState("simple", "running")).
|
||||
Step("rm without selection removes every container",
|
||||
ComposeCmd("rm", "--stop", "--force"),
|
||||
ServiceNotCreated("simple"),
|
||||
ServiceNotCreated("another"))
|
||||
}
|
||||
|
||||
func TestCompatibility(t *testing.T) {
|
||||
// this test shares a fixture with TestLocalComposeUp and can't run at the same time
|
||||
c := NewCLI(t)
|
||||
|
||||
const projectName = "compose-e2e-compatibility"
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd(t, "--compatibility", "-f", "./fixtures/sentences/compose.yaml", "--project-name",
|
||||
projectName, "up", "-d")
|
||||
})
|
||||
|
||||
t.Run("check container names", func(t *testing.T) {
|
||||
res := c.RunDockerCmd(t, "ps", "--format", "{{.Names}}")
|
||||
res.Assert(t, icmd.Expected{Out: "compose-e2e-compatibility_web_1"})
|
||||
res.Assert(t, icmd.Expected{Out: "compose-e2e-compatibility_words_1"})
|
||||
res.Assert(t, icmd.Expected{Out: "compose-e2e-compatibility_db_1"})
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
c.RunDockerComposeCmd(t, "-p", projectName, "down")
|
||||
})
|
||||
s := NewScenario(t, "--compatibility must name containers with underscore separators")
|
||||
s.Step("up names the container the v1 way",
|
||||
ComposeCmd("--compatibility", "up", "-d"),
|
||||
ServiceState("simple", "running")).
|
||||
Step("the container name uses underscores",
|
||||
DockerCmd("ps", "--filter", "label=com.docker.compose.project="+s.Project(), "--format", "{{.Names}}"),
|
||||
OutputContains(s.Project()+"_simple_1"))
|
||||
}
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
const projectName = "compose-e2e-config"
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
wd, err := os.Getwd()
|
||||
assert.NilError(t, err)
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/simple-build-test/compose.yaml", "-p", projectName, "config")
|
||||
res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`name: %s
|
||||
s := NewScenario(t, "config must render the canonical model, resolving the build context to an absolute path")
|
||||
s.Step("the rendering resolves the context and names the default network",
|
||||
ComposeCmd("config"),
|
||||
OutputContains(fmt.Sprintf(`name: %s
|
||||
services:
|
||||
nginx:
|
||||
build:
|
||||
@@ -250,24 +207,18 @@ services:
|
||||
default: null
|
||||
networks:
|
||||
default:
|
||||
name: compose-e2e-config_default
|
||||
`, projectName, filepath.Join(wd, "fixtures", "simple-build-test", "nginx-build")), ExitCode: 0})
|
||||
})
|
||||
name: %s_default
|
||||
`, s.Project(), filepath.Join(s.Dir(), "nginx-build"), s.Project())))
|
||||
}
|
||||
|
||||
func TestConfigInterpolate(t *testing.T) {
|
||||
const projectName = "compose-e2e-config-interpolate"
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
wd, err := os.Getwd()
|
||||
assert.NilError(t, err)
|
||||
|
||||
t.Run("config", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/simple-build-test/compose-interpolate.yaml", "-p", projectName, "config", "--no-interpolate")
|
||||
res.Assert(t, icmd.Expected{Out: fmt.Sprintf(`name: %s
|
||||
s := NewScenario(t, "config --no-interpolate must keep variable expressions while still resolving paths")
|
||||
s.Step("the rendering keeps the dockerfile variable",
|
||||
ComposeCmd("config", "--no-interpolate"),
|
||||
OutputContains(fmt.Sprintf(`name: %s
|
||||
networks:
|
||||
default:
|
||||
name: compose-e2e-config-interpolate_default
|
||||
name: %s_default
|
||||
services:
|
||||
nginx:
|
||||
build:
|
||||
@@ -275,43 +226,28 @@ services:
|
||||
dockerfile: ${MYVAR}
|
||||
networks:
|
||||
default: null
|
||||
`, projectName, filepath.Join(wd, "fixtures", "simple-build-test", "nginx-build")), ExitCode: 0})
|
||||
})
|
||||
`, s.Project(), s.Project(), filepath.Join(s.Dir(), "nginx-build"))))
|
||||
}
|
||||
|
||||
func TestStopWithDependenciesAttached(t *testing.T) {
|
||||
const projectName = "compose-e2e-stop-with-deps"
|
||||
c := NewParallelCLI(t, WithEnv("COMMAND=echo hello"))
|
||||
|
||||
cleanup := func() {
|
||||
c.RunDockerComposeCmd(t, "-p", projectName, "down", "--remove-orphans", "--timeout=0")
|
||||
}
|
||||
cleanup()
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/dependencies/compose.yaml", "-p", projectName, "up", "--attach-dependencies", "foo", "--menu=false")
|
||||
res.Assert(t, icmd.Expected{Out: "exited with code 0"})
|
||||
NewScenario(t, "up --attach-dependencies must stop with the target service, reporting its exit").
|
||||
Step("up returns when the attached service exits",
|
||||
ComposeCmd("up", "--attach-dependencies", "foo", "--menu=false").Within(60*time.Second),
|
||||
OutputContains("exited with code 0"))
|
||||
}
|
||||
|
||||
func TestRemoveOrphaned(t *testing.T) {
|
||||
const projectName = "compose-e2e-remove-orphaned"
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
cleanup := func() {
|
||||
c.RunDockerComposeCmd(t, "-p", projectName, "down", "--remove-orphans", "--timeout=0")
|
||||
}
|
||||
cleanup()
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
// run stack
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/sentences/compose.yaml", "-p", projectName, "up", "-d")
|
||||
|
||||
// down "web" service with orphaned removed
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/sentences/compose.yaml", "-p", projectName, "down", "--remove-orphans", "web")
|
||||
|
||||
// check "words" service has not been considered orphaned
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/sentences/compose.yaml", "-p", projectName, "ps", "--format", "{{.Name}}")
|
||||
res.Assert(t, icmd.Expected{Out: fmt.Sprintf("%s-words-1", projectName)})
|
||||
NewScenario(t, "down --remove-orphans scoped to a service must not touch the other services").
|
||||
Step("up starts the stack",
|
||||
ComposeCmd("up", "-d"),
|
||||
ServiceState("web", "running"),
|
||||
ServiceState("words", "running"),
|
||||
ServiceState("db", "running")).
|
||||
Step("down scoped to one service leaves the others alone",
|
||||
ComposeCmd("down", "--remove-orphans", "web"),
|
||||
ServiceNotCreated("web"),
|
||||
ServiceState("words", "running"),
|
||||
ServiceState("db", "running"))
|
||||
}
|
||||
|
||||
func TestComposeFileSetByDotEnv(t *testing.T) {
|
||||
@@ -390,16 +326,12 @@ func TestNestedDotEnv(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnnecessaryResources(t *testing.T) {
|
||||
const projectName = "compose-e2e-unnecessary-resources"
|
||||
c := NewParallelCLI(t)
|
||||
defer c.cleanupWithDown(t, projectName)
|
||||
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/external/compose.yaml", "-p", projectName, "up", "-d")
|
||||
res.Assert(t, icmd.Expected{
|
||||
ExitCode: 1,
|
||||
Err: "network foo_bar declared as external, but could not be found",
|
||||
})
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/external/compose.yaml", "-p", projectName, "up", "-d", "test")
|
||||
// Should not fail as missing external network is not used
|
||||
NewScenario(t, "a missing external network must only fail the services that use it").
|
||||
Step("up with every service is rejected over the missing network",
|
||||
ComposeCmd("up", "-d").MayFail(),
|
||||
ExitCode(1),
|
||||
OutputContains("network foo_bar declared as external, but could not be found")).
|
||||
Step("up scoped to the unaffected service succeeds",
|
||||
ComposeCmd("up", "-d", "test"),
|
||||
ServiceState("test", "running"))
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
services:
|
||||
foo:
|
||||
image: nginx:alpine
|
||||
command: "${COMMAND}"
|
||||
depends_on:
|
||||
- bar
|
||||
|
||||
bar:
|
||||
image: nginx:alpine
|
||||
scale: 2
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
services:
|
||||
test:
|
||||
image: nginx:alpine
|
||||
|
||||
other:
|
||||
image: nginx:alpine
|
||||
networks:
|
||||
test_network:
|
||||
ipv4_address: 8.8.8.8
|
||||
|
||||
networks:
|
||||
test_network:
|
||||
external: true
|
||||
name: foo_bar
|
||||
@@ -0,0 +1,5 @@
|
||||
services:
|
||||
simple:
|
||||
image: alpine
|
||||
init: true
|
||||
command: top
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
services:
|
||||
nginx:
|
||||
build:
|
||||
context: nginx-build
|
||||
dockerfile: Dockerfile
|
||||
@@ -0,0 +1,2 @@
|
||||
FROM alpine
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
services:
|
||||
nginx:
|
||||
build:
|
||||
context: nginx-build
|
||||
dockerfile: ${MYVAR}
|
||||
@@ -0,0 +1,2 @@
|
||||
FROM alpine
|
||||
|
||||
Vendored
@@ -0,0 +1,13 @@
|
||||
services:
|
||||
web:
|
||||
image: alpine
|
||||
init: true
|
||||
command: sleep infinity
|
||||
words:
|
||||
image: alpine
|
||||
init: true
|
||||
command: sleep infinity
|
||||
db:
|
||||
image: alpine
|
||||
init: true
|
||||
command: sleep infinity
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
simple:
|
||||
image: alpine
|
||||
init: true
|
||||
command: top
|
||||
another:
|
||||
image: alpine
|
||||
init: true
|
||||
command: top
|
||||
@@ -0,0 +1,10 @@
|
||||
services:
|
||||
foo:
|
||||
image: alpine
|
||||
command: echo hello
|
||||
depends_on:
|
||||
- bar
|
||||
bar:
|
||||
image: alpine
|
||||
init: true
|
||||
command: sleep infinity
|
||||
@@ -0,0 +1,18 @@
|
||||
services:
|
||||
test:
|
||||
image: alpine
|
||||
init: true
|
||||
command: sleep infinity
|
||||
|
||||
other:
|
||||
image: alpine
|
||||
init: true
|
||||
command: sleep infinity
|
||||
networks:
|
||||
test_network:
|
||||
ipv4_address: 8.8.8.8
|
||||
|
||||
networks:
|
||||
test_network:
|
||||
external: true
|
||||
name: foo_bar
|
||||
Reference in New Issue
Block a user