A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

394 lines
14 KiB

using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls.Platform;
using Avalonia.Threading;
using Avalonia.UnitTests;
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 : ScopedTestBase
{
private const string ExpectedExceptionText = "Exception thrown inside Dispatcher.Invoke / Dispatcher.BeginInvoke.";
private int _numberOfHandlerOnUnhandledEventInvoked;
private int _numberOfHandlerOnUnhandledEventFilterInvoked;
private Dispatcher _uiThread;
public DispatcherTests()
{
_numberOfHandlerOnUnhandledEventInvoked = 0;
_numberOfHandlerOnUnhandledEventFilterInvoked = 0;
VerifyDispatcherSanity();
_uiThread = Dispatcher.CurrentDispatcher;
}
void VerifyDispatcherSanity()
{
// Verify that we are in a clear-ish state. Do this for every test to ensure that our reset procedure is working
Assert.Null(Dispatcher.FromThread(Thread.CurrentThread));
Assert.Null(Dispatcher.TryGetUIThread());
// The first (this) dispatcher becomes UI thread one
Assert.NotNull(Dispatcher.CurrentDispatcher);
Assert.Equal(Dispatcher.TryGetUIThread(), Dispatcher.CurrentDispatcher);
Assert.Equal(Dispatcher.UIThread, Dispatcher.CurrentDispatcher);
// Dispatcher.FromThread works
Assert.Equal(Dispatcher.CurrentDispatcher, Dispatcher.FromThread(Thread.CurrentThread));
Assert.Equal(Dispatcher.UIThread, Dispatcher.FromThread(Thread.CurrentThread));
}
[Fact]
[SuppressMessage("Usage", "xUnit1031:Do not use blocking task operations in test method", Justification = "Tests the dispatcher itself")]
public void Different_Threads_Auto_Spawn_Dispatchers()
{
var dispatcher = Dispatcher.CurrentDispatcher;
ThreadRunHelper.RunOnDedicatedThread(() =>
{
Assert.Null(Dispatcher.FromThread(Thread.CurrentThread));
Assert.NotNull(Dispatcher.CurrentDispatcher);
Assert.NotEqual(dispatcher, Dispatcher.CurrentDispatcher);
Assert.Equal(Dispatcher.CurrentDispatcher, Dispatcher.FromThread(Thread.CurrentThread));
}).GetAwaiter().GetResult();
}
[Fact]
public void DispatcherHandlesExceptionWithPost()
{
var handled = false;
var executed = false;
_uiThread.UnhandledException += (sender, args) =>
{
handled = true;
args.Handled = true;
};
_uiThread.Post(() => ThrowAnException());
_uiThread.Post(() => executed = true);
_uiThread.RunJobs(null, TestContext.Current.CancellationToken);
Assert.True(handled);
Assert.True(executed);
}
[Fact]
public void SyncContextExceptionCanBeHandledWithPost()
{
var syncContext = _uiThread.GetContextWithPriority(DispatcherPriority.Background);
var handled = false;
var executed = false;
_uiThread.UnhandledException += (sender, args) =>
{
handled = true;
args.Handled = true;
};
syncContext.Post(_ => ThrowAnException(), null);
syncContext.Post(_ => executed = true, null);
_uiThread.RunJobs(null, TestContext.Current.CancellationToken);
Assert.True(handled);
Assert.True(executed);
}
[Fact]
public void CanRemoveDispatcherExceptionHandler()
{
var caughtCorrectException = false;
_uiThread.UnhandledExceptionFilter +=
HandlerOnUnhandledExceptionFilterRequestCatch;
_uiThread.UnhandledException +=
HandlerOnUnhandledExceptionNotHandled;
_uiThread.UnhandledExceptionFilter -=
HandlerOnUnhandledExceptionFilterRequestCatch;
_uiThread.UnhandledException -=
HandlerOnUnhandledExceptionNotHandled;
try
{
_uiThread.Post(ThrowAnException, DispatcherPriority.Normal);
_uiThread.RunJobs(null, TestContext.Current.CancellationToken);
}
catch (Exception e)
{
caughtCorrectException = e.Message == ExpectedExceptionText;
}
finally
{
Verification(caughtCorrectException, 0, 0);
}
}
[Fact]
public void CanHandleExceptionWithUnhandledException()
{
_uiThread.UnhandledExceptionFilter +=
HandlerOnUnhandledExceptionFilterRequestCatch;
_uiThread.UnhandledException +=
HandlerOnUnhandledExceptionHandled;
var caughtCorrectException = true;
try
{
_uiThread.Post(ThrowAnException, DispatcherPriority.Normal);
_uiThread.RunJobs(null, TestContext.Current.CancellationToken);
}
catch (Exception)
{
// should be no exception here.
caughtCorrectException = false;
}
finally
{
Verification(caughtCorrectException, 1, 1);
}
}
[Fact]
public void InvokeMethodDoesntTriggerUnhandledException()
{
_uiThread.UnhandledExceptionFilter +=
HandlerOnUnhandledExceptionFilterRequestCatch;
_uiThread.UnhandledException +=
HandlerOnUnhandledExceptionHandled;
var caughtCorrectException = false;
try
{
// Since both Invoke and InvokeAsync can throw exception, there is no need to pass them to the UnhandledException.
_uiThread.Invoke(ThrowAnException, DispatcherPriority.Normal, TestContext.Current.CancellationToken);
_uiThread.RunJobs(null, TestContext.Current.CancellationToken);
}
catch (Exception e)
{
// should be no exception here.
caughtCorrectException = e.Message == ExpectedExceptionText;
}
finally
{
Verification(caughtCorrectException, 0, 0);
}
}
[Fact]
public void InvokeAsyncMethodDoesntTriggerUnhandledException()
{
_uiThread.UnhandledExceptionFilter +=
HandlerOnUnhandledExceptionFilterRequestCatch;
_uiThread.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 = _uiThread.InvokeAsync(ThrowAnException, DispatcherPriority.Normal, TestContext.Current.CancellationToken);
op.Wait();
_uiThread.RunJobs(null, TestContext.Current.CancellationToken);
}
catch (Exception e)
{
// should be no exception here.
caughtCorrectException = e.Message == ExpectedExceptionText;
}
finally
{
Verification(caughtCorrectException, 0, 0);
}
}
[Fact]
public void CanRethrowExceptionWithUnhandledException()
{
_uiThread.UnhandledExceptionFilter +=
HandlerOnUnhandledExceptionFilterRequestCatch;
_uiThread.UnhandledException +=
HandlerOnUnhandledExceptionNotHandled;
var caughtCorrectException = false;
try
{
_uiThread.Post(ThrowAnException, DispatcherPriority.Normal);
_uiThread.RunJobs(null, TestContext.Current.CancellationToken);
}
catch (Exception e)
{
caughtCorrectException = e.Message == ExpectedExceptionText;
}
finally
{
Verification(caughtCorrectException, 1, 1);
}
}
[Fact]
public void MultipleUnhandledExceptionFilterCannotResetRequestCatchFlag()
{
_uiThread.UnhandledExceptionFilter +=
HandlerOnUnhandledExceptionFilterNotRequestCatch;
_uiThread.UnhandledExceptionFilter +=
HandlerOnUnhandledExceptionFilterRequestCatch;
_uiThread.UnhandledException +=
HandlerOnUnhandledExceptionNotHandled;
_uiThread.UnhandledException +=
HandlerOnUnhandledExceptionHandled;
var caughtCorrectException = false;
try
{
_uiThread.Post(ThrowAnException, DispatcherPriority.Normal);
_uiThread.RunJobs(null, TestContext.Current.CancellationToken);
}
catch (Exception e)
{
caughtCorrectException = e.Message == ExpectedExceptionText;
}
finally
{
Verification(caughtCorrectException, 0, 2);
}
}
[Fact]
public void MultipleUnhandledExceptionCannotResetHandleFlag()
{
_uiThread.UnhandledExceptionFilter +=
HandlerOnUnhandledExceptionFilterRequestCatch;
_uiThread.UnhandledException +=
HandlerOnUnhandledExceptionHandled;
_uiThread.UnhandledException +=
HandlerOnUnhandledExceptionNotHandled;
var caughtCorrectException = true;
try
{
_uiThread.Post(ThrowAnException, DispatcherPriority.Normal);
_uiThread.RunJobs(null, TestContext.Current.CancellationToken);
}
catch (Exception)
{
// should be no exception here.
caughtCorrectException = false;
}
finally
{
Verification(caughtCorrectException, 1, 1);
}
}
[Fact]
public void CanPushFrameAndShutdownDispatcherFromUnhandledException()
{
_uiThread.UnhandledExceptionFilter +=
HandlerOnUnhandledExceptionFilterNotRequestCatchPushFrame;
_uiThread.UnhandledException +=
HandlerOnUnhandledExceptionHandledPushFrame;
var caughtCorrectException = false;
try
{
_uiThread.Post(ThrowAnException, DispatcherPriority.Normal);
_uiThread.RunJobs(null, TestContext.Current.CancellationToken);
}
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);
}
}