Repository navigation
Conversation
A command that is a no-op (e.g. start on an already running service) must stay silent on stdout. The legacy tests asserted it by hand; expose it as a generic observable of the scenario vocabulary. Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
docker-agent
left a comment
There was a problem hiding this comment.
Assessment: 🟢 APPROVE
This PR cleanly migrates six icmd-style e2e tests to the Scenario DSL and adds two small, generic DSL extensions. The migration is straightforward and preserves test intent; the new StdoutEmpty() check and CompleteCmd action are well-structured. Two minor issues were found in the new code.
| return Check{ | ||
| name: "stdout is empty", | ||
| fn: func(ctx *CheckContext) error { | ||
| if out := ctx.result.Stdout(); out != "" { |
There was a problem hiding this comment.
[medium] StdoutEmpty() compares raw Stdout() to "" without trimming — may produce false failures
ctx.result.Stdout() returns the raw captured bytes from the process's stdout buffer; it does not strip trailing whitespace. Many CLI tools (including docker compose start) write a bare trailing newline to stdout even when they produce no meaningful output, so Stdout() returns "\n" rather than "". As written, StdoutEmpty() would report a spurious failure for TestStartAlreadyRunning's "start leaves the running container alone and writes nothing to stdout" step.
The rest of checks.go uses strings.TrimSpace(res.Stdout()) in every empty-sensitive comparison (e.g. lines 382, 589, 628, 730). StdoutContains does not need trimming because strings.Contains tolerates a trailing newline, but StdoutEmpty is specifically asserting complete absence of content, where a trailing newline causes a false failure.
| if out := ctx.result.Stdout(); out != "" { | |
| if out := strings.TrimSpace(ctx.result.Stdout()); out != "" { |
| Confidence | Score |
|---|---|
| 🟡 moderate | 57/100 |
There was a problem hiding this comment.
Not changed on purpose. StdoutEmpty is meant to pin the "no-op stays silent on stdout" contract exactly, so trimming would let a stray newline slip through. The premise also does not hold here: docker compose start on an already running service writes nothing to stdout (progress goes to stderr), and TestStartAlreadyRunning passes in the plugin and standalone CI jobs with the strict comparison. The TrimSpace calls elsewhere in checks.go compare values extracted from a command output, which is a different use.
There was a problem hiding this comment.
Thank you for the clarification — I understand now. The distinction is important: StdoutEmpty is enforcing an exact contract that stdout is completely silent (zero bytes), not "appears empty after normalizing whitespace." The fact that TestStartAlreadyRunning passes in CI confirms that docker compose start on an already-running service does indeed write nothing (not even a newline) to stdout, so the strict comparison is both intentional and correct.
I misread the pattern. The TrimSpace calls I cited are for extracting values from output (where trailing whitespace is incidental formatting), not for asserting complete silence. This is a false positive on my part.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Shell completion is reached through a different entry point in plugin mode (docker __complete compose) and in standalone mode (docker-compose __complete). Expose it as an action so that scenarios can cover completion in both modes, as the legacy tests did by hand. Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
Express the intent (events refuses a service outside the project) with the Scenario DSL; the project now lives in testdata/TestEventsUnknownService. The shared fixtures/simple-composefile is still used by another test. Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
State what the test locks (a missing env_file only breaks the commands that need the service referencing it) as a sequence of steps, and observe the containers (running, scaled, not created) rather than only exit codes. Completion is covered in both plugin and standalone modes through CompleteCmd. The fixtures/env_file directory had no other user and is removed. Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
TestStartAlreadyRunning, TestStopAlreadyStopped, TestStartStopMultipleServices and TestStartStopMultipleFiles now state their intent and observe container state (running/exited, not recreated) instead of matching progress lines. The shared fixtures/start-stop had no other user and is removed. Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
83af88b to
b848567
Compare
docker-agent
left a comment
There was a problem hiding this comment.
What this PR does, in one sentence
Migrates the legacy icmd-style e2e tests of
events_test.go,env_file_test.goandstart_stop_test.goto the declarativeScenarioDSL, without changing what they verify.Context
About 200 e2e tests already use
NewScenario, but 16 files inpkg/e2estill drive the CLI throughicmdhelpers directly. In those files the intent is buried in setup and cleanup code, and many assertions are brittle: they match progress lines such asContainer e2e-start-stop-svc-multiple-files-simple-1 Stopped(including the hard-coded project name) instead of observing what the user gets, namely the state of the containers. The two styles side by side make the suite harder to read and to review.Migrating everything at once would not be reviewable, so this is a first batch of the files that convert most mechanically. The others are left for follow-ups.
What the PR brings
Migrated (one commit per file):
events_test.go:TestEventsUnknownService.env_file_test.go:TestUnusedMissingEnvFile(a 12-command test becomes a readable sequence of steps).start_stop_test.go:TestStartAlreadyRunning,TestStopAlreadyStopped,TestStartStopMultipleServices,TestStartStopMultipleFiles.What the Scenario form gives:
(command -> expected observables).ServiceState,ServiceScale,NotRecreated,ServiceNotCreated) replace most output matching.OutputContains/StderrContainsremain only where the CLI's message is the observable (an error naming the unknown service or the missing env file).downcleanups and the hard-coded project names go away.testdata/<TestName>/;fixtures/env_fileandfixtures/start-stophad no other user and are removed.fixtures/simple-composefileis still used by another test and stays.Two small, generic additions to the DSL, each in its own commit:
StdoutEmpty()check (checks.go): a no-op command must stay silent on stdout.CompleteCmd(...)action (scenario.go): reaches shell completion in both plugin (docker __complete compose) and standalone (docker-compose __complete) forms, so completion stays covered in both modes as before.Guardrails:
TestStartStopMultipleServicesandTestStartStopMultipleFilesno longer match theContainer ... Stopped/Startedprogress lines; they assert the resulting container states (and, for multiple files, that the services of the other file keep running), which is what those messages stood for.TestUnusedMissingEnvFilealso asserts states (service running, scaled to 2,serviceBnever created) and a few outputs (pslistsserviceA,execprintshello) that were only exit-code checks before. Its fixture drops the unrelatedserviceC(andtest.env).TestStopAlreadyStoppedkeeps the quirk it already pinned (aStoppedline on stderr) with its TODO, and additionally checks the service stays stopped and is not recreated.TestStartAlreadyRunningadditionally checks the container is neither recreated nor stopped.TestWaitOnInfinity,TestComposeCancel,TestCascadeIgnoresOneOffContainer,TestLocalComposeLogsFollow).AI disclosure: prepared with GitHub Copilot CLI, orchestrated by Claude Code, on behalf of and under the direction of the submitter.