From 695a2a759af4e97e4255c4ebd408bded7423cdc3 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Mon, 17 Aug 2026 07:49:25 +0200 Subject: [PATCH] test(e2e): start/stop scenarios observe container state, not CLI chatter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrate the state-friendly start/stop tests to the Scenario DSL: - TestStartStop: stop halts containers in place, start brings the same ones back (NotRecreated); the ls checks match the project name exactly in the JSON output, immune to the prefix collisions the other start-stop project names would cause under parallel runs. - TestStartStopWithDependencies: dependency start/stop propagation read from container state instead of 'Container X Started' messages. - TestUpNoDeps replaces the 'Up no-deps links' subtest as its own scenario: up --no-deps must not create the linked dependency. - TestStartSingleServiceAndDependency: create/start walk the dependency chain and nothing else — now asserting the unrelated services have no container at all, stronger than the legacy message matching. The one-off and multi-file tests stay legacy: the vocabulary has no one-off awareness yet and a scenario holds a single compose file. The links and start-stop-deps fixtures become unused and are removed. Signed-off-by: Nicolas De Loof --- pkg/e2e/fixtures/links/compose.yaml | 8 - .../fixtures/start-stop/start-stop-deps.yaml | 17 -- pkg/e2e/start_stop_test.go | 181 ++++++------------ .../compose.yaml | 27 +++ pkg/e2e/testdata/TestStartStop/compose.yaml | 9 + .../compose.yaml | 11 ++ pkg/e2e/testdata/TestUpNoDeps/compose.yaml | 11 ++ 7 files changed, 121 insertions(+), 143 deletions(-) delete mode 100644 pkg/e2e/fixtures/links/compose.yaml delete mode 100644 pkg/e2e/fixtures/start-stop/start-stop-deps.yaml create mode 100644 pkg/e2e/testdata/TestStartSingleServiceAndDependency/compose.yaml create mode 100644 pkg/e2e/testdata/TestStartStop/compose.yaml create mode 100644 pkg/e2e/testdata/TestStartStopWithDependencies/compose.yaml create mode 100644 pkg/e2e/testdata/TestUpNoDeps/compose.yaml diff --git a/pkg/e2e/fixtures/links/compose.yaml b/pkg/e2e/fixtures/links/compose.yaml deleted file mode 100644 index 8c182c4d2..000000000 --- a/pkg/e2e/fixtures/links/compose.yaml +++ /dev/null @@ -1,8 +0,0 @@ -services: - foo: - image: nginx:alpine - links: - - bar - - bar: - image: nginx:alpine diff --git a/pkg/e2e/fixtures/start-stop/start-stop-deps.yaml b/pkg/e2e/fixtures/start-stop/start-stop-deps.yaml deleted file mode 100644 index fb1f7fad7..000000000 --- a/pkg/e2e/fixtures/start-stop/start-stop-deps.yaml +++ /dev/null @@ -1,17 +0,0 @@ -services: - another_2: - image: nginx:alpine - another: - image: nginx:alpine - depends_on: - - another_2 - dep_2: - image: nginx:alpine - dep_1: - image: nginx:alpine - depends_on: - - dep_2 - desired: - image: nginx:alpine - depends_on: - - dep_1 diff --git a/pkg/e2e/start_stop_test.go b/pkg/e2e/start_stop_test.go index 4cc12d404..e37a770b8 100644 --- a/pkg/e2e/start_stop_test.go +++ b/pkg/e2e/start_stop_test.go @@ -22,111 +22,62 @@ import ( "testing" "gotest.tools/v3/assert" - is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/icmd" ) func TestStartStop(t *testing.T) { - c := NewParallelCLI(t) - const projectName = "e2e-start-stop-no-dependencies" - - getProjectRegx := func(status string) string { - // match output with random spaces like: - // e2e-start-stop running(3) - return fmt.Sprintf("%s\\s+%s\\(%d\\)", projectName, status, 2) - } - - t.Run("Up a project", func(t *testing.T) { - res := c.RunDockerComposeCmd(t, "-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "up", - "-d") - assert.Assert(t, strings.Contains(res.Combined(), "Container e2e-start-stop-no-dependencies-simple-1 Started"), res.Combined()) - - res = c.RunDockerComposeCmd(t, "ls", "--all") - assert.Assert(t, is.Regexp(getProjectRegx("running"), res.Stdout())) - - res = c.RunDockerComposeCmd(t, "--project-name", projectName, "ps") - assertServiceStatus(t, projectName, "simple", "Up", res.Stdout()) - assertServiceStatus(t, projectName, "another", "Up", res.Stdout()) - }) - - t.Run("stop project", func(t *testing.T) { - c.RunDockerComposeCmd(t, "-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "stop") - - res := c.RunDockerComposeCmd(t, "ls") - assert.Assert(t, !strings.Contains(res.Combined(), "e2e-start-stop-no-dependencies"), res.Combined()) - - res = c.RunDockerComposeCmd(t, "ls", "--all") - assert.Assert(t, is.Regexp(getProjectRegx("exited"), res.Stdout())) - - res = c.RunDockerComposeCmd(t, "--project-name", projectName, "ps") - assert.Assert(t, !strings.Contains(res.Combined(), "e2e-start-stop-no-dependencies-words-1"), res.Combined()) - - res = c.RunDockerComposeCmd(t, "--project-name", projectName, "ps", "--all") - assertServiceStatus(t, projectName, "simple", "Exited", res.Stdout()) - assertServiceStatus(t, projectName, "another", "Exited", res.Stdout()) - }) - - t.Run("start project", func(t *testing.T) { - c.RunDockerComposeCmd(t, "-f", "./fixtures/start-stop/compose.yaml", "--project-name", projectName, "start") - - res := c.RunDockerComposeCmd(t, "ls") - assert.Assert(t, is.Regexp(getProjectRegx("running"), res.Stdout())) - }) - - t.Run("down", func(t *testing.T) { - _ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down") - }) + s := NewScenario(t, "stop must halt the project's containers in place, start must bring the same ones back") + s.Step("up starts every service", + ComposeCmd("up", "-d"), + ServiceState("simple", "running"), + ServiceState("another", "running")). + Step("ls reports the project as running", + ComposeCmd("ls", "--format", "json"), + OutputContains(`"Name":"`+s.Project()+`"`)). + Step("stop halts the containers without removing them", + ComposeCmd("stop"), + ServiceState("simple", "exited"), + ServiceState("another", "exited")). + Step("a stopped project is hidden from ls", + ComposeCmd("ls", "--format", "json"), + OutputNotContains(`"Name":"`+s.Project()+`"`)). + Step("ls --all still lists the stopped project", + ComposeCmd("ls", "--all", "--format", "json"), + OutputContains(`"Name":"`+s.Project()+`"`)). + Step("start brings the same containers back", + ComposeCmd("start"), + ServiceState("simple", "running"), + ServiceState("another", "running"), + NotRecreated("simple", "another")) } func TestStartStopWithDependencies(t *testing.T) { - c := NewParallelCLI(t) - const projectName = "e2e-start-stop-with-dependencies" + NewScenario(t, "stop must only halt the requested service, start must also start its dependencies"). + Step("up starts the service and its dependency", + ComposeCmd("up", "-d"), + ServiceState("foo", "running"), + ServiceState("bar", "running")). + Step("stop foo leaves the dependency running", + ComposeCmd("stop", "foo"), + ServiceState("foo", "exited"), + ServiceState("bar", "running")). + Step("stop halts the whole project", + ComposeCmd("stop"), + ServiceState("foo", "exited"), + ServiceState("bar", "exited")). + Step("start foo brings its dependency back too", + ComposeCmd("start", "foo"), + ServiceState("foo", "running"), + ServiceState("bar", "running"), + NotRecreated("foo", "bar")) +} - defer c.RunDockerComposeCmd(t, "--project-name", projectName, "rm", "-fsv") - - t.Run("Up", func(t *testing.T) { - res := c.RunDockerComposeCmd(t, "-f", "./fixtures/dependencies/compose.yaml", "--project-name", projectName, - "up", "-d") - assert.Assert(t, strings.Contains(res.Combined(), "Container e2e-start-stop-with-dependencies-foo-1 Started"), res.Combined()) - assert.Assert(t, strings.Contains(res.Combined(), "Container e2e-start-stop-with-dependencies-bar-1 Started"), res.Combined()) - }) - - t.Run("stop foo", func(t *testing.T) { - res := c.RunDockerComposeCmd(t, "--project-name", projectName, "stop", "foo") - - assert.Assert(t, strings.Contains(res.Combined(), "Container e2e-start-stop-with-dependencies-foo-1 Stopped"), res.Combined()) - - res = c.RunDockerComposeCmd(t, "--project-name", projectName, "ps", "--status", "running") - assert.Assert(t, strings.Contains(res.Combined(), "e2e-start-stop-with-dependencies-bar-1"), res.Combined()) - assert.Assert(t, !strings.Contains(res.Combined(), "e2e-start-stop-with-dependencies-foo-1"), res.Combined()) - }) - - t.Run("start foo", func(t *testing.T) { - res := c.RunDockerComposeCmd(t, "--project-name", projectName, "stop") - assert.Assert(t, strings.Contains(res.Combined(), "Container e2e-start-stop-with-dependencies-bar-1 Stopped"), res.Combined()) - - res = c.RunDockerComposeCmd(t, "--project-name", projectName, "start", "foo") - out := res.Combined() - assert.Assert(t, strings.Contains(out, "Container e2e-start-stop-with-dependencies-bar-1 Started"), out) - assert.Assert(t, strings.Contains(out, "Container e2e-start-stop-with-dependencies-foo-1 Started"), out) - - res = c.RunDockerComposeCmd(t, "--project-name", projectName, "ps", "--status", "running") - out = res.Combined() - assert.Assert(t, strings.Contains(out, "e2e-start-stop-with-dependencies-bar-1"), out) - assert.Assert(t, strings.Contains(out, "e2e-start-stop-with-dependencies-foo-1"), out) - }) - - t.Run("Up no-deps links", func(t *testing.T) { - _ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down") - res := c.RunDockerComposeCmd(t, "-f", "./fixtures/links/compose.yaml", "--project-name", projectName, "up", - "--no-deps", "-d", "foo") - assert.Assert(t, strings.Contains(res.Combined(), "Container e2e-start-stop-with-dependencies-foo-1 Started"), res.Combined()) - assert.Assert(t, !strings.Contains(res.Combined(), "Container e2e-start-stop-with-dependencies-bar-1 Started"), res.Combined()) - }) - - t.Run("down", func(t *testing.T) { - _ = c.RunDockerComposeCmd(t, "--project-name", projectName, "down") - }) +func TestUpNoDeps(t *testing.T) { + NewScenario(t, "up --no-deps must not create the service's dependencies"). + Step("up --no-deps starts only the requested service", + ComposeCmd("up", "--no-deps", "-d", "foo"), + ServiceState("foo", "running"), + ServiceNotCreated("bar")) } func TestStartStopWithOneOffs(t *testing.T) { @@ -244,27 +195,21 @@ func TestStartStopMultipleServices(t *testing.T) { } func TestStartSingleServiceAndDependency(t *testing.T) { - cli := NewParallelCLI(t, WithEnv( - "COMPOSE_PROJECT_NAME=e2e-start-single-deps", - "COMPOSE_FILE=./fixtures/start-stop/start-stop-deps.yaml")) - t.Cleanup(func() { - cli.RunDockerComposeCmd(t, "down", "--remove-orphans", "-v", "-t", "0") - }) - - cli.RunDockerComposeCmd(t, "create", "desired") - - res := cli.RunDockerComposeCmd(t, "start", "desired") - desiredServices := []string{"desired", "dep_1", "dep_2"} - for _, s := range desiredServices { - startMsg := fmt.Sprintf("Container e2e-start-single-deps-%s-1 Started", s) - assert.Assert(t, strings.Contains(res.Combined(), startMsg), - fmt.Sprintf("Missing start message for service: %s\n%s", s, res.Combined())) - } - undesiredServices := []string{"another", "another_2"} - for _, s := range undesiredServices { - assert.Assert(t, !strings.Contains(res.Combined(), s), - fmt.Sprintf("Shouldn't have message for service: %s\n%s", s, res.Combined())) - } + NewScenario(t, "start of a created service must start its dependency chain and nothing else"). + Step("create prepares the desired service and its dependencies only", + ComposeCmd("create", "desired"), + ServiceState("desired", "created"), + ServiceState("dep_1", "created"), + ServiceState("dep_2", "created"), + ServiceNotCreated("another"), + ServiceNotCreated("another_2")). + Step("start brings up the desired service with its dependencies, nothing else", + ComposeCmd("start", "desired"), + ServiceState("desired", "running"), + ServiceState("dep_1", "running"), + ServiceState("dep_2", "running"), + ServiceNotCreated("another"), + ServiceNotCreated("another_2")) } func TestStartStopMultipleFiles(t *testing.T) { diff --git a/pkg/e2e/testdata/TestStartSingleServiceAndDependency/compose.yaml b/pkg/e2e/testdata/TestStartSingleServiceAndDependency/compose.yaml new file mode 100644 index 000000000..bdb602623 --- /dev/null +++ b/pkg/e2e/testdata/TestStartSingleServiceAndDependency/compose.yaml @@ -0,0 +1,27 @@ +services: + desired: + image: alpine + init: true + command: sleep infinity + depends_on: + - dep_1 + dep_1: + image: alpine + init: true + command: sleep infinity + depends_on: + - dep_2 + dep_2: + image: alpine + init: true + command: sleep infinity + another: + image: alpine + init: true + command: sleep infinity + depends_on: + - another_2 + another_2: + image: alpine + init: true + command: sleep infinity diff --git a/pkg/e2e/testdata/TestStartStop/compose.yaml b/pkg/e2e/testdata/TestStartStop/compose.yaml new file mode 100644 index 000000000..5a5f3a6ad --- /dev/null +++ b/pkg/e2e/testdata/TestStartStop/compose.yaml @@ -0,0 +1,9 @@ +services: + simple: + image: alpine + init: true + command: sleep infinity + another: + image: alpine + init: true + command: sleep infinity diff --git a/pkg/e2e/testdata/TestStartStopWithDependencies/compose.yaml b/pkg/e2e/testdata/TestStartStopWithDependencies/compose.yaml new file mode 100644 index 000000000..c0932f7a7 --- /dev/null +++ b/pkg/e2e/testdata/TestStartStopWithDependencies/compose.yaml @@ -0,0 +1,11 @@ +services: + foo: + image: alpine + init: true + command: sleep infinity + depends_on: + - bar + bar: + image: alpine + init: true + command: sleep infinity diff --git a/pkg/e2e/testdata/TestUpNoDeps/compose.yaml b/pkg/e2e/testdata/TestUpNoDeps/compose.yaml new file mode 100644 index 000000000..dc76337f7 --- /dev/null +++ b/pkg/e2e/testdata/TestUpNoDeps/compose.yaml @@ -0,0 +1,11 @@ +services: + foo: + image: alpine + init: true + command: sleep infinity + links: + - bar + bar: + image: alpine + init: true + command: sleep infinity