Skip to content

Add xUnit v3 support#108

Merged
mattleibow merged 65 commits into
mainfrom
mattleibow/xunit-v3-support
May 26, 2026
Merged

Add xUnit v3 support#108
mattleibow merged 65 commits into
mainfrom
mattleibow/xunit-v3-support

Conversation

@mattleibow
Copy link
Copy Markdown
Owner

@mattleibow mattleibow commented May 8, 2026

Adds xUnit v3 as a supported test framework in DeviceRunners, alongside xUnit v2 and NUnit. Tests written with xUnit v3 can be discovered, executed, and reported through both the visual runner and CLI runner on all platforms — Android, iOS, macOS, Windows, and WASM.

Usage

Test library setup

xUnit v3 test assemblies must be class libraries (not executables) because the MAUI/Blazor app is the host process. Reference the lower-level xUnit v3 packages:

<ItemGroup>
  <PackageReference Include="xunit.v3.extensibility.core" />
  <PackageReference Include="xunit.v3.assert" />
</ItemGroup>

Do not reference xunit.v3 or xunit.v3.core in your device-targeted TFMs — those inject a Main entry point that conflicts with the app host. You can conditionally reference xunit.v3 on the host TFM (net10.0) to enable dotnet test.

Registration

// MAUI
builder.UseVisualTestRunner(conf => conf
    .AddXunit3()
    .AddTestAssemblies(typeof(MyXunit3Tests).Assembly)
    .AddConsoleResultChannel());

// Blazor WASM
builder.UseVisualTestRunner(conf => conf
    .AddXunit3()
    .AddTestAssemblies(typeof(MyXunit3Tests).Assembly)
    .AddConsoleResultChannel());

.AddXunit3() works the same on every platform — no flags, no useReflection, no per-platform configuration. It can be used alongside .AddXunit() and .AddNUnit().

UI testing

[UIFact] and [UITheory] attributes are available via the DeviceRunners.UITesting.Xunit3 package:

using DeviceRunners.UITesting.Xunit3;

public class MyUITests
{
    [UIFact]
    public void RunsOnUIThread() { /* ... */ }
}

Separate namespace from v2: The v3 [UIFact] and [UITheory] attributes are in the DeviceRunners.UITesting.Xunit3 namespace to avoid CS0433 ambiguity when both v2 and v3 runner packages are referenced in the same project.

How it works

Discovery and execution

The runner uses xUnit v3's in-process extensibility APIs directly (ITestFrameworkDiscoverer.Find() / ITestFrameworkExecutor.RunTestCases()). No separate test process is launched — everything runs inside the app.

Threading model

  • Discovery and execution run via Task.Run to escape the caller's SynchronizationContext (e.g. WinUI dispatcher), preventing deadlocks while preserving full parallelism.
  • Message reporting uses xUnit v3's async MessageBus pump on threaded platforms (desktop, Android, iOS) for maximum throughput. On WASM (where Thread cannot be spawned), synchronous reporting is enabled via OperatingSystem.IsBrowser().
  • Result callbacks are marshalled to the UI thread for ViewModel updates using SynchronizationContext.Post.
  • No WASM workarounds degrade threaded platforms — platform detection is always runtime (OperatingSystem.IsBrowser()) rather than compile-time or based on Assembly.Location emptiness.

Platform compatibility

On Android, iOS, and WASM, Assembly.Location returns an empty string because assemblies are loaded from streams rather than files. xUnit v3's XunitTestAssembly uses this path internally and crashes when it's empty.

DeviceRunners handles this transparently with InMemoryXunit3TestAssembly — a subclass that provides a logical path derived from the assembly name. The same code path works on all platforms; the runner auto-detects whether in-memory handling is needed at runtime.

UI thread dispatch

[UIFact]/[UITheory] dispatch the entire test lifecycle (class construction, IAsyncLifetime, test method invocation, and disposal) to the UI thread.

What's in this PR

New packages

Package What it does
DeviceRunners.VisualRunners.Xunit3 Test discovery, execution, message sinks, configuration
DeviceRunners.UITesting.Xunit3 [UIFact] / [UITheory] attributes and test case runners

Samples

  • DeviceTestingKitApp.MauiLibrary.Xunit3Tests — MAUI library tests (CounterView, VisualElement, Converter, SemanticAnnouncer, UIFact/UITheory)
  • DeviceTestingKitApp.BlazorLibrary.Xunit3Tests — Blazor library tests (CounterViewModel, Formatter, SemanticAnnouncer)
  • DeviceTests and BrowserTests updated to load v3 test assemblies

Tests

  • TestProject.Xunit3Tests — fixture assembly (facts, theories, skips, output)
  • DeviceRunners.VisualRunners.Xunit3.Tests — dedicated v3 unit test project with discoverer, runner, home view model, and threading tests
  • Tests follow the existing abstract-base pattern (TestDiscovererTests.Xunit3, TestRunnerTests.Xunit3, HomeViewModelTests.Xunit3)

Docs

  • docs/articles/xunit-v3-support.md — quick start, package architecture, platform compatibility table, in-memory assembly handling, desktop vs device/WASM differences

Other

  • Removed bUnit dependency (component rendering tests replaced with ViewModel/Formatter tests)
  • Updated Directory.Packages.props and DeviceRunners.slnx
  • Android Shell layout fix (WaitForPageLayout) for stable UI test navigation

Known limitations

  • Custom [TestFramework] — On platforms where Assembly.Location is empty (Android, iOS, WASM), the [TestFramework] assembly attribute is bypassed. Custom xUnit test frameworks only work on desktop. Tracked upstream: xunit/xunit#3096.
  • dotnet test on device TFMs — xUnit v3 device test projects cannot use dotnet test or VS Test Explorer for device TFMs. This is a fundamental xUnit v3 architecture constraint for device testing. However, dotnet test works on the host TFM (net10.0) for quick local iteration.
  • PrivateAssets="compile" — The DeviceRunners.VisualRunners.Xunit3 package uses PrivateAssets="compile" on its xunit.v3 dependencies to avoid type conflicts when co-hosted with the v2 runner. Consumers must bring their own xunit.v3 package references (which they always do since they're writing xunit tests).

mattleibow and others added 9 commits May 8, 2026 23:50
Add DeviceRunners.VisualRunners.Xunit3 project with in-process test
discovery and execution using xUnit v3's ExtensibilityPointFactory API.

New projects:
- src/DeviceRunners.VisualRunners.Xunit3 - Visual runner for xUnit v3
- test/TestProject.Xunit3Tests - xUnit v3 test data project
- sample/test/DeviceTestingKitApp.MauiLibrary.Xunit3Tests - Sample tests

Key architecture decisions:
- Uses xunit.v3.extensibility.core directly for in-process discovery/execution
- Test class libraries reference xunit.v3.extensibility.core + xunit.v3.assert
  (NOT xunit.v3.core which forces OutputType=Exe and injects Main)
- PrivateAssets=compile prevents v3 types from conflicting with v2 types
- Follows guidance from xunit/xunit#3112 discussion

Changes:
- Directory.Packages.props: Add xunit.v3.* package versions (3.2.2)
- DeviceRunners.slnx: Add new projects
- Sample MauiProgram.cs: Add .AddXunit3() alongside existing runners
- docs/articles/xunit-v3-support.md: Usage guide
- Test base classes: Add virtual TestAssembly property for framework flexibility

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Key fixes:
- Await the ValueTask from ITestFrameworkDiscoverer.Find() which runs
  discovery asynchronously on a ThreadPool thread
- Call TestContext.SetForInitialization() before ExtensibilityPointFactory
  as required by the xUnit v3 framework
- Use ExtensibilityPointFactory.GetTestFramework() directly instead of
  ConsoleRunnerInProcess for simpler in-process discovery/execution
- Make test base classes support overridable ExpectedTestCount

All 148 tests pass (including 12 new xUnit v3 tests).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Critical fixes:
- Await RunTestCases ValueTask (was discarded — exceptions swallowed,
  potential deadlock if execution fails before ITestAssemblyFinished)
- Dispose ITestFramework via await using (implements IAsyncDisposable,
  was leaking on every discovery/execution call)
- Wire Xunit3DiagnosticMessageSink to TestContext.SetForInitialization
  so xUnit framework errors are forwarded to IDiagnosticsManager

Thread safety:
- Add lock to Xunit3ExecutionMessageSink.OnMessage() to protect
  Dictionary mutations from concurrent test execution

Error handling:
- Handle IErrorMessage, ITestAssemblyCleanupFailure,
  ITestCollectionCleanupFailure, ITestClassCleanupFailure,
  ITestCaseCleanupFailure, ITestCleanupFailure in execution sink
- Flush remaining pending results on ITestAssemblyFinished for
  tests that emitted ITestStarting but never ITestFinished
- Improve Xunit3DiagnosticMessageSink to also handle
  IInternalDiagnosticMessage

Cleanup:
- Delete dead Xunit3DiscoveryMessageSink (not needed with
  callback-based ITestFrameworkDiscoverer.Find())
- Update docs to match actual implementation (ExtensibilityPointFactory
  API, not ConsoleRunnerInProcess; note config files not yet supported)
- Add dotnet-reportgenerator-globaltool for code coverage
- Add artifacts/coverage/ to .gitignore
- Fix output capture test to not pass vacuously

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update UITestCase and UITheoryTestCase to use the correct xUnit v3 API:
- Use base constructor instead of non-existent Initialize() method
- Pass resolved values from TestIntrospectionHelper.GetTestCaseDetails
  directly to base class constructors
- Await CreateTests() which returns ValueTask<IReadOnlyCollection>

Update UIFactDiscoverer and UITheoryDiscoverer to call
TestIntrospectionHelper.GetTestCaseDetails with the required
ITestFrameworkDiscoveryOptions parameter and pass decomposed details
to test case constructors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
New project:
- DeviceRunners.UITesting.Xunit3 — [UIFact] and [UITheory] attributes
  for xUnit v3 that dispatch test execution to the UI thread via
  ISelfExecutingXunitTestCase

Config loading:
- Xunit3TestDiscoverer now loads xunit.runner.json via
  ConfigReader_Json.LoadFromJson() using FileSystemUtils stream
- TestAssemblyConfiguration is passed through to both discovery and
  execution options
- Xunit3TestAssemblyInfo now holds TestAssemblyConfiguration

Updates:
- DeviceRunners.slnx — added UITesting.Xunit3
- Sample DeviceTests.csproj — added UITesting.Xunit3 reference
- Docs — updated limitations, added UI testing section

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The sample DeviceTests project references both xunit v2 and v3 test
libraries. Fix type conflicts by:
- Remove UITesting.Xunit3 reference from device tests (users choose
  one or the other based on their xunit version)
- Add PrivateAssets=compile to sample Xunit3Tests project's v3 packages
  to prevent v3 types from flowing transitively into the device tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Enable PreEnumerateTheories in both discoverer and runner so each
theory data row becomes its own Xunit3TestCaseInfo (matching xUnit v2).

Without this, all theory rows share one test case ID and the last
row's result overwrites earlier ones — silently hiding failures
(e.g. a theory where row 1 fails but row 3 passes reports Passed).

With pre-enumeration:
- DataTest(1), DataTest(2), DataTest(3) each get a unique test case
- ExpectedTestCount goes back to 8 (matching v2 parity)
- Each row's pass/fail is tracked independently

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
UIFactDiscoverer now derives from FactDiscoverer (inherits parameter
validation, generic method checks, and trait handling).

UITheoryDiscoverer now derives from TheoryDiscoverer (inherits theory
data enumeration, serialization checks, no-data error handling, skip
semantics, and trait propagation).

This matches the v2 pattern where UIFactDiscoverer : FactDiscoverer
and UITheoryDiscoverer : TheoryDiscoverer.

Added TraitsHelper to replicate the internal ToReadWrite() extension
for converting read-only trait dictionaries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mattleibow and others added 17 commits May 13, 2026 01:08
# Conflicts:
#	.gitignore
#	DeviceRunners.slnx
#	test/DeviceRunners.VisualRunners.Tests/Testing/TestDiscovererTests.cs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
bUnit uses AngleSharp which has TFM compatibility issues (net8.0 vs net10.0).
The bUnit tests (CounterComponentClickTests, LoginComponentTests) use a
fake Razor renderer, not real component rendering. Remove them and the bUnit
package reference. The remaining tests use HtmlRenderer or pure unit testing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use logical assembly name (assembly.GetName().Name + ".dll") instead of
FileSystemUtils.GetAssemblyFileName() which returns empty string on WASM
(Assembly.Location is empty in WebAssembly). This matches the approach
used by the v2 XunitReflectionTestDiscoverer.

The xunit v3 API (ExtensibilityPointFactory.GetTestFramework) is already
fully reflection-based and works on WASM without special handling. The
only issue was our wrapper code using file-path-based assembly matching.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add DeviceTestingKitApp.BlazorLibrary.Xunit3Tests with unit tests for
CounterViewModel, CounterValueFormatter, and BlazorSemanticAnnouncer
using xunit v3 (mirrors the existing v2 XunitTests).

Wire up AddXunit3() in BrowserTests Program.cs alongside AddXunit().
Unlike v2 which needs useReflection: true for WASM, v3 uses
ExtensibilityPointFactory which is already reflection-based.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
xunit v3's TestAssemblyRunner.OnTestAssemblyStarting calls
Path.GetFileNameWithoutExtension(TestAssembly.AssemblyPath) which
crashes on WASM because Assembly.Location returns empty string.

Add WasmXunit3TestAssembly that subclasses XunitTestAssembly and
re-implements IXunitTestAssembly to return a logical assembly path
(assembly.GetName().Name + ".dll") when Assembly.Location is empty.

Add WasmXunit3TestFramework that creates WASM-safe assembly objects
for both discovery and execution.

Update Xunit3TestDiscoverer and Xunit3TestRunner to use the WASM-safe
framework automatically when Assembly.Location is empty.

WASM BrowserTests results: 58 passed, 5 intentional failures, 3 skipped
(previously 24 non-intentional failures from AssemblyName guard).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Create DeviceTestingKitApp.BlazorLibrary.NUnitTests project with sample
  NUnit tests (UnitTests, CounterViewModelTests, CounterValueFormatterTests)
- Wire up NUnit in BrowserTests via AddNUnit() alongside existing xunit
- Add VisualRunners.NUnit project reference to BrowserTests
- Add new project to DeviceRunners.slnx

NUnit's DefaultTestAssemblyBuilder already uses reflection-based discovery
(takes Assembly object directly), so no special WASM-specific discoverer is
needed — unlike xunit v2 which required XunitReflectionTestDiscoverer.
The NUnitTestAssemblyInfoBuilder also short-circuits the runner's Load to
avoid filesystem access.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Root causes of NUnit producing 0 tests on WASM:

1. Thread creation: NUnit's SimpleWorkItemDispatcher calls new Thread().Start()
   which throws PlatformNotSupportedException on single-threaded WASM.
   Fix: Set RunOnMainThread=true to use MainThreadWorkItemDispatcher which
   executes inline without spawning threads.

2. EventPump thread: NUnit creates an EventPump thread for async event delivery.
   Fix: Set SynchronousEvents=true to bypass the EventPump.

3. Assembly path: FileSystemUtils.GetAssemblyFileName used Assembly.Location
   which returns empty string on WASM. Fix: Fall back to AssemblyName + .dll.

4. Silent failures: Added diagnostic messages when NUnit marks assemblies as
   not runnable or discovers 0 tests, making debugging much easier.

5. WorkDirectory: NUnit calls Directory.GetCurrentDirectory() internally.
   Fix: Provide WorkDirectory=. in configuration to short-circuit this.

The RunOnMainThread and SynchronousEvents settings are safe for all platforms
since the NUnit runner is already called from a background thread via
AsyncUtils.RunAsync on MAUI — 'main thread' just means 'calling thread'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mattleibow mattleibow changed the title Add xUnit v3 visual runner support feat: Add xUnit v3 support with visual runner, UITesting, WASM, and samples May 16, 2026
mattleibow and others added 2 commits May 16, 2026 12:29
Add comprehensive documentation for how xUnit v3 works on WASM/Blazor:
- New 'WASM / Blazor Browser Support' section in xunit-v3-support.md
  explaining the automatic WASM detection, WasmXunit3TestAssembly,
  WasmXunit3TestFramework, and the interface dispatch remapping pattern
- Desktop vs WASM comparison table (assembly location, threading, result
  output, configuration, [TestFramework] attribute support)
- Comparison with xUnit v2 WASM approach (reflection-based vs targeted
  assembly path workaround)
- Updated technical-architecture-overview.md with xunit v3 framework
  entries, WASM architecture details, and UITesting.Xunit3
- Updated visual-runner-in-the-ide.md Blazor example to show .AddXunit3()

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ndroid/iOS too

The empty Assembly.Location problem is not WASM-specific. On Android,
assemblies are loaded from APK streams; on iOS, from AOT bundles. Both
return empty Assembly.Location just like WASM.

Renames:
- WasmXunit3TestAssembly → InMemoryXunit3TestAssembly
- WasmXunit3TestFramework → InMemoryXunit3TestFramework

Updated docs to explain the cross-platform Assembly.Location behavior
with a platform comparison table showing which platforms have empty
locations and why. Also documents how xunit v2 handled this differently
per platform (dummy files on Android, reflection discoverer on WASM)
vs xunit v3's unified InMemory approach.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mattleibow and others added 8 commits May 20, 2026 02:24
The Find callback now returns false when cancellation is requested,
allowing faster abort during discovery of large assemblies.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add 46 new tests covering the xUnit v3 message sink, runner, and
discoverer that would have caught bugs found during development:

Xunit3ExecutionMessageSinkTests (31 tests):
- Theory aggregation: worst-status-wins (Failed > Passed > Skipped > NotRun)
- Duration and output accumulation across theory rows
- Multiple failure error message/stack trace concatenation
- ITestCaseFinished flush behavior
- Orphan result flush on ITestAssemblyFinished
- All cleanup failure types routed to diagnostics
- Output + error preserved together on failing tests
- Skip reason first-wins semantics
- Cancellation token stops message pump
- Unknown test case IDs handled gracefully
- Thread-safe concurrent message delivery
- ResultReported event firing and overwrite behavior
- Result channel integration and null safety

Xunit3RunnerAdvancedTests (8 tests):
- Re-run same tests produces fresh results
- ResultReported event fires for each test case
- Selective execution only runs specified tests
- Empty test case list doesn't throw
- Non-Xunit3 test cases are filtered out
- Cancellation stops execution cleanly
- Failed test with output captures both

Xunit3DiscovererAdvancedTests (7 tests):
- Test cases have correct metadata
- Null results before execution
- Cancellation during discovery
- Expected test count matches
- Rediscovery returns consistent results
- Assembly filename populated
- Configuration has device defaults

Infrastructure changes:
- Add InternalsVisibleTo for test project on Xunit3 library
- Add xunit.v3.common and xunit.v3.runner.common to test project
- Suppress xUnit1051 analyzer (v3 rule, project uses v2 runner)

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

- Remove ParallelizeTestCollections and MaxParallelThreads overrides from
  all discoverers (v2 and v3) — parallelization is the test author's choice
- Remove PrivateAssets="compile" from Xunit3 library — types flow naturally
- Create DeviceRunners.VisualRunners.Xunit3.Tests project using xunit v3 as
  the test framework (no v2/v3 conflict, proper TestContext.Current usage)
- Move all xunit3 test files to the new project
- Add CancellationToken parameter to HomeViewModel.StartAssemblyScanAsync
- Clean up v2 test project (remove xunit.v3.* packages and NoWarn hack)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix UniqueID collision in InMemoryXunit3TestAssembly by passing explicit
  uniqueID derived from logicalAssemblyPath to base XunitTestAssembly ctor
  (previously all in-memory assemblies shared the same UniqueID)
- Fix UITestCase/UITheoryTestCase to run CreateTests() through the
  ExceptionAggregator, properly handling data enumeration failures as
  test failures/skips instead of unhandled exceptions
- Fix runner test silently passing via early return — now asserts NotNull
- Fix cancellation test to assert discovery returns fewer tests
- Fix 'does not throw' tests to verify no results were recorded and
  cancellation leaves tests unrun
- Fix LoadContent/WaitForLayout in sample UI tests to fail on timeout
  instead of hanging indefinitely or returning silently

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

The runner library (DeviceRunners.VisualRunners.Xunit3) is designed to be
co-hosted with the v2 runner in apps that run both v2 and v3 tests.
Without PrivateAssets="compile", the xunit v3 attribute types
(FactAttribute, TheoryAttribute, etc.) flow transitively and conflict
with xunit v2 types in the same namespace, causing CS0433 errors.

Consuming test libraries that need xunit v3 types reference
xunit.v3.extensibility.core directly and are unaffected.

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

The test app's Console.WriteLine diagnostic messages (e.g. 'Framework error:
System.Runtime.InteropServices.COMException') match MSBuild's canonical error
format when flowing through the Exec task. This causes MSBuild to fail with
exit code 1 even though all tests pass (results arrive via TCP).

Redirect stdout/stderr and consume them asynchronously to prevent contamination
of MSBuild's output parsing.

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

xunit v3's executor was being called on the UI thread, causing it to capture
WinUI's DispatcherQueueSynchronizationContext. When xunit's internal async
infrastructure then posts callbacks from thread pool threads through the
captured WinUI context, it triggers COMExceptions (RPC_E_WRONG_THREAD).

Run the executor on a thread pool thread where SynchronizationContext.Current
is null. UIFact/UITheory tests still dispatch to the UI thread correctly
via UIThreadCoordinator.DispatchAsync.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Wrap xunit v3 discovery in Task.Run to avoid SyncContext capture
- Marshal DiagnosticsViewModel.Messages.Add to UI thread via SynchronizationContext
- Handle OperationCanceledException in RunTestsSafe without logging as error

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

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

Copilot reviewed 86 out of 88 changed files in this pull request and generated 3 comments.

Comment thread src/DeviceRunners.VisualRunners.Xunit3/Xunit3TestRunner.cs
…reading tests

- TestCaseViewModel.OnTestResultReported now marshals to UI thread via
  SynchronizationContext (same pattern as DiagnosticsViewModel fix)
- Prevents COMException on WinUI when xunit v3 reports results from
  thread pool threads after the Task.Run isolation
- Add comprehensive threading test suite verifying:
  - Discovery/execution don't deadlock with blocking SyncContext
  - Execution moves to thread pool (not caller thread)
  - Concurrent executions complete without deadlock (AsyncLock)
  - DiagnosticsViewModel marshals via captured SyncContext
  - DiagnosticsViewModel falls back to direct add without SyncContext

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove unused 'firstResults' variable in Xunit3RunnerAdvancedTests
- Remove unused 'using NSubstitute' in Xunit3DiscovererAdvancedTests
- OCE handling in RunTestsSafe was already addressed in prior commit

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

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

Copilot reviewed 88 out of 90 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

test/DeviceRunners.VisualRunners.Xunit3.Tests/Testing/Xunit3RunnerAdvancedTests.cs:88

  • reportedResults is a List<T> that is appended to from ResultReported callbacks. Those callbacks may be raised from multiple threads (xUnit v3 message sink is explicitly thread-safe and can report from parallel execution), so this list mutation can race and make the test flaky. Use a thread-safe collection (or protect Add with a lock).
    src/DeviceRunners.VisualRunners/ViewModels/HomeViewModel.cs:83
  • StartAssemblyScanAsync now accepts a CancellationToken, but it’s only passed into discovery. If _options.AutoStart is enabled, the subsequent run (RunEverythingAsync / _runner.RunTestsAsync) ignores the token, so cancellation can still lead to a full test run starting. Consider propagating the token through the auto-run path (and early-exiting if cancellation is already requested).

mattleibow and others added 2 commits May 21, 2026 21:11
…variable

- Use ConcurrentBag<T> for ResultReported callbacks (may fire from
  parallel xunit v3 execution threads)
- Remove unused firstResult variable in RerunSingleTest test

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Verify the UIThreadCoordinator plumbing works correctly:
- Throws InvalidOperationException when no coordinator is configured
- Delegates to configured coordinator
- Coordinator receives and executes dispatched work
- UIFactAttribute/UITheoryAttribute have correct discoverer attributes
- UITestCase/UITheoryTestCase implement ISelfExecutingXunitTestCase

Full end-to-end UIFact/UITheory execution is tested by MAUI device
sample apps in CI (DynamicUITests, TestPageUITests).

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

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

Copilot reviewed 89 out of 91 changed files in this pull request and generated 1 comment.

Comment thread src/DeviceRunners.VisualRunners/ViewModels/HomeViewModel.cs
mattleibow and others added 6 commits May 22, 2026 23:26
…upport

# Conflicts:
#	sample/test/DeviceTestingKitApp.BrowserTests/DeviceTestingKitApp.BrowserTests.csproj
#	sample/test/DeviceTestingKitApp.BrowserTests/Program.cs
…etry

OperationCanceledException is now caught separately so that IsLoaded
is not set on cancellation, allowing subsequent discovery attempts.
IsLoaded is only set after successful discovery or on non-cancellation
errors (where retry would likely fail again anyway).

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

- Xunit3TestDiscoverer.DiscoverAsync now re-throws OperationCanceledException
  instead of swallowing it, so HomeViewModel's cancellation-aware catch block
  correctly prevents IsLoaded from being set (enabling retry).
- IDiagnosticsManager.DiagnosticMessageReceived now documents that handlers
  may be invoked on any thread and must not block.
- InMemoryXunit3TestFramework.CreateForAssembly documents that custom
  [TestFramework] attributes are not consulted when Assembly.Location is
  empty (matching v2 behavior on these platforms).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
xunit v3's MessageBus spawns a dedicated Thread for message delivery,
which throws PlatformNotSupportedException on single-threaded platforms
(WASM). Setting SynchronousMessageReporting=true tells the framework to
use a synchronous message bus instead, matching the approach already used
by the v2 runner (XunitReflectionTestRunner).

Applied unconditionally since threaded message reporting adds no value
in a device runner context where results are processed by the visual
runner's own pipeline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
RunTestsAsync_ExecutesOnThreadPoolThread asserted that result callback
thread IDs differ from the caller's thread ID. This is unreliable because
async/await releases the caller's thread back to the pool, allowing
Task.Run to reuse it — causing the assertion to fail non-deterministically
on macOS-Intel and Windows CI runners.

Replaced with RunTestsAsync_ReportsResultsForAllTestCases which verifies
the meaningful behavior (every test case gets a result callback with correct
status) rather than an implementation detail (thread scheduling). The
SyncContext non-capture guarantee is already tested by the dedicated
RunTestsAsync_DoesNotCaptureCallerSyncContext and
RunTestsAsync_WithCustomSyncContext_DoesNotDeadlock tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
On threaded platforms, the async MessageBus pump avoids contention on the
message sink lock by draining messages from a dedicated thread. Setting
SynchronousMessageReporting=true unconditionally forced every parallel
test worker to serialize on that lock inline, degrading throughput.

Now only applied when OperatingSystem.IsBrowser() is true (where Thread
cannot be spawned). Desktop/mobile retain full parallel execution perf.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mattleibow mattleibow marked this pull request as ready for review May 26, 2026 00:05
@mattleibow mattleibow merged commit 4cdfc85 into main May 26, 2026
73 of 74 checks passed
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.

2 participants