Skip to content

Fix flaky It_generates_satellite_assemblies by decoding captured test stdout as UTF-8#55428

Open
MichaelSimons wants to merge 2 commits into
mainfrom
michaelsimons-fix-satellite-assembly-stdout-encoding
Open

Fix flaky It_generates_satellite_assemblies by decoding captured test stdout as UTF-8#55428
MichaelSimons wants to merge 2 commits into
mainfrom
michaelsimons-fix-satellite-assembly-stdout-encoding

Conversation

@MichaelSimons

@MichaelSimons MichaelSimons commented Jul 23, 2026

Copy link
Copy Markdown
Member

Fixes #55408

Symptom

Microsoft.NET.Build.Tests.GivenThatWeWantToBuildAnAppWithLibrary.It_generates_satellite_assemblies intermittently fails when asserting the built app's localized stdout contains the French string "Bienvenue à .Net!". Two corruption shapes have been observed across agents:

...Bienvenue ├á .Net!...   (two chars: à decoded from UTF-8 bytes C3 A0 as an OEM code page)
...Bienvenue � .Net!...    (single char: à emitted as one OEM byte, then read as UTF-8 -> U+FFFD)

Exit code is 0 and every ASCII line is fine — only the non-ASCII character is mangled. As @baronfel noted on the issue, this is "some kind of encoding/stream error in the stdout - otherwise the expected content looks fine."

Root cause

This is a console/stdout encoding problem in the test path, and it has two sides that must agree, neither of which was pinned:

  1. Child emit side — The built TestApp prints its localized strings with plain Console.WriteLine. When its stdout is redirected (as the test harness does), .NET defaults Console.OutputEncoding to the host console's active code page. On Windows CI agents that is the OEM code page (e.g. CP437/CP850), which varies by machine and system locale. So the bytes the child emits for à are themselves non-deterministic: UTF-8 C3 A0 on one agent, a single OEM byte (e.g. 0xE0/0x85) on another.

  2. Parent capture side — The shared test helper (TestCommandMicrosoft.DotNet.Cli.Utils.Command) redirects and captures that stdout. With StandardOutputEncoding left null, Process decodes the stream using the parent test host's Console.OutputEncoding — again the ambient OEM code page.

Because both sides independently follow a machine-dependent code page, they disagree on some agents and the non-ASCII byte(s) get mis-decoded — producing ├á (UTF-8 bytes read as CP437) or (a lone OEM byte read as UTF-8). Whichever agent an intermittent run lands on determines pass/fail, which is exactly why it flakes.

Why the first attempt was insufficient

An earlier revision fixed only the capture side (default the harness to decode UTF-8). That correctly handles agents where the child emits UTF-8, but on an agent where the child emits a single OEM byte it turned ├á into — the test still failed. Fixing capture alone cannot work while the child's emit encoding is unpinned. The complete fix must make both sides deterministically UTF-8.

Fix

Two small, surgical changes that pin both sides to UTF-8:

  1. test/TestAssets/TestProjects/KitchenSink/TestApp/Program.cs — set Console.OutputEncoding = Encoding.UTF8; before writing, so the app emits UTF-8 bytes regardless of the host code page. This is BOM-free (Console strips the preamble) and a no-op on platforms that already default to UTF-8.

  2. test/Microsoft.NET.TestFramework/Commands/TestCommand.cs — default captured-stdout decoding to UTF-8 when a test hasn't explicitly chosen an encoding:

    command.StandardOutputEncoding(StandardOutputEncoding ?? Encoding.UTF8);

    This decodes captured stdout consistently, independent of the host console's code page, and also aligns with the dotnet CLI, which already emits UTF-8. Tests that opt in via WithStandardOutputEncoding(...) still win.

Why it's correct cross-platform

  • Windows: The child now always emits UTF-8 and the harness always decodes UTF-8, removing the dependency on the ambient OEM code page that caused the flake.
  • Linux/macOS: stdout is already UTF-8 on both sides, so both changes are effectively no-ops and preserve existing behavior.
  • The changes are confined to test assets and the test framework; no product/runtime behavior changes. This follows existing precedent already in the repo (e.g. EntryPointFilePath_WithUnicodeCharacters uses WithStandardOutputEncoding(Encoding.UTF8)).

Local validation

Built the SDK (build.cmd -c Debug, 0 errors) and ran the actual test against the freshly built SDK:

  • With the fix: It_generates_satellite_assembliesPassed (1/1).
  • Negative control: temporarily changing the TestApp to emit single-byte Encoding.Latin1 (simulating a hostile OEM agent) reproduced the exact reported failure — Expected string "...Bienvenue � .Net!..." to contain "Bienvenue à .Net!" — then reverted. This ties the reproduction directly to the repo test and confirms the fix is what makes it pass.

Additionally reproduced the mechanism end-to-end with standalone built apps under a parent console pinned to CP437: an app using the ambient encoding + UTF-8 capture → contains-expected=False; an app forcing UTF-8 + UTF-8 capture → contains-expected=True.

…test

Fixes #55408

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 390f9fba-29f4-4d5a-9178-138258b2b895
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
2 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@MichaelSimons
MichaelSimons marked this pull request as ready for review July 23, 2026 13:16
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
2 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@MichaelSimons
MichaelSimons marked this pull request as draft July 23, 2026 13:17
@MichaelSimons
MichaelSimons marked this pull request as ready for review July 23, 2026 13:18
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
2 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses intermittent test failures in the SDK test infrastructure by making redirected stdout capture decode as UTF-8 by default, avoiding host-console code page–dependent mojibake (notably on Windows) when asserting on localized/non-ASCII output.

Changes:

  • Default TestCommand’s captured stdout decoding to UTF-8 when no explicit StandardOutputEncoding is provided.
  • Add an explanatory comment documenting the flakiness root cause and why UTF-8 is the appropriate default for redirected output.

…apture

The satellite-assembly test captures this app's localized stdout. With stdout redirected, Console defaults its output encoding to the host console code page, which varies across CI agents; on some agents 'a-grave' was emitted as a single OEM byte, producing a U+FFFD after UTF-8 capture. Forcing UTF-8 output makes the emitted bytes deterministic on every agent.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 390f9fba-29f4-4d5a-9178-138258b2b895
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Test flakiness for Microsoft.NET.Build.Tests.GivenThatWeWantToBuildAnAppWithLibrary.It_generates_satellite_assemblies

2 participants