mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-05 12:22:13 -04:00
TestAssemblySetup set the unhandled-exception mode and a ThreadException handler from a module initializer. WinForms stores both per thread (ThreadContext/NativeWindow thread statics), so they applied only to the runner thread that loaded the assembly, never to the STA threads the tests run on. There, an exception escaping a window message or a BeginInvoke/SynchronizationContext callback still opened a modal ThreadExceptionDialog in an interactive session, or was silently ignored otherwise; the handler would also have logged and swallowed it. Replace the fifteen copied RunSta helpers with StaTestThread.Run, which selects UnhandledExceptionMode.ThrowException on each STA thread before it creates a window, so the exception propagates to the test body and fails the test. Each class keeps its own timeout and message; all threads are now background threads. Remove TestAssemblySetup. StaTestThreadTests covers a throwing BeginInvoke callback through StaTestThread (fails with the original exception) and, as a control, the default mode on a plain STA thread (the exception goes to the thread's handler and never reaches the test body). Cross-compiled on Linux; Windows execution is the windows-desktop CI job.
49 lines
1.7 KiB
C#
49 lines
1.7 KiB
C#
using System.Runtime.ExceptionServices;
|
|
using System.Windows.Forms;
|
|
|
|
namespace OpenNest.WinForms.Tests;
|
|
|
|
/// <summary>
|
|
/// Runs a test body on its own STA thread and rethrows its failure on the
|
|
/// calling test thread.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// By default WinForms catches an exception raised while dispatching a window
|
|
/// message or a BeginInvoke/SynchronizationContext callback and hands it to the
|
|
/// thread's ThreadException handler. With none registered it opens a modal
|
|
/// ThreadExceptionDialog in an interactive session, which blocks the test until
|
|
/// its join timeout, and silently ignores the exception otherwise. Both the
|
|
/// unhandled-exception mode and the handler are per-thread, so they cannot be
|
|
/// configured once for the assembly: each STA thread selects
|
|
/// <see cref="UnhandledExceptionMode.ThrowException"/> before it creates a
|
|
/// window, and the exception then propagates to the code pumping messages
|
|
/// (Show, DoEvents, ShowDialog) and fails the test.
|
|
/// </remarks>
|
|
internal static class StaTestThread
|
|
{
|
|
public static void Run(Action body, TimeSpan timeout, string timeoutMessage)
|
|
{
|
|
Exception? failure = null;
|
|
var thread = new Thread(() =>
|
|
{
|
|
try
|
|
{
|
|
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
|
|
body();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
failure = ex;
|
|
}
|
|
})
|
|
{
|
|
IsBackground = true,
|
|
};
|
|
thread.SetApartmentState(ApartmentState.STA);
|
|
thread.Start();
|
|
Assert.True(thread.Join(timeout), timeoutMessage);
|
|
if (failure != null)
|
|
ExceptionDispatchInfo.Capture(failure).Throw();
|
|
}
|
|
}
|