mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-07 13:42:13 -04:00
test(desktop): fail WinForms tests on UI-callback exceptions per STA thread
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.
This commit is contained in:
1 parent
67fb80e660
commit
a15c54fbaf
18 files changed
+142
-290
No files matched your search
@@ -1,5 +1,4 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Actions;
|
||||
using OpenNest.Controls;
|
||||
@@ -86,19 +85,6 @@ public class ActionSelectAreaCutOffTests
|
||||
public void ToggleSelectionDirection() => OnKeyUp(new KeyEventArgs(Keys.Space));
|
||||
}
|
||||
|
||||
private static void RunSta(System.Action action)
|
||||
{
|
||||
Exception? failure = null;
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
try { action(); }
|
||||
catch (Exception ex) { failure = ex; }
|
||||
})
|
||||
{ IsBackground = true };
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
Assert.True(thread.Join(TimeSpan.FromSeconds(60)), "The selection/fill test did not complete.");
|
||||
if (failure != null)
|
||||
ExceptionDispatchInfo.Capture(failure).Throw();
|
||||
}
|
||||
private static void RunSta(System.Action action) =>
|
||||
StaTestThread.Run(action, TimeSpan.FromSeconds(60), "The selection/fill test did not complete.");
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
|
||||
@@ -48,19 +47,6 @@ public class ArchUnitsTests
|
||||
|
||||
// Actual timeout execution is covered by the shared Regex mechanism in FractionTests;
|
||||
// these desktop tests deliberately avoid timing-sensitive oversized-input fixtures.
|
||||
internal static void RunSta(Action action)
|
||||
{
|
||||
Exception? error = null;
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
try { action(); }
|
||||
catch (Exception ex) { error = ex; }
|
||||
})
|
||||
{ IsBackground = true };
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
Assert.True(thread.Join(TimeSpan.FromSeconds(15)), "The STA test did not complete.");
|
||||
if (error != null)
|
||||
ExceptionDispatchInfo.Capture(error).Throw();
|
||||
}
|
||||
internal static void RunSta(Action action) =>
|
||||
StaTestThread.Run(action, TimeSpan.FromSeconds(15), "The STA test did not complete.");
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Drawing;
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.CNC;
|
||||
@@ -292,19 +291,6 @@ public class ProgramEditorControlTests
|
||||
private static void ApplyHighlighting(ProgramEditorControl control) => typeof(ProgramEditorControl)
|
||||
.GetMethod("ApplyHighlighting", BindingFlags.Instance | BindingFlags.NonPublic)!.Invoke(control, null);
|
||||
|
||||
private static void RunSta(Action action)
|
||||
{
|
||||
Exception? error = null;
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
try { action(); }
|
||||
catch (Exception ex) { error = ex; }
|
||||
})
|
||||
{ IsBackground = true };
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
Assert.True(thread.Join(TimeSpan.FromSeconds(30)), "The STA test did not complete.");
|
||||
if (error != null)
|
||||
ExceptionDispatchInfo.Capture(error).Throw();
|
||||
}
|
||||
private static void RunSta(Action action) =>
|
||||
StaTestThread.Run(action, TimeSpan.FromSeconds(30), "The STA test did not complete.");
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Drawing;
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Controls;
|
||||
|
||||
@@ -109,19 +108,6 @@ public class SeparatorPenLifetimeTests
|
||||
}
|
||||
}
|
||||
|
||||
private static void RunSta(Action action)
|
||||
{
|
||||
Exception? error = null;
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
try { action(); }
|
||||
catch (Exception ex) { error = ex; }
|
||||
})
|
||||
{ IsBackground = true };
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
Assert.True(thread.Join(TimeSpan.FromSeconds(30)), "The STA test did not complete.");
|
||||
if (error != null)
|
||||
ExceptionDispatchInfo.Capture(error).Throw();
|
||||
}
|
||||
private static void RunSta(Action action) =>
|
||||
StaTestThread.Run(action, TimeSpan.FromSeconds(30), "The STA test did not complete.");
|
||||
}
|
||||
@@ -2,7 +2,6 @@ using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Forms;
|
||||
@@ -231,19 +230,6 @@ public class AutoNestFormStockOptionsTests
|
||||
private static void SetValue(object row, string property, object value) =>
|
||||
TypeDescriptor.GetProperties(row)[property]!.SetValue(row, value);
|
||||
|
||||
private static void RunSta(Action action)
|
||||
{
|
||||
Exception? error = null;
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
try { action(); }
|
||||
catch (Exception ex) { error = ex; }
|
||||
})
|
||||
{ IsBackground = true };
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
Assert.True(thread.Join(TimeSpan.FromSeconds(15)), "The STA test did not complete.");
|
||||
if (error != null)
|
||||
ExceptionDispatchInfo.Capture(error).Throw();
|
||||
}
|
||||
private static void RunSta(Action action) =>
|
||||
StaTestThread.Run(action, TimeSpan.FromSeconds(15), "The STA test did not complete.");
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Controls;
|
||||
@@ -354,24 +353,6 @@ public class AutomaticCutOffFormTests
|
||||
typeof(AutomaticCutOffForm).GetMethod(method, BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||
.Invoke(form, arguments);
|
||||
|
||||
private static void RunSta(System.Action action)
|
||||
{
|
||||
Exception? failure = null;
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failure = ex;
|
||||
}
|
||||
});
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
thread.Join();
|
||||
if (failure != null)
|
||||
ExceptionDispatchInfo.Capture(failure).Throw();
|
||||
}
|
||||
private static void RunSta(System.Action action) =>
|
||||
StaTestThread.Run(action, Timeout.InfiniteTimeSpan, "The STA test did not complete.");
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Collections;
|
||||
@@ -408,19 +407,6 @@ public class BestFitViewerOperationLifetimeTests
|
||||
private static void Invoke(BestFitViewerForm form, string name, params object[] args) =>
|
||||
typeof(BestFitViewerForm).GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic)!.Invoke(form, args);
|
||||
|
||||
private static void RunSta(System.Action action)
|
||||
{
|
||||
Exception? failure = null;
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
try { action(); }
|
||||
catch (Exception ex) { failure = ex; }
|
||||
})
|
||||
{ IsBackground = true };
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
Assert.True(thread.Join(TimeSpan.FromSeconds(60)), "STA test did not complete.");
|
||||
if (failure != null)
|
||||
ExceptionDispatchInfo.Capture(failure).Throw();
|
||||
}
|
||||
private static void RunSta(System.Action action) =>
|
||||
StaTestThread.Run(action, TimeSpan.FromSeconds(60), "STA test did not complete.");
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Forms;
|
||||
|
||||
@@ -146,25 +145,6 @@ public class EditNestFocusTraversalTests
|
||||
tabControl.SelectedTab = Assert.IsType<TabPage>(plates.Parent);
|
||||
}
|
||||
|
||||
private static void RunSta(System.Action testBody)
|
||||
{
|
||||
Exception? testFailure = null;
|
||||
var staThread = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
testBody();
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
testFailure = error;
|
||||
}
|
||||
})
|
||||
{ IsBackground = true };
|
||||
staThread.SetApartmentState(ApartmentState.STA);
|
||||
staThread.Start();
|
||||
Assert.True(staThread.Join(TimeSpan.FromSeconds(60)), "The focus traversal STA test did not complete.");
|
||||
if (testFailure != null)
|
||||
ExceptionDispatchInfo.Capture(testFailure).Throw();
|
||||
}
|
||||
private static void RunSta(System.Action testBody) =>
|
||||
StaTestThread.Run(testBody, TimeSpan.FromSeconds(60), "The focus traversal STA test did not complete.");
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Forms;
|
||||
|
||||
@@ -40,25 +39,6 @@ public class LeadInSidePanelTests
|
||||
Assert.Equal("Select", form.PlateView.Status);
|
||||
});
|
||||
|
||||
private static void RunSta(System.Action action)
|
||||
{
|
||||
Exception? failure = null;
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failure = ex;
|
||||
}
|
||||
})
|
||||
{ IsBackground = true };
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
Assert.True(thread.Join(TimeSpan.FromSeconds(60)), "The STA test did not complete.");
|
||||
if (failure != null)
|
||||
ExceptionDispatchInfo.Capture(failure).Throw();
|
||||
}
|
||||
private static void RunSta(System.Action action) =>
|
||||
StaTestThread.Run(action, TimeSpan.FromSeconds(60), "The STA test did not complete.");
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Controls;
|
||||
@@ -299,25 +298,6 @@ public class NestReportExportTests
|
||||
internal override void ReportSuccess(string destination) => Successes.Add(destination);
|
||||
}
|
||||
|
||||
private static void RunSta(System.Action action)
|
||||
{
|
||||
Exception? failure = null;
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failure = ex;
|
||||
}
|
||||
})
|
||||
{ IsBackground = true };
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
Assert.True(thread.Join(TimeSpan.FromSeconds(90)), "The STA test did not complete.");
|
||||
if (failure != null)
|
||||
ExceptionDispatchInfo.Capture(failure).Throw();
|
||||
}
|
||||
private static void RunSta(System.Action action) =>
|
||||
StaTestThread.Run(action, TimeSpan.FromSeconds(90), "The STA test did not complete.");
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Forms;
|
||||
|
||||
@@ -62,14 +61,6 @@ public class NestValidationFormTests
|
||||
});
|
||||
}
|
||||
|
||||
private static void RunSta(Action action)
|
||||
{
|
||||
Exception? error = null;
|
||||
var thread = new Thread(() => { try { action(); } catch (Exception ex) { error = ex; } });
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
Assert.True(thread.Join(TimeSpan.FromSeconds(15)));
|
||||
if (error != null)
|
||||
ExceptionDispatchInfo.Capture(error).Throw();
|
||||
}
|
||||
private static void RunSta(Action action) =>
|
||||
StaTestThread.Run(action, TimeSpan.FromSeconds(15), "The STA test did not complete.");
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Data;
|
||||
using OpenNest.Forms;
|
||||
@@ -453,25 +452,6 @@ public class PlateExportTests
|
||||
public void Dispose() => Directory.Delete(DirectoryPath, true);
|
||||
}
|
||||
|
||||
private static void RunSta(System.Action action)
|
||||
{
|
||||
Exception? failure = null;
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failure = ex;
|
||||
}
|
||||
})
|
||||
{ IsBackground = true };
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
Assert.True(thread.Join(TimeSpan.FromSeconds(90)), "The STA test did not complete.");
|
||||
if (failure != null)
|
||||
ExceptionDispatchInfo.Capture(failure).Throw();
|
||||
}
|
||||
private static void RunSta(System.Action action) =>
|
||||
StaTestThread.Run(action, TimeSpan.FromSeconds(90), "The STA test did not complete.");
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Controls;
|
||||
@@ -283,25 +282,6 @@ public class PlateViewFillLifetimeTests
|
||||
form.GetType().GetMethod(method, BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||
.Invoke(form, arguments);
|
||||
|
||||
private static void RunSta(System.Action action)
|
||||
{
|
||||
Exception? failure = null;
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failure = ex;
|
||||
}
|
||||
})
|
||||
{ IsBackground = true };
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
Assert.True(thread.Join(TimeSpan.FromSeconds(60)), "The STA test did not complete.");
|
||||
if (failure != null)
|
||||
ExceptionDispatchInfo.Capture(failure).Throw();
|
||||
}
|
||||
private static void RunSta(System.Action action) =>
|
||||
StaTestThread.Run(action, TimeSpan.FromSeconds(60), "The STA test did not complete.");
|
||||
}
|
||||
@@ -206,18 +206,6 @@ public class PostVerificationFormTests
|
||||
form.GetType().GetMethod(method, BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||
.Invoke(form, arguments);
|
||||
|
||||
private static void RunSta(System.Action action)
|
||||
{
|
||||
Exception? failure = null;
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
try { action(); }
|
||||
catch (Exception ex) { failure = ex; }
|
||||
});
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
Assert.True(thread.Join(TimeSpan.FromSeconds(30)), "STA verification test timed out.");
|
||||
if (failure != null)
|
||||
ExceptionDispatchInfo.Capture(failure).Throw();
|
||||
}
|
||||
private static void RunSta(System.Action action) =>
|
||||
StaTestThread.Run(action, TimeSpan.FromSeconds(30), "STA verification test timed out.");
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Controls;
|
||||
@@ -1183,19 +1182,6 @@ public class PlateOverlapOverlayTests
|
||||
return new Part(new Drawing("same name", program), new Vector(x, 0));
|
||||
}
|
||||
|
||||
private static void RunSta(System.Action action)
|
||||
{
|
||||
Exception? failure = null;
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
try { action(); }
|
||||
catch (Exception ex) { failure = ex; }
|
||||
})
|
||||
{ IsBackground = true };
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
Assert.True(thread.Join(TimeSpan.FromSeconds(60)), "The STA test did not complete.");
|
||||
if (failure != null)
|
||||
ExceptionDispatchInfo.Capture(failure).Throw();
|
||||
}
|
||||
private static void RunSta(System.Action action) =>
|
||||
StaTestThread.Run(action, TimeSpan.FromSeconds(60), "The STA test did not complete.");
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace OpenNest.WinForms.Tests;
|
||||
|
||||
public class StaTestThreadTests
|
||||
{
|
||||
private const string CallbackMessage = "thrown from a queued UI callback";
|
||||
|
||||
[Fact]
|
||||
public void QueuedCallbackExceptionFailsTheTestInsteadOfOpeningADialog()
|
||||
{
|
||||
var error = Assert.Throws<InvalidOperationException>(() =>
|
||||
StaTestThread.Run(
|
||||
ThrowFromQueuedCallback,
|
||||
TimeSpan.FromSeconds(30),
|
||||
"The STA test did not complete."
|
||||
)
|
||||
);
|
||||
|
||||
Assert.Equal(CallbackMessage, error.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithoutThrowExceptionModeTheCallbackExceptionNeverReachesTheTestBody()
|
||||
{
|
||||
// Control: the default mode routes the same exception to the thread's
|
||||
// ThreadException handler (or, with none, to a modal dialog), so the
|
||||
// test body itself completes without seeing it.
|
||||
Exception? routed = null;
|
||||
Exception? bodyFailure = null;
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
Application.ThreadException += (_, e) => routed = e.Exception;
|
||||
try
|
||||
{
|
||||
ThrowFromQueuedCallback();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
bodyFailure = ex;
|
||||
}
|
||||
})
|
||||
{
|
||||
IsBackground = true,
|
||||
};
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
|
||||
Assert.True(thread.Join(TimeSpan.FromSeconds(30)), "The control STA thread did not complete.");
|
||||
if (bodyFailure != null)
|
||||
ExceptionDispatchInfo.Capture(bodyFailure).Throw();
|
||||
Assert.IsType<InvalidOperationException>(routed);
|
||||
Assert.Equal(CallbackMessage, routed!.Message);
|
||||
}
|
||||
|
||||
private static void ThrowFromQueuedCallback()
|
||||
{
|
||||
using var control = new Control();
|
||||
_ = control.Handle;
|
||||
control.BeginInvoke(new Action(() => throw new InvalidOperationException(CallbackMessage)));
|
||||
Application.DoEvents();
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace OpenNest.WinForms.Tests;
|
||||
|
||||
internal static class TestAssemblySetup
|
||||
{
|
||||
// SetUnhandledExceptionMode only changes which *default* dialog style WinForms would
|
||||
// show; it still shows one. A registered ThreadException handler is what actually
|
||||
// suppresses it (WinForms always prefers the handler over the default dialog), which
|
||||
// matters here because none of our tests call Application.Run() to install one.
|
||||
// Without this, an exception thrown inside a WndProc callback (e.g. a DataGridView
|
||||
// commit failure during Show()) pops a modal ThreadExceptionDialog that blocks the
|
||||
// desktop until someone dismisses it by hand, instead of failing the test normally.
|
||||
[ModuleInitializer]
|
||||
internal static void Initialize()
|
||||
{
|
||||
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
|
||||
Application.ThreadException += (_, e) =>
|
||||
Console.Error.WriteLine($"Suppressed ThreadExceptionDialog: {e.Exception}");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user