Skip to content

Nightly azure-dev - cli red: phantom unit-test failures from tool.go spinners writing to os.Stdout (regression from #9045) #9181

Description

@vhvb1989

Summary

The nightly scheduled azure-dev - cli pipeline (ADO azure-sdk/internal, definition 4643) is failing every night due to 7 deterministic phantom unit-test failures in the cmd and cmd/middleware packages. The tests actually pass, but a spinner writes to os.Stdout during the test, corrupting the go test -json event stream that gotestsum consumes, so they are reported as (unknown) → failure. This is the same class of failure as #8385.

Failing tests (identical every run, all platforms — Windows/Mac)

  • cmd TestToolInstallAction_resolveToolIds_AllFlag
  • cmd TestToolInstallAction_resolveToolIds_NoPromptWithoutTarget_Errors
  • cmd TestToolUpgradeAction_AllFlag_NoPrompt
  • cmd TestToolUpgradeAction_NoPrompt_WithoutTarget_Errors
  • cmd TestToolUninstallAction_resolveToolIds_AllFlag_NoPrompt
  • cmd TestToolUninstallAction_resolveToolIds_NoPromptWithoutTarget_Errors
  • cmd/middleware TestToolFirstRunMiddleware_OfferInstall_PromptError

Log signature (each test PASSES but is marked FAIL):

=== FAIL: cmd TestToolInstallAction_resolveToolIds_AllFlag (unknown)
| Detecting tool status...
--- PASS: TestToolInstallAction_resolveToolIds_AllFlag (0.00s)

When it started

Regression introduced by #9045 ("UX: implement new design of azd tool (install/upgrade/list/check)"), merged 2026-07-14 17:51 UTC (commit 197a3aec). The first nightly to include it (2026-07-14) and every nightly since have been red on these tests.

Why it happens (root cause)

In cli/azd/cmd/tool.go, five uxlib.NewSpinner(...) calls omit the Writer option, so the spinner defaults to os.Stdout (cli/azd/pkg/ux/spinner.go:43):

  • L206 "Detecting tools..."
  • L374 "Checking tool status..."
  • L907 detectAllTools ("Detecting tool status..." / "Detecting installed tools...") — hit by the resolveToolIds tests
  • L1920 "Checking for upgrades..."
  • L2118 "Checking %s..."

During the unit tests these spinners write raw ANSI to os.Stdout (fd 1), bypassing testing's output capture and corrupting the parallel go test -json stream that gotestsum parses. The result is phantom (unknown) failures on whichever cmd/cmd/middleware tests are emitting at that moment.

The correct pattern already exists in cli/azd/cmd/middleware/tool_first_run.go, where every spinner sets Writer: m.console.Handles().Stdout (test-safe buffer under test). The five tool.go spinners were not given a Writer.

This is explicitly called out in cli/azd/AGENTS.md ("Never write to os.Stdout in tests") and is meant to be guarded by the forbidigo linter — but that linter is scoped to *_test.go only, so a spinner created in non-test code (tool.go) and exercised by a test slips through.

Why the nightly retry fix (#9118) does not help

#9118 added gotestsum --rerun-fails to the integration run only:

  1. These 7 failures are unit tests, which have no retry — so they always red the run.
  2. They also defeat the integration retry: 7 phantom failures + a couple of real live-Azure flakes exceed --rerun-fails-max-failures=5, so gotestsum skips all reruns (ERROR number of test failures (9) exceeds maximum (5)), meaning even genuinely flaky integration tests never get retried.

Proposed fix

Route all five tool.go spinners through the console writer instead of os.Stdout (pass the input.Console into detectAllTools and set Writer: console.Handles().Stdout), matching cmd/middleware/tool_first_run.go. That clears the 7 phantom failures; with the count back under 5, #9118's retry can also do its job on real integration flakes.

Follow-up worth considering: extend forbidigo/lint coverage (or a Writer-required lint) to catch spinners created in non-test code that default to os.Stdout.

Evidence

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingengsysEngineering systemsregressionBroke something that worked beforetest automationTest infrastructure work

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions