84 lines
2.2 KiB
C#
84 lines
2.2 KiB
C#
using System;
|
|
using System.Threading;
|
|
using System.Windows.Threading;
|
|
using Xunit;
|
|
|
|
namespace ytLive.Tests;
|
|
|
|
/// <summary>
|
|
/// Tests that spin up the real WPF App need exactly one Application instance per
|
|
/// AppDomain (WPF enforces it) — so they share this serial collection and a
|
|
/// single App created on one dedicated STA thread. Marshal test bodies onto it
|
|
/// via <see cref="RealAppHost.Run"/>, never `new App()` per test.
|
|
/// </summary>
|
|
[CollectionDefinition("RealApp", DisableParallelization = true)]
|
|
public sealed class RealAppCollection : ICollectionFixture<RealAppHost>
|
|
{
|
|
}
|
|
|
|
/// <summary>Owns the one-and-only WPF App on a dedicated STA thread.</summary>
|
|
public sealed class RealAppHost : IDisposable
|
|
{
|
|
private readonly Thread _thread;
|
|
private readonly Dispatcher _dispatcher;
|
|
|
|
public RealAppHost()
|
|
{
|
|
Exception? boot = null;
|
|
Dispatcher? dispatcher = null;
|
|
_thread = new Thread(() =>
|
|
{
|
|
try
|
|
{
|
|
var app = new App();
|
|
app.InitializeComponent();
|
|
dispatcher = Dispatcher.CurrentDispatcher;
|
|
Dispatcher.Run();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
boot = ex;
|
|
}
|
|
});
|
|
_thread.SetApartmentState(ApartmentState.STA);
|
|
_thread.Start();
|
|
while (dispatcher == null && boot == null)
|
|
Thread.Sleep(5);
|
|
if (boot != null)
|
|
throw new Xunit.Sdk.XunitException("Real WPF App failed to start: " + boot);
|
|
_dispatcher = dispatcher!;
|
|
}
|
|
|
|
/// <summary>Runs <paramref name="action"/> on the App's STA thread and rethrows any failure.</summary>
|
|
public void Run(Action action)
|
|
{
|
|
Exception? failure = null;
|
|
_dispatcher.Invoke(() =>
|
|
{
|
|
try
|
|
{
|
|
action();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
failure = ex;
|
|
}
|
|
});
|
|
if (failure != null)
|
|
throw failure;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
try
|
|
{
|
|
_dispatcher.InvokeShutdown();
|
|
}
|
|
catch
|
|
{
|
|
// The App never ran its message loop — shutdown is best-effort.
|
|
}
|
|
_thread.Join(2000);
|
|
}
|
|
}
|