Skip to content

Migrate test infrastructure from xUnit v2 to xUnit v3#52930

Open
MichaelSimons wants to merge 36 commits intodotnet:mainfrom
MichaelSimons:xunit-v3
Open

Migrate test infrastructure from xUnit v2 to xUnit v3#52930
MichaelSimons wants to merge 36 commits intodotnet:mainfrom
MichaelSimons:xunit-v3

Conversation

@MichaelSimons
Copy link
Copy Markdown
Member

@MichaelSimons MichaelSimons commented Feb 10, 2026

Related to #52914.

Switch all ~55 test projects from xUnit v2 (2.9.3) to xUnit v3 (3.2.2) using the arcade SDK's built-in XUnitV3.targets support. MTP enablement is deferred to a follow-up (#52914).

Key changes

  • Set TestRunnerName=XUnitV3 and OutputType=Exe in test/Directory.Build.targets
  • Switch package references: xunitxunit.v3, XUnitExtensionsXUnitV3Extensions, Verify.XunitVerify.XunitV3, TemplateEngine test packages to XunitV3 variants
  • Remove custom Program.cs entry point (xUnit v3 generates its own)
  • Add CallerFilePath/CallerLineNumber to custom Fact/Theory attributes (xUnit3003)
  • Add chmod +x for POSIX Helix work items (AppHost loses execute bit in zip)
  • Thread RuntimeIdentifier through test publish for cross-arch Helix queues
  • Fix Ctrl+C test crash: launch child processes in a new process group to isolate from xUnit v3's out-of-process test host

<_CurrentAdditionalProperties>%(SDKCustomXUnitProject.AdditionalProperties)</_CurrentAdditionalProperties>
</PropertyGroup>
<MSBuild Projects="$(_CurrentSDKCustomXUnitProject)" Targets="PublishWithOutput" Properties="CustomAfterMicrosoftCommonTargets=$(_SDKCustomXUnitPublishTargetsPath);TargetFramework=$(_CurrentPublishTargetFramework);BuildTestPackages=false;$(_CurrentAdditionalProperties)">
<!-- In CI, Arcade's centralized NuGet restore runs via NuGet.targets and does not
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can this be addressed in Arcade instead?

BTW, the test targets in Arcade SDK were created a long time ago, when the product test runners lacked necessary features. Can we now use dotnet test directly?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, I would like to do this in a follow-up PR.

Copilot AI added a commit that referenced this pull request Mar 23, 2026
…getRid) to test publish

When cross-compiling for arm64 Helix queues (x64 build agent, TargetArchitecture=arm64),
test projects were published without a RuntimeIdentifier. This caused the native AppHost
to be built for the build machine (x64), which under Rosetta would invoke the arm64 dotnet
from the test SDK payload, failing to load the x64 libhostpolicy.dylib.

Apply the fix from #52930: add RuntimeIdentifier=$(TargetRid) to the test project
publish and restore steps so the AppHost matches the arm64 Helix target architecture.

Co-authored-by: MichaelSimons <8290530+MichaelSimons@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/sdk/sessions/80a2fae2-c36c-46b1-9474-c8bb1e4f585c
MichaelSimons and others added 6 commits March 23, 2026 15:29
On macOS arm64, unsigned binaries like createdump cannot execute due to
Gatekeeper/SIP restrictions. This adds a Helix pre-command that finds
any createdump binary in the correlation payload and ad-hoc signs it
with codesign, allowing the .NET runtime to invoke createdump to collect
crash dumps when a test process crashes.

This mirrors the approach used in dotnet/runtime's sendtohelixhelp.proj.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The setup script (RunTestsOnHelix.sh) invokes numerous dotnet commands
that could crash. Signing createdump first ensures dump collection is
available for the entire Helix work item lifecycle.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Ad-hoc codesign test AppHost executables on macOS Helix queues so
  createdump can attach via task_for_pid for crash dump collection.
- Pass TargetRid to SDKCustomCreateXUnitWorkItemsWithTestExclusion
  for RID-based conditional logic.
- Fix find -exec missing semicolon terminator in HelixPostCommands
  by escaping as %3B so MSBuild does not consume it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
# Conflicts:
#	test/Microsoft.NET.TestFramework/Attributes/PlatformSpecificFact.cs
#	test/Microsoft.NET.TestFramework/Attributes/PlatformSpecificTheory.cs
#	test/UnitTests.proj
MichaelSimons and others added 3 commits March 25, 2026 21:39
The find -exec \; terminator was broken by a double-escaping issue:
MSBuild resolved %3B to a literal semicolon, but the Helix SDK then
split HelixPostCommands on that same semicolon, leaving find without
its required terminator.

Switch to find -print0 | xargs -0 which avoids the semicolon entirely.
Verified locally that it copies .dmp files when present and exits
cleanly when none are found.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The createdump binary from NuGet packages loses its code signature on
extraction. Re-sign it on macOS Helix with the same entitlements used
by dotnet/runtime (cs.debugger, allow-dyld-environment-variables,
disable-library-validation) so it can call task_for_pid for crash dumps.

Sign test AppHost executables with get-task-allow so createdump can
attach to them as the target process.

Two separate plist files are shipped via the Helix correlation payload:
- helix-createdump-entitlements.plist for createdump (matches runtime)
- helix-debug-entitlements.plist for test exes (get-task-allow)

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

@dotnet/dotnet-analyzers - After upgrading the NetAnalyzers to XUnit V3, I am seeing fairly consistent failures in the NetAnalyzers legs. Could you take a look at the crash dump. The following is a /ci-crash-dump analysis from Copilot. TIA.

NetAnalyzers SIGSEGV Crash on macOS ARM64

Summary

Microsoft.CodeAnalysis.NetAnalyzers.UnitTests.dll.161 crashes with exit code 139 (SIGSEGV) on the macOS ARM64 Helix leg. The test process segfaults ~60 seconds in with zero tests completed. A core dump and crash report were collected automatically.

What's happening

The crash occurs on Thread 23 inside the xUnit v3 test runner pipeline while executing DoNotUseDSATests.TestCreateObjectOfDSADerivedClassWithCngKeyParameterDiagnosticAsync. The stack shows the test entering the Roslyn CodeFixTest.RunImplAsync()SolutionState.WithProcessedMarkup()TestFileMarkupParser.Parse() path, then segfaulting in native code while creating ImmutableArray structures.

The main thread (Thread 0) is blocked in XunitAutoGeneratedEntryPoint.Main() — the new xUnit v3 auto-generated entry point, which replaced the xUnit v2 hosting model.

Helix work item test filter

The dotnet test command used in the Helix work item:

dotnet test Microsoft.CodeAnalysis.NetAnalyzers.UnitTests.dll \
  --filter 'Microsoft.NetCore.Analyzers.Security.UnitTests.DoNotUseDSATests.|Microsoft.NetCore.Analyzers.Security.UnitTests.DoNotUseInsecureCryptographicAlgorithmsTests.' \
  --blame-hang --blame-hang-timeout 60m \
  --logger trx --logger 'console;verbosity=detailed'

