From 88a83c713ba154245b65f689eae8548ad86616a5 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Thu, 8 Oct 2026 10:23:26 +0200 Subject: [PATCH 1/4] test(e2e): lock the behavior of the foreground up Most e2e coverage of the start phase runs detached; the foreground `up`, whose create and start phases are driven by separate code paths, had none for hooks, health-gated dependencies, a container that exits as soon as it starts, restarts of exited containers, recreation, dependency timeouts or dry-run. Add scenarios that fix these observables before the machinery behind it changes. Signed-off-by: Nicolas De Loof --- .../TestUpDryRunCreatesNothing/compose.yaml | 5 + .../compose.yaml | 16 +++ .../TestUpInteractiveHookLogs/compose.yaml | 13 +++ .../compose.yaml | 8 ++ .../compose.yaml | 6 ++ .../compose.yaml | 4 + .../compose.yaml | 22 ++++ pkg/e2e/up_interactive_test.go | 102 ++++++++++++++++++ 8 files changed, 176 insertions(+) create mode 100644 pkg/e2e/testdata/TestUpDryRunCreatesNothing/compose.yaml create mode 100644 pkg/e2e/testdata/TestUpInteractiveDependencyTimeout/compose.yaml create mode 100644 pkg/e2e/testdata/TestUpInteractiveHookLogs/compose.yaml create mode 100644 pkg/e2e/testdata/TestUpInteractiveImmediateExit/compose.yaml create mode 100644 pkg/e2e/testdata/TestUpInteractiveRecreateStreamsNewContainer/compose.yaml create mode 100644 pkg/e2e/testdata/TestUpInteractiveRestartsExitedContainer/compose.yaml create mode 100644 pkg/e2e/testdata/TestUpInteractiveWaitsForHealthy/compose.yaml create mode 100644 pkg/e2e/up_interactive_test.go diff --git a/pkg/e2e/testdata/TestUpDryRunCreatesNothing/compose.yaml b/pkg/e2e/testdata/TestUpDryRunCreatesNothing/compose.yaml new file mode 100644 index 00000000000..b7020371ace --- /dev/null +++ b/pkg/e2e/testdata/TestUpDryRunCreatesNothing/compose.yaml @@ -0,0 +1,5 @@ +services: + app: + image: alpine + command: sleep infinity + init: true diff --git a/pkg/e2e/testdata/TestUpInteractiveDependencyTimeout/compose.yaml b/pkg/e2e/testdata/TestUpInteractiveDependencyTimeout/compose.yaml new file mode 100644 index 00000000000..dd15a9420a5 --- /dev/null +++ b/pkg/e2e/testdata/TestUpInteractiveDependencyTimeout/compose.yaml @@ -0,0 +1,16 @@ +services: + db: + image: alpine + command: sleep infinity + init: true + healthcheck: + test: ["CMD", "false"] + interval: 1s + timeout: 1s + retries: 100 + web: + image: alpine + command: echo web-started + depends_on: + db: + condition: service_healthy diff --git a/pkg/e2e/testdata/TestUpInteractiveHookLogs/compose.yaml b/pkg/e2e/testdata/TestUpInteractiveHookLogs/compose.yaml new file mode 100644 index 00000000000..f7c2b4b394f --- /dev/null +++ b/pkg/e2e/testdata/TestUpInteractiveHookLogs/compose.yaml @@ -0,0 +1,13 @@ +services: + app: + image: alpine + command: sh -c 'echo "app-read:$(cat /shared/init.txt)"; sleep 3' + volumes: + - data:/shared + pre_start: + - image: alpine + command: sh -c 'echo "pre-start-hook-ran" | tee /shared/init.txt' + post_start: + - command: echo post-start-hook-ran +volumes: + data: diff --git a/pkg/e2e/testdata/TestUpInteractiveImmediateExit/compose.yaml b/pkg/e2e/testdata/TestUpInteractiveImmediateExit/compose.yaml new file mode 100644 index 00000000000..c0bcba59920 --- /dev/null +++ b/pkg/e2e/testdata/TestUpInteractiveImmediateExit/compose.yaml @@ -0,0 +1,8 @@ +services: + quick: + image: alpine + command: echo hi + long: + image: alpine + command: sleep infinity + init: true diff --git a/pkg/e2e/testdata/TestUpInteractiveRecreateStreamsNewContainer/compose.yaml b/pkg/e2e/testdata/TestUpInteractiveRecreateStreamsNewContainer/compose.yaml new file mode 100644 index 00000000000..0c8339de58a --- /dev/null +++ b/pkg/e2e/testdata/TestUpInteractiveRecreateStreamsNewContainer/compose.yaml @@ -0,0 +1,6 @@ +services: + app: + image: alpine + command: sh -c 'echo "value=$VALUE"; sleep 2' + environment: + VALUE: ${VALUE:-one} diff --git a/pkg/e2e/testdata/TestUpInteractiveRestartsExitedContainer/compose.yaml b/pkg/e2e/testdata/TestUpInteractiveRestartsExitedContainer/compose.yaml new file mode 100644 index 00000000000..8f80cc9df5c --- /dev/null +++ b/pkg/e2e/testdata/TestUpInteractiveRestartsExitedContainer/compose.yaml @@ -0,0 +1,4 @@ +services: + app: + image: alpine + command: sh -c 'if [ -f /tmp/ran ]; then echo second-run; else touch /tmp/ran; fi' diff --git a/pkg/e2e/testdata/TestUpInteractiveWaitsForHealthy/compose.yaml b/pkg/e2e/testdata/TestUpInteractiveWaitsForHealthy/compose.yaml new file mode 100644 index 00000000000..a940773b8dd --- /dev/null +++ b/pkg/e2e/testdata/TestUpInteractiveWaitsForHealthy/compose.yaml @@ -0,0 +1,22 @@ +services: + db: + image: alpine + command: sh -c 'sleep 3; touch /shared/ready; sleep 60' + init: true + volumes: + - shared:/shared + healthcheck: + test: ["CMD", "test", "-f", "/shared/ready"] + interval: 1s + timeout: 1s + retries: 30 + web: + image: alpine + command: sh -c 'if [ -f /shared/ready ]; then echo web-saw-ready; else echo web-too-early; fi' + volumes: + - shared:/shared + depends_on: + db: + condition: service_healthy +volumes: + shared: diff --git a/pkg/e2e/up_interactive_test.go b/pkg/e2e/up_interactive_test.go new file mode 100644 index 00000000000..c052943ce03 --- /dev/null +++ b/pkg/e2e/up_interactive_test.go @@ -0,0 +1,102 @@ +//go:build e2e && !windows + +/* + Copyright 2026 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package e2e + +import ( + "testing" + "time" +) + +// The scenarios below lock the observable behavior of the foreground +// (non-detached) `compose up`, whose create and start phases are driven by two +// separate code paths today. They exist so a change of the machinery behind +// it (epic #14081, lot 2) can be reviewed against a fixed specification. +// Most e2e coverage of the start phase runs detached; these do not. + +func TestUpInteractiveHookLogs(t *testing.T) { + NewScenario(t, "a foreground up must stream pre_start and post_start hook output into its session"). + Step("up prints both hooks' output, and the service reads what pre_start wrote before it started", + ComposeCmd("up", "--menu=false").Within(120*time.Second), + OutputContains("pre-start-hook-ran"), + OutputContains("app-read:pre-start-hook-ran"), + OutputContains("post-start-hook-ran"), + ServiceState("app", "exited")) +} + +func TestUpInteractiveImmediateExit(t *testing.T) { + NewScenario(t, "a foreground up --abort-on-container-exit must notice a container that exits as soon as it starts"). + Step("up aborts on the instantly exiting container instead of waiting for the long-running one", + ComposeCmd("up", "--menu=false", "--abort-on-container-exit").Within(60*time.Second), + OutputContains("quick-1 exited with code 0"), + OutputContains("Aborting on container exit"), + ServiceState("quick", "exited"), + ServiceState("long", "exited")) +} + +func TestUpInteractiveRestartsExitedContainer(t *testing.T) { + NewScenario(t, "a foreground up must start a container that exited earlier, reusing it"). + Step("up -d leaves the service exited after its first run", + ComposeCmd("up", "-d"), + Eventually(ServiceState("app", "exited"), 15*time.Second)). + Step("a foreground up runs the same container a second time", + ComposeCmd("up", "--menu=false").Within(60*time.Second), + NotRecreated("app"), + OutputContains("second-run"), + ServiceState("app", "exited")) +} + +func TestUpInteractiveWaitsForHealthy(t *testing.T) { + NewScenario(t, "a foreground up must not start a service before its service_healthy dependency is healthy"). + Step("up starts web only once db reports healthy", + ComposeCmd("up", "--menu=false", "--abort-on-container-exit").Within(120*time.Second), + OutputContains("web-saw-ready"), + OutputNotContains("web-too-early")) +} + +func TestUpInteractiveRecreateStreamsNewContainer(t *testing.T) { + NewScenario(t, "a foreground up after a config change must recreate the service and stream the new container's output"). + Step("up -d creates the service with its first configuration", + ComposeCmd("up", "-d"), + Eventually(ServiceState("app", "exited"), 15*time.Second)). + Step("a foreground up with another value recreates it and shows the new output", + ComposeCmd("up", "--menu=false").WithEnv("VALUE=two").Within(60*time.Second), + Recreated("app"), + OutputContains("value=two")) +} + +func TestUpInteractiveDependencyTimeout(t *testing.T) { + NewScenario(t, "a foreground up with --wait-timeout must fail once a dependency stays unhealthy, without starting the dependent"). + Step("up fails on the dependency timeout; web is created but never started", + ComposeCmd("up", "--menu=false", "--wait-timeout", "3").MayFail().Within(60*time.Second), + ExitCode(1), + OutputContains("timeout waiting for dependencies"), + OutputNotContains("web-started"), + ServiceState("db", "running"), + ServiceState("web", "created")) +} + +func TestUpDryRunCreatesNothing(t *testing.T) { + s := NewScenario(t, "a foreground up --dry-run must plan the create phase only, never the start phase") + s.Step("up --dry-run reports the creation, no start, and leaves no container behind", + ComposeCmd("up", "--menu=false", "--dry-run").Within(60*time.Second), + OutputContains("Container "+s.Project()+"-app-1 Created"), + OutputNotContains("Start"), + OutputContains("interactive run is not supported in dry-run mode"), + ServiceNotCreated("app")) +} From 0e34c016b4860e80351917a1aea1f5f348bcd1e9 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Thu, 8 Oct 2026 11:10:58 +0200 Subject: [PATCH 2/4] test(e2e): stop asserting pre_start output the foreground up never streams The hook scenario matched "pre-start-hook-ran" in the output, but the service's own line "app-read:pre-start-hook-ran" contains it, so the assertion passed without any pre_start output reaching the session. A foreground up streams post_start's output but not pre_start's today (checked on main too). Lock what actually holds: pre_start's effect, read by the service through a value distinct from anything it logs. Signed-off-by: Nicolas De Loof --- pkg/e2e/testdata/TestUpInteractiveHookLogs/compose.yaml | 2 +- pkg/e2e/up_interactive_test.go | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/e2e/testdata/TestUpInteractiveHookLogs/compose.yaml b/pkg/e2e/testdata/TestUpInteractiveHookLogs/compose.yaml index f7c2b4b394f..8c79f2d665c 100644 --- a/pkg/e2e/testdata/TestUpInteractiveHookLogs/compose.yaml +++ b/pkg/e2e/testdata/TestUpInteractiveHookLogs/compose.yaml @@ -6,7 +6,7 @@ services: - data:/shared pre_start: - image: alpine - command: sh -c 'echo "pre-start-hook-ran" | tee /shared/init.txt' + command: sh -c 'echo "pre-start-file-content" > /shared/init.txt' post_start: - command: echo post-start-hook-ran volumes: diff --git a/pkg/e2e/up_interactive_test.go b/pkg/e2e/up_interactive_test.go index c052943ce03..9e86390a7f5 100644 --- a/pkg/e2e/up_interactive_test.go +++ b/pkg/e2e/up_interactive_test.go @@ -29,12 +29,13 @@ import ( // it (epic #14081, lot 2) can be reviewed against a fixed specification. // Most e2e coverage of the start phase runs detached; these do not. +// pre_start output is not asserted: a foreground up does not stream it into the +// session today, only post_start's. What is locked for pre_start is its effect. func TestUpInteractiveHookLogs(t *testing.T) { - NewScenario(t, "a foreground up must stream pre_start and post_start hook output into its session"). - Step("up prints both hooks' output, and the service reads what pre_start wrote before it started", + NewScenario(t, "a foreground up must run pre_start before the service starts and stream post_start output into its session"). + Step("up shows post_start's output, and the service reads what pre_start wrote before it started", ComposeCmd("up", "--menu=false").Within(120*time.Second), - OutputContains("pre-start-hook-ran"), - OutputContains("app-read:pre-start-hook-ran"), + OutputContains("app-read:pre-start-file-content"), OutputContains("post-start-hook-ran"), ServiceState("app", "exited")) } From 4e8a4d0df5e65bbf75ee6264d3186c975b986b5d Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Thu, 8 Oct 2026 11:23:38 +0200 Subject: [PATCH 3/4] test(e2e): harden the foreground up scenarios against review findings Wait for the long-running service's final state after an abort instead of reading it the instant the command returns, document why the dependent is expected in the created state on a dependency timeout, and narrow the dry-run "no start" check to the service's own lines. Signed-off-by: Nicolas De Loof --- pkg/e2e/up_interactive_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/e2e/up_interactive_test.go b/pkg/e2e/up_interactive_test.go index 9e86390a7f5..0298a332e84 100644 --- a/pkg/e2e/up_interactive_test.go +++ b/pkg/e2e/up_interactive_test.go @@ -47,7 +47,7 @@ func TestUpInteractiveImmediateExit(t *testing.T) { OutputContains("quick-1 exited with code 0"), OutputContains("Aborting on container exit"), ServiceState("quick", "exited"), - ServiceState("long", "exited")) + Eventually(ServiceState("long", "exited"), 10*time.Second)) } func TestUpInteractiveRestartsExitedContainer(t *testing.T) { @@ -89,6 +89,9 @@ func TestUpInteractiveDependencyTimeout(t *testing.T) { OutputContains("timeout waiting for dependencies"), OutputNotContains("web-started"), ServiceState("db", "running"), + // web's container is created in the create phase, before the start + // phase blocks on db's health: "created" (not "no container") is + // what says it was prepared but never started. ServiceState("web", "created")) } @@ -97,7 +100,7 @@ func TestUpDryRunCreatesNothing(t *testing.T) { s.Step("up --dry-run reports the creation, no start, and leaves no container behind", ComposeCmd("up", "--menu=false", "--dry-run").Within(60*time.Second), OutputContains("Container "+s.Project()+"-app-1 Created"), - OutputNotContains("Start"), + OutputNotContains(s.Project()+"-app-1 Start"), OutputContains("interactive run is not supported in dry-run mode"), ServiceNotCreated("app")) } From 7061d16a7c0c96099921eb68c337a75fc9358fbb Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Mon, 26 Oct 2026 10:19:15 +0100 Subject: [PATCH 4/4] test(e2e): lock pre_start's streamed output and tighten the foreground up scenarios A foreground up does stream a pre_start hook's output into the session since the hook's log stream is opened once it is started. The hook scenario had stopped asserting it on the belief it never did; its fixture only wrote to a file, so nothing could reach stdout. The pre_start now also prints a marker the service never logs, asserted on stdout, and the scenario states a single intent covering both hooks. Also tighten the scenarios after review: - assert a container's printed output on stdout rather than on the whole output, and add the state or negative checks that were missing (ContainerEnv after a recreation, web exited once its dependency was healthy, no output of the replaced container); - the dependency timeout scenario carries the "deadline exceeded" guard of its detached counterpart, with a fixture aligned on it; - drop the abort message and the useless wait from the immediate-exit scenario: up only returns once the application is stopped; - rename the dry-run scenario after the others, minimize its fixture and stop coupling it to the dry-run end-of-output wording; - explain the sleeps of the fixtures, and give the hook one a larger margin; - build tag e2e only: nothing here is Unix-specific. Signed-off-by: Nicolas De Loof --- .../TestUpDryRunCreatesNothing/compose.yaml | 5 --- .../compose.yaml | 2 - .../TestUpInteractiveDryRun/compose.yaml | 3 ++ .../TestUpInteractiveHookLogs/compose.yaml | 8 +++- .../compose.yaml | 1 + pkg/e2e/up_interactive_test.go | 43 +++++++++---------- 6 files changed, 31 insertions(+), 31 deletions(-) delete mode 100644 pkg/e2e/testdata/TestUpDryRunCreatesNothing/compose.yaml create mode 100644 pkg/e2e/testdata/TestUpInteractiveDryRun/compose.yaml diff --git a/pkg/e2e/testdata/TestUpDryRunCreatesNothing/compose.yaml b/pkg/e2e/testdata/TestUpDryRunCreatesNothing/compose.yaml deleted file mode 100644 index b7020371ace..00000000000 --- a/pkg/e2e/testdata/TestUpDryRunCreatesNothing/compose.yaml +++ /dev/null @@ -1,5 +0,0 @@ -services: - app: - image: alpine - command: sleep infinity - init: true diff --git a/pkg/e2e/testdata/TestUpInteractiveDependencyTimeout/compose.yaml b/pkg/e2e/testdata/TestUpInteractiveDependencyTimeout/compose.yaml index dd15a9420a5..114b50c6418 100644 --- a/pkg/e2e/testdata/TestUpInteractiveDependencyTimeout/compose.yaml +++ b/pkg/e2e/testdata/TestUpInteractiveDependencyTimeout/compose.yaml @@ -2,11 +2,9 @@ services: db: image: alpine command: sleep infinity - init: true healthcheck: test: ["CMD", "false"] interval: 1s - timeout: 1s retries: 100 web: image: alpine diff --git a/pkg/e2e/testdata/TestUpInteractiveDryRun/compose.yaml b/pkg/e2e/testdata/TestUpInteractiveDryRun/compose.yaml new file mode 100644 index 00000000000..a35fee06453 --- /dev/null +++ b/pkg/e2e/testdata/TestUpInteractiveDryRun/compose.yaml @@ -0,0 +1,3 @@ +services: + app: + image: alpine diff --git a/pkg/e2e/testdata/TestUpInteractiveHookLogs/compose.yaml b/pkg/e2e/testdata/TestUpInteractiveHookLogs/compose.yaml index 8c79f2d665c..fc7cf9fc2b6 100644 --- a/pkg/e2e/testdata/TestUpInteractiveHookLogs/compose.yaml +++ b/pkg/e2e/testdata/TestUpInteractiveHookLogs/compose.yaml @@ -1,12 +1,16 @@ services: app: image: alpine - command: sh -c 'echo "app-read:$(cat /shared/init.txt)"; sleep 3' + # the sleep keeps the container running while post_start execs into it and + # the session streams its output + command: sh -c 'echo "app-read:$(cat /shared/init.txt)"; sleep 5' volumes: - data:/shared pre_start: - image: alpine - command: sh -c 'echo "pre-start-file-content" > /shared/init.txt' + # prints a marker the service never logs, so its presence in the + # session can only come from the hook's own output stream + command: sh -c 'echo pre-start-hook-stdout; echo "pre-start-file-content" > /shared/init.txt' post_start: - command: echo post-start-hook-ran volumes: diff --git a/pkg/e2e/testdata/TestUpInteractiveRecreateStreamsNewContainer/compose.yaml b/pkg/e2e/testdata/TestUpInteractiveRecreateStreamsNewContainer/compose.yaml index 0c8339de58a..bf0ece21b24 100644 --- a/pkg/e2e/testdata/TestUpInteractiveRecreateStreamsNewContainer/compose.yaml +++ b/pkg/e2e/testdata/TestUpInteractiveRecreateStreamsNewContainer/compose.yaml @@ -1,6 +1,7 @@ services: app: image: alpine + # the sleep keeps the container running while the session attaches to it command: sh -c 'echo "value=$VALUE"; sleep 2' environment: VALUE: ${VALUE:-one} diff --git a/pkg/e2e/up_interactive_test.go b/pkg/e2e/up_interactive_test.go index 0298a332e84..00b6d3a7d78 100644 --- a/pkg/e2e/up_interactive_test.go +++ b/pkg/e2e/up_interactive_test.go @@ -1,4 +1,4 @@ -//go:build e2e && !windows +//go:build e2e /* Copyright 2026 Docker Compose CLI authors @@ -23,20 +23,16 @@ import ( "time" ) -// The scenarios below lock the observable behavior of the foreground -// (non-detached) `compose up`, whose create and start phases are driven by two -// separate code paths today. They exist so a change of the machinery behind -// it (epic #14081, lot 2) can be reviewed against a fixed specification. -// Most e2e coverage of the start phase runs detached; these do not. +// Scenarios locking the observable behavior of the foreground (non-detached) +// `compose up`. Most e2e coverage of the start phase runs detached. -// pre_start output is not asserted: a foreground up does not stream it into the -// session today, only post_start's. What is locked for pre_start is its effect. func TestUpInteractiveHookLogs(t *testing.T) { - NewScenario(t, "a foreground up must run pre_start before the service starts and stream post_start output into its session"). - Step("up shows post_start's output, and the service reads what pre_start wrote before it started", + NewScenario(t, "a foreground up must run the hooks around the service and stream their output into its session"). + Step("up shows both hooks' output, and the service reads what pre_start wrote before it started", ComposeCmd("up", "--menu=false").Within(120*time.Second), - OutputContains("app-read:pre-start-file-content"), - OutputContains("post-start-hook-ran"), + StdoutContains("pre-start-hook-stdout"), + StdoutContains("app-read:pre-start-file-content"), + StdoutContains("post-start-hook-ran"), ServiceState("app", "exited")) } @@ -45,9 +41,8 @@ func TestUpInteractiveImmediateExit(t *testing.T) { Step("up aborts on the instantly exiting container instead of waiting for the long-running one", ComposeCmd("up", "--menu=false", "--abort-on-container-exit").Within(60*time.Second), OutputContains("quick-1 exited with code 0"), - OutputContains("Aborting on container exit"), ServiceState("quick", "exited"), - Eventually(ServiceState("long", "exited"), 10*time.Second)) + ServiceState("long", "exited")) } func TestUpInteractiveRestartsExitedContainer(t *testing.T) { @@ -58,7 +53,7 @@ func TestUpInteractiveRestartsExitedContainer(t *testing.T) { Step("a foreground up runs the same container a second time", ComposeCmd("up", "--menu=false").Within(60*time.Second), NotRecreated("app"), - OutputContains("second-run"), + StdoutContains("second-run"), ServiceState("app", "exited")) } @@ -66,8 +61,9 @@ func TestUpInteractiveWaitsForHealthy(t *testing.T) { NewScenario(t, "a foreground up must not start a service before its service_healthy dependency is healthy"). Step("up starts web only once db reports healthy", ComposeCmd("up", "--menu=false", "--abort-on-container-exit").Within(120*time.Second), - OutputContains("web-saw-ready"), - OutputNotContains("web-too-early")) + StdoutContains("web-saw-ready"), + OutputNotContains("web-too-early"), + ServiceState("web", "exited")) } func TestUpInteractiveRecreateStreamsNewContainer(t *testing.T) { @@ -78,15 +74,18 @@ func TestUpInteractiveRecreateStreamsNewContainer(t *testing.T) { Step("a foreground up with another value recreates it and shows the new output", ComposeCmd("up", "--menu=false").WithEnv("VALUE=two").Within(60*time.Second), Recreated("app"), - OutputContains("value=two")) + ContainerEnv("app", "VALUE", "two"), + StdoutContains("value=two"), + OutputNotContains("value=one")) } func TestUpInteractiveDependencyTimeout(t *testing.T) { NewScenario(t, "a foreground up with --wait-timeout must fail once a dependency stays unhealthy, without starting the dependent"). - Step("up fails on the dependency timeout; web is created but never started", + Step("up fails on the dependency timeout; web exists but was never started", ComposeCmd("up", "--menu=false", "--wait-timeout", "3").MayFail().Within(60*time.Second), ExitCode(1), OutputContains("timeout waiting for dependencies"), + OutputNotContains("deadline exceeded"), OutputNotContains("web-started"), ServiceState("db", "running"), // web's container is created in the create phase, before the start @@ -95,12 +94,12 @@ func TestUpInteractiveDependencyTimeout(t *testing.T) { ServiceState("web", "created")) } -func TestUpDryRunCreatesNothing(t *testing.T) { - s := NewScenario(t, "a foreground up --dry-run must plan the create phase only, never the start phase") +func TestUpInteractiveDryRun(t *testing.T) { + // the progress lines name the container after the project + s := NewScenario(t, "a foreground up --dry-run must report the container creation, never start it and leave nothing behind") s.Step("up --dry-run reports the creation, no start, and leaves no container behind", ComposeCmd("up", "--menu=false", "--dry-run").Within(60*time.Second), OutputContains("Container "+s.Project()+"-app-1 Created"), OutputNotContains(s.Project()+"-app-1 Start"), - OutputContains("interactive run is not supported in dry-run mode"), ServiceNotCreated("app")) }