Skip to content

Serialize Infrastructure.Tests classes that build Aspire.TestTools - #21543

Merged
Eric Erhardt (eerhardt) merged 2 commits into
microsoft:mainfrom
davidfowl:davidfowl/testtools-build-race
Nov 5, 2026
Merged

Eric Erhardt (eerhardt) merged 2 commits into
microsoft:mainfrom
davidfowl:davidfowl/testtools-build-race

Conversation

@davidfowl

@davidfowl David Fowler (davidfowl) commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #19637

Root cause

Two Infrastructure.Tests classes build tools/Aspire.TestTools out of process, and they used to run in parallel:

Test class Build it triggers
GenerateTestSummaryToolTests Its class fixture runs dotnet build tools/GenerateTestSummary/GenerateTestSummary.csproj --restore, which has a ProjectReference to Aspire.TestTools.
DownloadFailingJobLogsToolTests Each test runs dotnet run tools/scripts/DownloadFailingJobLogs.cs, a file-based app with #:project ../Aspire.TestTools/Aspire.TestTools.csproj.

The two classes were in separate xunit collections, so their builds could overlap. Both builds write the same outputs under artifacts/bin/Aspire.TestTools/Debug/net10.0/. When Aspire.TestTools.deps.json needs regenerating, both MSBuild processes open it for exclusive write. The loser fails:

error MSB4018: The "GenerateDepsFile" task failed unexpectedly.
System.IO.IOException: The process cannot access the file '.../artifacts/bin/Aspire.TestTools/Debug/net10.0/Aspire.TestTools.deps.json' because it is being used by another process.

That is the failure in CI job 97605592624, which made DownloadFailingJobLogsToolTests' dotnet run exit 1.

The other tool fixtures (CreateFailingTestIssue, ExtractTestPartitions) don't build Aspire.TestTools as a project. CreateFailingTestIssue compiles the shared sources into itself. GenerateTestSummaryToolTests invokes the tool with --no-build.

Fix

Put both classes in one new xunit collection, AspireTestToolsBuild. Xunit runs classes in the same collection sequentially, so the fixture build and the dotnet run builds can never overlap. There are no retries or sleeps, and test behavior is unchanged.

Evidence

  1. Exact failure reproduced deterministically.

    • Setup: a helper process held Aspire.TestTools.deps.json open with FileShare.None, as a concurrent GenerateDepsFile writer does. The file was stale, so MSBuild had to regenerate it.
    • Result: the fixture's dotnet build tools/GenerateTestSummary/GenerateTestSummary.csproj --restore failed with the CI error: error MSB4018: The "GenerateDepsFile" task failed unexpectedly. System.IO.IOException: The process cannot access the file '.../Aspire.TestTools.deps.json' because it is being used by another process.
    • Once the holder released the file, the same build succeeded.
  2. Overlap measured in the real test run. I ran both classes with the real test runner (--filter-class *GenerateTestSummaryToolTests --filter-class *DownloadFailingJobLogsToolTests). Before each run, I forced a TestTools rebuild by touching a source file and deleting deps.json. A sampler checked ps every 100 ms for the fixture's dotnet build and a dotnet run DownloadFailingJobLogs.cs alive at the same time.

    Concurrent Aspire.TestTools builds observed Test failures
    Before (main) 5/5 runs 0/5
    After (this PR) 0/8 runs (5 + 3 after review update) 0/8

    The IOException itself is a narrow window (the deps.json write takes milliseconds). Locally I forced 40 concurrent build/run pairs and none hit it, which fits the rare CI rate. The overlap that makes it possible was present on every run before the fix and is gone after.

  3. No metadata-only guard. Following .github/instructions/infrastructure-tests.instructions.md, this PR adds no test that asserts collection attributes. Such a test would restate the source, not observe behavior. Behavioral coverage of the race isn't practical in a unit test: it needs two real MSBuild processes and a stale deps.json, and the conflicting write window is milliseconds. The real-runner evidence above is the verification. Both affected classes pass after the change (10/10 tests on every run).

🤖 Posted by AspireAgent (AI Agent)

GenerateTestSummaryFixture runs 'dotnet build' on GenerateTestSummary
(ProjectReference to Aspire.TestTools) while DownloadFailingJobLogsToolTests
runs 'dotnet run' on a file-based app with '#:project ../Aspire.TestTools'.
The two test classes were in different xunit collections and ran in
parallel, so both MSBuild processes could write
artifacts/bin/Aspire.TestTools/.../Aspire.TestTools.deps.json at the same
time and GenerateDepsFile failed with an IOException. Put both classes in
one collection and add a guard test.

Fixes microsoft#19637

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

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 21543

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 21543"

@github-actions github-actions Bot added the area-engineering-systems infrastructure helix infra engineering repo stuff label Oct 31, 2026
@aspire-repo-bot
aspire-repo-bot Bot requested a balanced review from Copilot October 31, 2026 07:27

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.

🟡 Changes recommended

The new theory checks hard-coded collection metadata rather than whether builds overlap, violating the infrastructure tests’ behavioral-testing requirement.

1 open issue
What changed in this PR

Addresses #19637 by serializing the Infrastructure.Tests classes that build Aspire.TestTools, preventing concurrent writes to shared build outputs.

Changes:

  • Places both tool test classes in the same xUnit collection.
  • Documents the shared-output contention behind the intermittent failure.
  • Adds a reflection-based check of collection membership.
File Description
tests/​Infrastructure.Tests/​GenerateTestSummary/​GenerateTestSummaryToolTests.cs Joins the shared build collection.
tests/​Infrastructure.Tests/​DownloadFailingJobLogs/​DownloadFailingJobLogsToolTests.cs Joins the same collection to prevent overlapping builds.
tests/​Infrastructure.Tests/​AspireTestToolsBuildCollection.cs Defines the collection, explains the contention, and checks membership.

Review effort: Balanced

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

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.

🟢 Approval recommended

No active review issues are recorded for the change to serialize infrastructure tests that share Aspire.TestTools build outputs.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@davidfowl
David Fowler (davidfowl) marked this pull request as ready for review October 31, 2026 08:21
@eerhardt
Eric Erhardt (eerhardt) merged commit a7cf861 into microsoft:main Nov 5, 2026
48 checks passed
@microsoft-github-policy-service microsoft-github-policy-service Bot added this to the 17.0 milestone Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-engineering-systems infrastructure helix infra engineering repo stuff

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[CI Failure] Flaky: DownloadFailingJobLogsToolTests.ReportsMissingArtifactWithoutFailing assertion mismatch

3 participants