From b8d86e5167aa432ebbf09ef8cbbeaa044f7c4611 Mon Sep 17 00:00:00 2001 From: "hiroto.toyoda" Date: Sun, 16 Aug 2026 21:51:40 +0900 Subject: [PATCH] fix(display): wire --dry-run flag into progress writers The tty, plain, and json EventProcessor constructors accepted no dry-run flag, so the DRY-RUN prefix was never rendered even when --dry-run was passed. Signed-off-by: hiroto.toyoda --- cmd/compose/compose.go | 20 +++++++------ cmd/compose/compose_progress_test.go | 4 +-- cmd/display/json.go | 5 ++-- cmd/display/json_test.go | 11 +++++++ cmd/display/plain.go | 5 ++-- cmd/display/plain_test.go | 43 ++++++++++++++++++++++++++++ cmd/display/tty_test.go | 19 ++++++++++++ 7 files changed, 93 insertions(+), 14 deletions(-) create mode 100644 cmd/display/plain_test.go diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 5b39a335939..bcbe42f65f2 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -506,7 +506,7 @@ func RootCommand(dockerCli command.Cli, backendOptions *BackendOptions) *cobra.C applyAnsiMode(dockerCli, ansi) detached, _ := cmd.Flags().GetBool("detach") - ep, err := selectEventProcessor(dockerCli, opts.Progress, ansi, detached) + ep, err := selectEventProcessor(dockerCli, opts.Progress, ansi, detached, dryRun) if err != nil { return err } @@ -712,39 +712,43 @@ func stdinfo(dockerCli command.Cli) io.Writer { // In auto mode we probe Err() (not Out()) because the renderer writes to stderr; // probing stdout would force plain mode whenever stdout is redirected (e.g. // `docker compose up | tee log`) while stderr is still a terminal. -func selectEventProcessor(dockerCli command.Cli, progress, ansi string, detached bool) (api.EventProcessor, error) { +func selectEventProcessor(dockerCli command.Cli, progress, ansi string, detached, dryRun bool) (api.EventProcessor, error) { + var termOpts []display.TermOption + if dryRun { + termOpts = append(termOpts, display.WithDryRun()) + } switch progress { case "", display.ModeAuto: switch { case ansi == "never": display.Mode = display.ModePlain - return display.Plain(dockerCli.Err()), nil + return display.Plain(dockerCli.Err(), dryRun), nil case dockerCli.Err().IsTerminal(): display.Mode = display.ModeTTY - return display.Full(dockerCli.Err(), stdinfo(dockerCli), detached), nil + return display.Full(dockerCli.Err(), stdinfo(dockerCli), detached, termOpts...), nil default: display.Mode = display.ModePlain - return display.Plain(dockerCli.Err()), nil + return display.Plain(dockerCli.Err(), dryRun), nil } case display.ModeTTY: if ansi == "never" { return nil, errors.New("can't use --progress tty while ANSI support is disabled") } display.Mode = display.ModeTTY - return display.Full(dockerCli.Err(), stdinfo(dockerCli), detached), nil + return display.Full(dockerCli.Err(), stdinfo(dockerCli), detached, termOpts...), nil case display.ModePlain: if ansi == "always" { return nil, errors.New("can't use --progress plain while ANSI support is forced") } display.Mode = display.ModePlain - return display.Plain(dockerCli.Err()), nil + return display.Plain(dockerCli.Err(), dryRun), nil case display.ModeQuiet, "none": display.Mode = display.ModeQuiet return display.Quiet(), nil case display.ModeJSON: display.Mode = display.ModeJSON logrus.SetFormatter(&logrus.JSONFormatter{}) - return display.JSON(dockerCli.Err()), nil + return display.JSON(dockerCli.Err(), dryRun), nil default: return nil, fmt.Errorf("unsupported --progress value %q", progress) } diff --git a/cmd/compose/compose_progress_test.go b/cmd/compose/compose_progress_test.go index 6f5ede4f4b5..18655b5ff9c 100644 --- a/cmd/compose/compose_progress_test.go +++ b/cmd/compose/compose_progress_test.go @@ -131,7 +131,7 @@ func TestSelectEventProcessor_AutoMode(t *testing.T) { saveGlobalState(t) cli := newMockCli(t, newStream(t, tc.outIsTTY), newStream(t, tc.errIsTTY)) - ep, err := selectEventProcessor(cli, "", tc.ansi, false) + ep, err := selectEventProcessor(cli, "", tc.ansi, false, false) assert.NilError(t, err) assert.Equal(t, fmt.Sprintf("%T", ep), tc.wantType) // the global must hold the mode actually rendered, never ModeAuto @@ -210,7 +210,7 @@ func TestSelectEventProcessor_ExplicitMode(t *testing.T) { // Explicit modes don't probe IsTerminal; pipes are fine for both. cli := newMockCli(t, newStream(t, false), newStream(t, false)) - ep, err := selectEventProcessor(cli, tc.progress, tc.ansi, false) + ep, err := selectEventProcessor(cli, tc.progress, tc.ansi, false, false) if tc.wantErrText != "" { assert.ErrorContains(t, err, tc.wantErrText) assert.Assert(t, ep == nil) diff --git a/cmd/display/json.go b/cmd/display/json.go index 463d9c7c14f..dd554be7e7b 100644 --- a/cmd/display/json.go +++ b/cmd/display/json.go @@ -25,9 +25,10 @@ import ( "github.com/docker/compose/v5/pkg/api" ) -func JSON(out io.Writer) api.EventProcessor { +func JSON(out io.Writer, dryRun bool) api.EventProcessor { return &jsonWriter{ - out: out, + out: out, + dryRun: dryRun, } } diff --git a/cmd/display/json_test.go b/cmd/display/json_test.go index 0f0dff23a61..9201486dea4 100644 --- a/cmd/display/json_test.go +++ b/cmd/display/json_test.go @@ -60,3 +60,14 @@ func TestJsonWriter_Event(t *testing.T) { } assert.DeepEqual(t, expected, actual) } + +func TestJSON_DryRunWiring(t *testing.T) { + var out bytes.Buffer + ep := JSON(&out, true) + ep.On(api.Resource{ID: "service1", Text: api.StatusCreating}) + + var actual jsonMessage + err := json.Unmarshal(out.Bytes(), &actual) + assert.NilError(t, err) + assert.Equal(t, actual.DryRun, true) +} diff --git a/cmd/display/plain.go b/cmd/display/plain.go index 16f2816c011..626e7d377b6 100644 --- a/cmd/display/plain.go +++ b/cmd/display/plain.go @@ -24,9 +24,10 @@ import ( "github.com/docker/compose/v5/pkg/api" ) -func Plain(out io.Writer) api.EventProcessor { +func Plain(out io.Writer, dryRun bool) api.EventProcessor { return &plainWriter{ - out: out, + out: out, + dryRun: dryRun, } } diff --git a/cmd/display/plain_test.go b/cmd/display/plain_test.go new file mode 100644 index 00000000000..668b25fe192 --- /dev/null +++ b/cmd/display/plain_test.go @@ -0,0 +1,43 @@ +/* + Copyright 2020 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 display + +import ( + "bytes" + "strings" + "testing" + + "gotest.tools/v3/assert" + + "github.com/docker/compose/v5/pkg/api" +) + +func TestPlain_DryRun(t *testing.T) { + var out bytes.Buffer + ep := Plain(&out, true) + ep.On(api.Resource{ID: "service1", Text: api.StatusCreating}) + + assert.Assert(t, strings.Contains(out.String(), DRYRUN_PREFIX)) +} + +func TestPlain_NotDryRun(t *testing.T) { + var out bytes.Buffer + ep := Plain(&out, false) + ep.On(api.Resource{ID: "service1", Text: api.StatusCreating}) + + assert.Assert(t, !strings.Contains(out.String(), DRYRUN_PREFIX)) +} diff --git a/cmd/display/tty_test.go b/cmd/display/tty_test.go index 2f3cf84c3df..0167a3dc8c3 100644 --- a/cmd/display/tty_test.go +++ b/cmd/display/tty_test.go @@ -373,3 +373,22 @@ func TestTerm_VisualSnapshot(t *testing.T) { assert.Equal(t, expected[i], strings.TrimRight(lines[i], " "), "line %d", i) } } + +// TestTerm_DryRunPrefixesRows covers the wiring from the --dry-run flag down +// to the rendered rows: WithDryRun must reach the layout, which is what puts +// the marker on every task row. +func TestTerm_DryRunPrefixesRows(t *testing.T) { + var buf bytes.Buffer + ep := Full(&buf, &buf, false, WithDryRun()) + w, ok := ep.(*termWriter) + assert.Assert(t, ok) + + w.size = func() (int, int) { return 120, 40 } + w.now = testClock().Now + w.operation = "pull" + feed(w, api.Resource{ID: "svc", Text: "Pulling", Status: api.Working}) + w.repaint() + + assert.Assert(t, strings.Contains(stripAnsi(buf.String()), DRYRUN_PREFIX), + "rendered frame does not carry the dry-run marker: %q", stripAnsi(buf.String())) +}