The console log shows "No test matches the given testcase filter" but this message appeared after the process crash — the long-running test warnings confirm the tests were discovered and actively running before the SIGSEGV. The "no match" message is likely a non-issue (class-level filter that didn't match additional tests beyond what was already running).

Contributing factors

  1. xUnit v3 process model change — the test now runs through XunitAutoGeneratedEntryPoint.Main (v3) instead of the v2 host. The full xUnit v3 runner pipeline (XunitTestRunner, XunitTestCaseRunner, XunitRunnerHelper, ExceptionAggregator, etc.) is on the stack.
  2. Assembly binding failureMicrosoft.Bcl.AsyncInterfaces, Version=9.0.0.8 fails to load at test platform startup. This may contribute to instability or indicate a packaging/dependency gap in the xUnit v3 migration.
  3. Two long-running tests at crash timeCA5353TripleDESCryptoServiceProviderInGetDeclarationAsync and TestCreateObjectOfDSADerivedClassWithCngKeyParameterDiagnosticAsync were both running for 41+ seconds before the crash.

Environment

  • Queue: osx.15.arm64.open
  • Machine: DNCHELIXMAC063
  • SDK: 11.0.100-ci
  • Test duration before crash: ~1 minute

Artifacts

Artifact URL
Core dump https://helixr1107v0xdeko0k025g8.blob.core.windows.net/dotnet-sdk-refs-pull-52930-merge-1698e2a9c9454761a8/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests.dll.161/1/coredump.65593?helixlogtype=result
Crash report JSON https://helixr1107v0xdeko0k025g8.blob.core.windows.net/dotnet-sdk-refs-pull-52930-merge-1698e2a9c9454761a8/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests.dll.161/1/coredump.65593.crashreport.json?helixlogtype=result
Console log https://helixr1107v0xdeko0k025g8.blob.core.windows.net/dotnet-sdk-refs-pull-52930-merge-1698e2a9c9454761a8/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests.dll.161/1/console.5b2c1c17.log?helixlogtype=result
Test host log https://helixr1107v0xdeko0k025g8.blob.core.windows.net/dotnet-sdk-refs-pull-52930-merge-1698e2a9c9454761a8/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests.dll.161/1/dotnetTestLog.host.26-03-27_10-12-52_21578_5.log?helixlogtype=result
Test diagnostic log https://helixr1107v0xdeko0k025g8.blob.core.windows.net/dotnet-sdk-refs-pull-52930-merge-1698e2a9c9454761a8/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests.dll.161/1/dotnetTestLog.log?helixlogtype=result
AzDO job https://dev.azure.com/dnceng-public/cbb18261-c48f-4abb-8651-8cdcb5474649/_build/results?buildId=1354916&view=logs&j=32d0f345-9bdd-572a-2118-4d6c9a54e829
Helix work item https://helix.dot.net/api/2019-06-17/jobs/1698e2a9-c945-4761-a8cc-2db99693e03d/workitems/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests.dll.161/console
PR #52930

Crashed thread stack (abbreviated)

DoNotUseDSATests.TestCreateObjectOfDSADerivedClassWithCngKeyParameterDiagnosticAsync()
  → AnalyzerTest`1.RunAsync()
    → CodeFixTest`1.RunImplAsync()
      → SolutionState.WithProcessedMarkup()
        → TestFileMarkupParser.Parse()
          → ImmutableArray.CreateBuilder()
            → [native frames → SIGSEGV]

Note: The core dump is a macOS Mach-O format file — it requires lldb on a macOS machine to debug (not dotnet-dump).

@MichaelSimons
Copy link
Copy Markdown
Member Author

Current status

  1. Several dotnet new integration tests are failing waiting for Fix Verify snapshot naming for xunit v3 compatibility templating#10052 to flow in. Currently blocked by [main] Source code updates from dotnet/dotnet #53613.
  2. There are intermittent failures in the NetAnalyzer tests as reported in Migrate test infrastructure from xUnit v2 to xUnit v3 #52930 (comment). These are happening too frequently to merge.

The .NET runtime already ad-hoc signs createdump with the required
entitlements at build time (src/coreclr/debug/createdump/CMakeLists.txt).
The SDK's Helix pre-command was re-applying identical entitlements from
a duplicate plist file, which is unnecessary.

- Remove find/codesign pre-command for createdump from UnitTests.proj
- Remove helix-createdump-entitlements.plist from Helix payload
- Delete build/helix-createdump-entitlements.plist

The helix-debug-entitlements.plist (get-task-allow for test executables)
is retained since test EXEs are built by the SDK, not the runtime.

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

@jeffhandley - I was given your name to help investigate a crash in the NetAnalyzer tests after upgrading to xunit v3 -- see #52930 (comment) for details. TIA.

@Youssef1313
Copy link
Copy Markdown
Member

Youssef1313 commented Mar 27, 2026

The only thing I can see that is really special with the specific class that's failing for NetAnalyzers tests is that it setup the tests with ReferenceAssemblies = ReferenceAssemblies.NetFramework.Net462.Default. I don't see how it can be a problem, but I thought it's worth noting.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants