Skip to content

[Diagnostic] Strengthen #52029 repro and unskip IncludeTestAssembly=true cases#54578

Open
Evangelink wants to merge 2 commits into
mainfrom
dev/amauryleve/52029-prove-or-unskip
Open

[Diagnostic] Strengthen #52029 repro and unskip IncludeTestAssembly=true cases#54578
Evangelink wants to merge 2 commits into
mainfrom
dev/amauryleve/52029-prove-or-unskip

Conversation

@Evangelink
Copy link
Copy Markdown
Member

Follow-up to #54514 / #52029 — diagnostic to prove-or-disprove the bug.

Why

Reviewer @Youssef1313 pointed out on #54514 (comment) that it's unclear whether issue #52029 (BadImageFormatException: Index not found on Linux/macOS when IncludeTestAssembly=true) actually still reproduces. PR #54514 just parameterized the existing test on IncludeTestAssembly and skipped the =true cases with a link to the issue, but the original test asset is so minimal that the bug would never trigger anyway — so a green CI on those skips would have proved nothing.

Concretely, StaticManagedInstrumenter.InstrumentAsync short-circuits when both <IncludeDirectories> and <AdditionalFiles> are empty in the coverage config. The merged coverage.config only sets <IncludeTestAssembly>, so the instrumenter never actually rewrote any IL and the historical crash path was never exercised.

What this PR does (diagnostic)

  1. Adds a Lib class library with async methods (Task.Yield/Task.Delay loops, IAsyncEnumerable) so there are real async state machines for the static instrumenter to rewrite.
  2. Adds async test methods that exercise Lib so the test assembly itself has async patterns (matching the original crash stack which was during async cleanup).
  3. Writes a richer coverage.config that explicitly enables static managed instrumentation and populates <ModulePaths><IncludeDirectories> pointing at the test project bin folder, defeating the early-return.
  4. Unskips both IncludeTestAssembly=true cases.
  5. Updates the test summary assertion to reflect the new total (6 tests / 5 passed / 1 failed).

What to do with the CI results

Marking as Draft until CI tells us which branch we're on.

cc @Youssef1313 @Evangelink

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

PR #54514 parameterized the code-coverage test on IncludeTestAssembly and skipped the =true cases pointing at issue #52029 (BadImageFormatException on Linux/macOS). However, the minimal test asset never actually triggered the static managed instrumenter (StaticManagedInstrumenter.InstrumentAsync short-circuits when both <IncludeDirectories> and <AdditionalFiles> are empty), so the skip was not meaningful: CI would have been green even with the bug present.

This change is a diagnostic to prove-or-disprove the bug:

- Adds a Lib class library with async methods (Task.Yield/Delay loops, IAsyncEnumerable).

- Adds async test methods exercising those methods so the test assembly has real async state machines.

- Writes a richer coverage.config that explicitly enables static managed instrumentation and populates <ModulePaths><IncludeDirectories> so the instrumenter actually rewrites the test assembly and Lib.

- Unskips both IncludeTestAssembly=true cases.

If CI fails on Linux/macOS, the bug is confirmed and we will re-skip with the stronger repro committed; if green across the board, the unskip stands.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Evangelink
Copy link
Copy Markdown
Member Author

CI results: the bug does NOT reproduce even with the strengthened repro

I dug into the macOS x64 + Windows x64 Helix runs of build 1448073. The previously-skipped includeTestAssembly: True cases now run, and they pass everywhere:

Windows x64 (Helix job 4f509454-26b8-4242-bd01-92f88ab91d77, 449/449 work items passed) — dotnet.Tests.dll.20 shows:

Passed ...RunTestProjectWithCodeCoverage_ShouldReturnExitCodeAtLeastOneTestFailed(configuration: "Release", includeTestAssembly: False) [12 s]
Passed ...RunTestProjectWithCodeCoverage_ShouldReturnExitCodeAtLeastOneTestFailed(configuration: "Debug",   includeTestAssembly: False) [6 s]
Passed ...RunTestProjectWithCodeCoverage_ShouldReturnExitCodeAtLeastOneTestFailed(configuration: "Release", includeTestAssembly: True)  [6 s]
Passed ...RunTestProjectWithCodeCoverage_ShouldReturnExitCodeAtLeastOneTestFailed(configuration: "Debug",   includeTestAssembly: True)  [6 s]

macOS x64 (Helix job 50bb077d-212b-4c49-8440-8761c2d7918e, our partition dotnet.Tests.dll.20 passed in 6m 2s) — same 4 outcomes, all Passed. No BadImageFormatException, no crash, no exit 134; the inner dotnet test exits with 2 (TestingPlatform "tests failed") as expected.

What the inner test produced (snippet from includeTestAssembly: True on macOS x64):

failed TestMethod2 (18ms)
  Assertion failed. Expected values to be equal.
  expected: 1
  actual:   2
  Assert.AreEqual(1, 2)
    at TestProject.Test1.TestMethod2()
Exit code: 2

The strengthened test asset (Lib with AsyncCalculatorTask.Yield/Task.Delay loops + IAsyncEnumerable) and the coverage.config explicitly enabling EnableStaticManagedInstrumentation + recursive ModulePaths/IncludeDirectories pointing at the test output did not trigger #52029. Static instrumentation runs cleanly against the test assembly + Lib.dll.

About the other red checks (all unrelated)

  • Linux x64 build: MSB3894/MSB3026 "process cannot access file" on Microsoft.DotNet.GenAPI.Tool resource DLLs during parallel publish — classic CI file-lock flake. Tests never ran.
  • macOS x64 TestBuild + AoT: AzDO job timed out at 2h30m because 4 unrelated Helix work items hung: NetAnalyzers.UnitTests.dll.16, StaticWebAssets.Tests.dll.10, StaticWebAssets.Tests.dll.15, dotnet-watch.Tests.dll.1. Our test partition (dotnet.Tests.dll.20) completed in 6m 2s with all assertions passing.

Recommendation

Bug doesn't reproduce. I'm marking this PR Ready for Review — the unskip stands and #52029 can stay closed. Happy to drop the diagnostic test-asset enrichment (extra Lib + async tests + stronger coverage.config) before merge if you'd rather keep the asset minimal; let me know.

cc @Youssef1313

@Evangelink Evangelink marked this pull request as ready for review June 3, 2026 20:56
@Evangelink Evangelink requested a review from a team as a code owner June 3, 2026 20:56
Copilot AI review requested due to automatic review settings June 3, 2026 20:56
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Strengthens the existing dotnet test --coverage regression/diagnostic for #52029 by ensuring static managed instrumentation actually performs IL rewriting (via non-empty <ModulePaths><IncludeDirectories>) and by making the test asset contain real async state machines in both a referenced library and the test assembly itself. This makes the unskipped IncludeTestAssembly=true rows meaningful on Linux/macOS instead of being effectively no-ops.

Changes:

  • Expanded the TestProjectSolutionWithCodeCoverage test asset with a new Lib project containing async patterns (Task.Yield, Task.Delay, IAsyncEnumerable) and added async MSTest methods that exercise it.
  • Updated the coverage settings written by the CLI test to enable static managed instrumentation and to include the test project bin/<Configuration> directory recursively, preventing the instrumenter’s early short-circuit.
  • Unskipped the IncludeTestAssembly=true cases and updated the expected test run summary counts (now 6 total tests, 1 failing).
Show a summary per file
File Description
test/TestAssets/TestProjects/TestProjectSolutionWithCodeCoverage/TestProjectSolutionWithCodeCoverage.sln Adds the new Lib project to the solution and configuration mappings.
test/TestAssets/TestProjects/TestProjectSolutionWithCodeCoverage/TestProject/TestProject.csproj References Lib so coverage instrumentation and test execution traverse both assemblies.
test/TestAssets/TestProjects/TestProjectSolutionWithCodeCoverage/TestProject/Test1.cs Adds async tests and an await foreach path to produce realistic async state machines in the test assembly.
test/TestAssets/TestProjects/TestProjectSolutionWithCodeCoverage/Lib/Lib.csproj Introduces a class library project in the test asset (TFM via $(CurrentTargetFramework)).
test/TestAssets/TestProjects/TestProjectSolutionWithCodeCoverage/Lib/AsyncCalculator.cs Implements async APIs designed to force IL rewriting by static instrumentation.
test/dotnet.Tests/CommandTests/Test/GivenDotnetTestBuildsAndRunsTestsWithArtifacts.cs Unskips IncludeTestAssembly=true, writes richer coverage.config, and updates assertions for the new test count.

Copilot's findings

  • Files reviewed: 6/6 changed files
  • Comments generated: 0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants