|
|
@@ -0,0 +1,387 @@
|
|
|
+using System;
|
|
|
+using System.Threading;
|
|
|
+using System.Threading.Tasks;
|
|
|
+using Avalonia.Controls.Platform;
|
|
|
+using Avalonia.Threading;
|
|
|
+using Xunit;
|
|
|
+
|
|
|
+namespace Avalonia.Base.UnitTests;
|
|
|
+
|
|
|
+// Some of these exceptions are based from https://github.com/dotnet/wpf-test/blob/05797008bb4975ceeb71be36c47f01688f535d53/src/Test/ElementServices/FeatureTests/Untrusted/Dispatcher/UnhandledExceptionTest.cs#L30
|
|
|
+public partial class DispatcherTests
|
|
|
+{
|
|
|
+ private const string ExpectedExceptionText = "Exception thrown inside Dispatcher.Invoke / Dispatcher.BeginInvoke.";
|
|
|
+
|
|
|
+ private int _numberOfHandlerOnUnhandledEventInvoked;
|
|
|
+ private int _numberOfHandlerOnUnhandledEventFilterInvoked;
|
|
|
+
|
|
|
+ public DispatcherTests()
|
|
|
+ {
|
|
|
+ _numberOfHandlerOnUnhandledEventInvoked = 0;
|
|
|
+ _numberOfHandlerOnUnhandledEventFilterInvoked = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void DispatcherHandlesExceptionWithPost()
|
|
|
+ {
|
|
|
+ var impl = new ManagedDispatcherImpl(null);
|
|
|
+ var disp = new Dispatcher(impl);
|
|
|
+
|
|
|
+ var handled = false;
|
|
|
+ var executed = false;
|
|
|
+ disp.UnhandledException += (sender, args) =>
|
|
|
+ {
|
|
|
+ handled = true;
|
|
|
+ args.Handled = true;
|
|
|
+ };
|
|
|
+ disp.Post(() => ThrowAnException());
|
|
|
+ disp.Post(() => executed = true);
|
|
|
+
|
|
|
+ disp.RunJobs();
|
|
|
+
|
|
|
+ Assert.True(handled);
|
|
|
+ Assert.True(executed);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void SyncContextExceptionCanBeHandledWithPost()
|
|
|
+ {
|
|
|
+ var impl = new ManagedDispatcherImpl(null);
|
|
|
+ var disp = new Dispatcher(impl);
|
|
|
+
|
|
|
+ var syncContext = disp.GetContextWithPriority(DispatcherPriority.Background);
|
|
|
+
|
|
|
+ var handled = false;
|
|
|
+ var executed = false;
|
|
|
+ disp.UnhandledException += (sender, args) =>
|
|
|
+ {
|
|
|
+ handled = true;
|
|
|
+ args.Handled = true;
|
|
|
+ };
|
|
|
+
|
|
|
+ syncContext.Post(_ => ThrowAnException(), null);
|
|
|
+ syncContext.Post(_ => executed = true, null);
|
|
|
+
|
|
|
+ disp.RunJobs();
|
|
|
+
|
|
|
+ Assert.True(handled);
|
|
|
+ Assert.True(executed);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void CanRemoveDispatcherExceptionHandler()
|
|
|
+ {
|
|
|
+ var impl = new ManagedDispatcherImpl(null);
|
|
|
+ var dispatcher = new Dispatcher(impl);
|
|
|
+ var caughtCorrectException = false;
|
|
|
+
|
|
|
+ dispatcher.UnhandledExceptionFilter +=
|
|
|
+ HandlerOnUnhandledExceptionFilterRequestCatch;
|
|
|
+ dispatcher.UnhandledException +=
|
|
|
+ HandlerOnUnhandledExceptionNotHandled;
|
|
|
+
|
|
|
+ dispatcher.UnhandledExceptionFilter -=
|
|
|
+ HandlerOnUnhandledExceptionFilterRequestCatch;
|
|
|
+ dispatcher.UnhandledException -=
|
|
|
+ HandlerOnUnhandledExceptionNotHandled;
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ dispatcher.Post(ThrowAnException, DispatcherPriority.Normal);
|
|
|
+ dispatcher.RunJobs();
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ caughtCorrectException = e.Message == ExpectedExceptionText;
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ Verification(caughtCorrectException, 0, 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void CanHandleExceptionWithUnhandledException()
|
|
|
+ {
|
|
|
+ var impl = new ManagedDispatcherImpl(null);
|
|
|
+ var dispatcher = new Dispatcher(impl);
|
|
|
+
|
|
|
+ dispatcher.UnhandledExceptionFilter +=
|
|
|
+ HandlerOnUnhandledExceptionFilterRequestCatch;
|
|
|
+
|
|
|
+ dispatcher.UnhandledException +=
|
|
|
+ HandlerOnUnhandledExceptionHandled;
|
|
|
+ var caughtCorrectException = true;
|
|
|
+ try
|
|
|
+ {
|
|
|
+ dispatcher.Post(ThrowAnException, DispatcherPriority.Normal);
|
|
|
+ dispatcher.RunJobs();
|
|
|
+ }
|
|
|
+ catch (Exception)
|
|
|
+ {
|
|
|
+ // should be no exception here.
|
|
|
+ caughtCorrectException = false;
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ Verification(caughtCorrectException, 1, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void InvokeMethodDoesntTriggerUnhandledException()
|
|
|
+ {
|
|
|
+ var impl = new ManagedDispatcherImpl(null);
|
|
|
+ var dispatcher = new Dispatcher(impl);
|
|
|
+
|
|
|
+ dispatcher.UnhandledExceptionFilter +=
|
|
|
+ HandlerOnUnhandledExceptionFilterRequestCatch;
|
|
|
+
|
|
|
+ dispatcher.UnhandledException +=
|
|
|
+ HandlerOnUnhandledExceptionHandled;
|
|
|
+ var caughtCorrectException = false;
|
|
|
+ try
|
|
|
+ {
|
|
|
+ // Since both Invoke and InvokeAsync can throw exception, there is no need to pass them to the UnhandledException.
|
|
|
+ dispatcher.Invoke(ThrowAnException, DispatcherPriority.Normal);
|
|
|
+ dispatcher.RunJobs();
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ // should be no exception here.
|
|
|
+ caughtCorrectException = e.Message == ExpectedExceptionText;
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ Verification(caughtCorrectException, 0, 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void InvokeAsyncMethodDoesntTriggerUnhandledException()
|
|
|
+ {
|
|
|
+ var impl = new ManagedDispatcherImpl(null);
|
|
|
+ var dispatcher = new Dispatcher(impl);
|
|
|
+
|
|
|
+ dispatcher.UnhandledExceptionFilter +=
|
|
|
+ HandlerOnUnhandledExceptionFilterRequestCatch;
|
|
|
+
|
|
|
+ dispatcher.UnhandledException +=
|
|
|
+ HandlerOnUnhandledExceptionHandled;
|
|
|
+ var caughtCorrectException = false;
|
|
|
+ try
|
|
|
+ {
|
|
|
+ // Since both Invoke and InvokeAsync can throw exception, there is no need to pass them to the UnhandledException.
|
|
|
+ var op = dispatcher.InvokeAsync(ThrowAnException, DispatcherPriority.Normal);
|
|
|
+ op.Wait();
|
|
|
+ dispatcher.RunJobs();
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ // should be no exception here.
|
|
|
+ caughtCorrectException = e.Message == ExpectedExceptionText;
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ Verification(caughtCorrectException, 0, 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void CanRethrowExceptionWithUnhandledException()
|
|
|
+ {
|
|
|
+ var impl = new ManagedDispatcherImpl(null);
|
|
|
+ var dispatcher = new Dispatcher(impl);
|
|
|
+
|
|
|
+ dispatcher.UnhandledExceptionFilter +=
|
|
|
+ HandlerOnUnhandledExceptionFilterRequestCatch;
|
|
|
+
|
|
|
+ dispatcher.UnhandledException +=
|
|
|
+ HandlerOnUnhandledExceptionNotHandled;
|
|
|
+ var caughtCorrectException = false;
|
|
|
+ try
|
|
|
+ {
|
|
|
+ dispatcher.Post(ThrowAnException, DispatcherPriority.Normal);
|
|
|
+ dispatcher.RunJobs();
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ caughtCorrectException = e.Message == ExpectedExceptionText;
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ Verification(caughtCorrectException, 1, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void MultipleUnhandledExceptionFilterCannotResetRequestCatchFlag()
|
|
|
+ {
|
|
|
+ var impl = new ManagedDispatcherImpl(null);
|
|
|
+ var dispatcher = new Dispatcher(impl);
|
|
|
+
|
|
|
+ dispatcher.UnhandledExceptionFilter +=
|
|
|
+ HandlerOnUnhandledExceptionFilterNotRequestCatch;
|
|
|
+ dispatcher.UnhandledExceptionFilter +=
|
|
|
+ HandlerOnUnhandledExceptionFilterRequestCatch;
|
|
|
+
|
|
|
+ dispatcher.UnhandledException +=
|
|
|
+ HandlerOnUnhandledExceptionNotHandled;
|
|
|
+ dispatcher.UnhandledException +=
|
|
|
+ HandlerOnUnhandledExceptionHandled;
|
|
|
+ var caughtCorrectException = false;
|
|
|
+ try
|
|
|
+ {
|
|
|
+ dispatcher.Post(ThrowAnException, DispatcherPriority.Normal);
|
|
|
+ dispatcher.RunJobs();
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ caughtCorrectException = e.Message == ExpectedExceptionText;
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ Verification(caughtCorrectException, 0, 2);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void MultipleUnhandledExceptionCannotResetHandleFlag()
|
|
|
+ {
|
|
|
+ var impl = new ManagedDispatcherImpl(null);
|
|
|
+ var dispatcher = new Dispatcher(impl);
|
|
|
+
|
|
|
+ dispatcher.UnhandledExceptionFilter +=
|
|
|
+ HandlerOnUnhandledExceptionFilterRequestCatch;
|
|
|
+
|
|
|
+ dispatcher.UnhandledException +=
|
|
|
+ HandlerOnUnhandledExceptionHandled;
|
|
|
+ dispatcher.UnhandledException +=
|
|
|
+ HandlerOnUnhandledExceptionNotHandled;
|
|
|
+ var caughtCorrectException = true;
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ dispatcher.Post(ThrowAnException, DispatcherPriority.Normal);
|
|
|
+ dispatcher.RunJobs();
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ // should be no exception here.
|
|
|
+ caughtCorrectException = false;
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ Verification(caughtCorrectException, 1, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void CanPushFrameAndShutdownDispatcherFromUnhandledException()
|
|
|
+ {
|
|
|
+ var impl = new ManagedDispatcherImpl(null);
|
|
|
+ var dispatcher = new Dispatcher(impl);
|
|
|
+
|
|
|
+ dispatcher.UnhandledExceptionFilter +=
|
|
|
+ HandlerOnUnhandledExceptionFilterNotRequestCatchPushFrame;
|
|
|
+
|
|
|
+ dispatcher.UnhandledException +=
|
|
|
+ HandlerOnUnhandledExceptionHandledPushFrame;
|
|
|
+ var caughtCorrectException = false;
|
|
|
+ try
|
|
|
+ {
|
|
|
+ dispatcher.Post(ThrowAnException, DispatcherPriority.Normal);
|
|
|
+ dispatcher.RunJobs();
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ caughtCorrectException = e.Message == ExpectedExceptionText;
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ Verification(caughtCorrectException, 0, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void Verification(bool caughtCorrectException, int numberOfHandlerOnUnhandledEventShouldInvoke,
|
|
|
+ int numberOfHandlerOnUnhandledEventFilterShouldInvoke)
|
|
|
+ {
|
|
|
+ Assert.True(
|
|
|
+ _numberOfHandlerOnUnhandledEventInvoked >= numberOfHandlerOnUnhandledEventShouldInvoke,
|
|
|
+ "Number of handler invoked on UnhandledException is invalid");
|
|
|
+
|
|
|
+ Assert.True(
|
|
|
+ _numberOfHandlerOnUnhandledEventFilterInvoked >= numberOfHandlerOnUnhandledEventFilterShouldInvoke,
|
|
|
+ "Number of handler invoked on UnhandledExceptionFilter is invalid");
|
|
|
+
|
|
|
+ Assert.True(caughtCorrectException, "Wrong exception caught.");
|
|
|
+ }
|
|
|
+
|
|
|
+ private void HandlerOnUnhandledExceptionFilterRequestCatch(object sender,
|
|
|
+ DispatcherUnhandledExceptionFilterEventArgs args)
|
|
|
+ {
|
|
|
+ args.RequestCatch = true;
|
|
|
+
|
|
|
+ _numberOfHandlerOnUnhandledEventFilterInvoked += 1;
|
|
|
+ Assert.Equal(ExpectedExceptionText, args.Exception.Message);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void HandlerOnUnhandledExceptionFilterNotRequestCatchPushFrame(object sender,
|
|
|
+ DispatcherUnhandledExceptionFilterEventArgs args)
|
|
|
+ {
|
|
|
+ HandlerOnUnhandledExceptionFilterNotRequestCatch(sender, args);
|
|
|
+ var frame = new DispatcherFrame();
|
|
|
+ args.Dispatcher.InvokeAsync(() => frame.Continue = false, DispatcherPriority.Background);
|
|
|
+ args.Dispatcher.PushFrame(frame);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void HandlerOnUnhandledExceptionFilterNotRequestCatch(object sender,
|
|
|
+ DispatcherUnhandledExceptionFilterEventArgs args)
|
|
|
+ {
|
|
|
+ args.RequestCatch = false;
|
|
|
+ _numberOfHandlerOnUnhandledEventFilterInvoked += 1;
|
|
|
+
|
|
|
+ Assert.Equal(ExpectedExceptionText, args.Exception.Message);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void HandlerOnUnhandledExceptionHandledPushFrame(object sender, DispatcherUnhandledExceptionEventArgs args)
|
|
|
+ {
|
|
|
+ Assert.Equal(ExpectedExceptionText, args.Exception.Message);
|
|
|
+ Assert.False(_numberOfHandlerOnUnhandledEventFilterInvoked == 0,
|
|
|
+ "UnhandledExceptionFilter should be invoked before UnhandledException.");
|
|
|
+
|
|
|
+ args.Handled = true;
|
|
|
+ _numberOfHandlerOnUnhandledEventInvoked += 1;
|
|
|
+
|
|
|
+ var dispatcher = args.Dispatcher;
|
|
|
+ var frame = new DispatcherFrame();
|
|
|
+ dispatcher.BeginInvokeShutdown(DispatcherPriority.Background);
|
|
|
+ dispatcher.PushFrame(frame);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void HandlerOnUnhandledExceptionHandled(object sender, DispatcherUnhandledExceptionEventArgs args)
|
|
|
+ {
|
|
|
+ Assert.Equal(ExpectedExceptionText, args.Exception.Message);
|
|
|
+ Assert.False(_numberOfHandlerOnUnhandledEventFilterInvoked == 0,
|
|
|
+ "UnhandledExceptionFilter should be invoked before UnhandledException.");
|
|
|
+
|
|
|
+ args.Handled = true;
|
|
|
+ _numberOfHandlerOnUnhandledEventInvoked += 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void HandlerOnUnhandledExceptionNotHandled(object sender, DispatcherUnhandledExceptionEventArgs args)
|
|
|
+ {
|
|
|
+ Assert.Equal(ExpectedExceptionText, args.Exception.Message);
|
|
|
+ Assert.False(_numberOfHandlerOnUnhandledEventFilterInvoked == 0,
|
|
|
+ "UnhandledExceptionFilter should be invoked before UnhandledException.");
|
|
|
+
|
|
|
+ args.Handled = false;
|
|
|
+ _numberOfHandlerOnUnhandledEventInvoked += 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ThrowAnException()
|
|
|
+ {
|
|
|
+ throw new Exception(ExpectedExceptionText);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|