@ -1,43 +1,76 @@
using System ;
using System ;
using System.Diagnostics.CodeAnalysis ;
using System.Threading ;
using System.Threading ;
using System.Threading.Tasks ;
using System.Threading.Tasks ;
using Avalonia.Controls.Platform ;
using Avalonia.Controls.Platform ;
using Avalonia.Threading ;
using Avalonia.Threading ;
using Avalonia.UnitTests ;
using Xunit ;
using Xunit ;
namespace Avalonia.Base.UnitTests ;
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
// 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
public partial class DispatcherTests : ScopedTestBase
{
{
private const string ExpectedExceptionText = "Exception thrown inside Dispatcher.Invoke / Dispatcher.BeginInvoke." ;
private const string ExpectedExceptionText = "Exception thrown inside Dispatcher.Invoke / Dispatcher.BeginInvoke." ;
private int _ numberOfHandlerOnUnhandledEventInvoked ;
private int _ numberOfHandlerOnUnhandledEventInvoked ;
private int _ numberOfHandlerOnUnhandledEventFilterInvoked ;
private int _ numberOfHandlerOnUnhandledEventFilterInvoked ;
private Dispatcher _ uiThread ;
public DispatcherTests ( )
public DispatcherTests ( )
{
{
_ numberOfHandlerOnUnhandledEventInvoked = 0 ;
_ numberOfHandlerOnUnhandledEventInvoked = 0 ;
_ numberOfHandlerOnUnhandledEventFilterInvoked = 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]
[Fact]
public void DispatcherHandlesExceptionWithPost ( )
[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 impl = new ManagedDispatcherImpl ( null ) ;
var dispatcher = Dispatcher . CurrentDispatcher ;
var disp = new Dispatcher ( impl ) ;
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 handled = false ;
var executed = false ;
var executed = false ;
disp . UnhandledException + = ( sender , args ) = >
_ uiThread . UnhandledException + = ( sender , args ) = >
{
{
handled = true ;
handled = true ;
args . Handled = true ;
args . Handled = true ;
} ;
} ;
disp . Post ( ( ) = > ThrowAnException ( ) ) ;
_ uiThread . Post ( ( ) = > ThrowAnException ( ) ) ;
disp . Post ( ( ) = > executed = true ) ;
_ uiThread . Post ( ( ) = > executed = true ) ;
disp . RunJobs ( null , TestContext . Current . CancellationToken ) ;
_ uiThread . RunJobs ( null , TestContext . Current . CancellationToken ) ;
Assert . True ( handled ) ;
Assert . True ( handled ) ;
Assert . True ( executed ) ;
Assert . True ( executed ) ;
@ -46,14 +79,11 @@ public partial class DispatcherTests
[Fact]
[Fact]
public void SyncContextExceptionCanBeHandledWithPost ( )
public void SyncContextExceptionCanBeHandledWithPost ( )
{
{
var impl = new ManagedDispatcherImpl ( null ) ;
var syncContext = _ uiThread . GetContextWithPriority ( DispatcherPriority . Background ) ;
var disp = new Dispatcher ( impl ) ;
var syncContext = disp . GetContextWithPriority ( DispatcherPriority . Background ) ;
var handled = false ;
var handled = false ;
var executed = false ;
var executed = false ;
disp . UnhandledException + = ( sender , args ) = >
_ uiThread . UnhandledException + = ( sender , args ) = >
{
{
handled = true ;
handled = true ;
args . Handled = true ;
args . Handled = true ;
@ -62,7 +92,7 @@ public partial class DispatcherTests
syncContext . Post ( _ = > ThrowAnException ( ) , null ) ;
syncContext . Post ( _ = > ThrowAnException ( ) , null ) ;
syncContext . Post ( _ = > executed = true , null ) ;
syncContext . Post ( _ = > executed = true , null ) ;
disp . RunJobs ( null , TestContext . Current . CancellationToken ) ;
_ uiThread . RunJobs ( null , TestContext . Current . CancellationToken ) ;
Assert . True ( handled ) ;
Assert . True ( handled ) ;
Assert . True ( executed ) ;
Assert . True ( executed ) ;
@ -71,24 +101,22 @@ public partial class DispatcherTests
[Fact]
[Fact]
public void CanRemoveDispatcherExceptionHandler ( )
public void CanRemoveDispatcherExceptionHandler ( )
{
{
var impl = new ManagedDispatcherImpl ( null ) ;
var dispatcher = new Dispatcher ( impl ) ;
var caughtCorrectException = false ;
var caughtCorrectException = false ;
dispatcher . UnhandledExceptionFilter + =
_ uiThread . UnhandledExceptionFilter + =
HandlerOnUnhandledExceptionFilterRequestCatch ;
HandlerOnUnhandledExceptionFilterRequestCatch ;
dispatcher . UnhandledException + =
_ uiThread . UnhandledException + =
HandlerOnUnhandledExceptionNotHandled ;
HandlerOnUnhandledExceptionNotHandled ;
dispatcher . UnhandledExceptionFilter - =
_ uiThread . UnhandledExceptionFilter - =
HandlerOnUnhandledExceptionFilterRequestCatch ;
HandlerOnUnhandledExceptionFilterRequestCatch ;
dispatcher . UnhandledException - =
_ uiThread . UnhandledException - =
HandlerOnUnhandledExceptionNotHandled ;
HandlerOnUnhandledExceptionNotHandled ;
try
try
{
{
dispatcher . Post ( ThrowAnException , DispatcherPriority . Normal ) ;
_ uiThread . Post ( ThrowAnException , DispatcherPriority . Normal ) ;
dispatcher . RunJobs ( null , TestContext . Current . CancellationToken ) ;
_ uiThread . RunJobs ( null , TestContext . Current . CancellationToken ) ;
}
}
catch ( Exception e )
catch ( Exception e )
{
{
@ -103,19 +131,16 @@ public partial class DispatcherTests
[Fact]
[Fact]
public void CanHandleExceptionWithUnhandledException ( )
public void CanHandleExceptionWithUnhandledException ( )
{
{
var impl = new ManagedDispatcherImpl ( null ) ;
_ uiThread . UnhandledExceptionFilter + =
var dispatcher = new Dispatcher ( impl ) ;
dispatcher . UnhandledExceptionFilter + =
HandlerOnUnhandledExceptionFilterRequestCatch ;
HandlerOnUnhandledExceptionFilterRequestCatch ;
dispatcher . UnhandledException + =
_ uiThread . UnhandledException + =
HandlerOnUnhandledExceptionHandled ;
HandlerOnUnhandledExceptionHandled ;
var caughtCorrectException = true ;
var caughtCorrectException = true ;
try
try
{
{
dispatcher . Post ( ThrowAnException , DispatcherPriority . Normal ) ;
_ uiThread . Post ( ThrowAnException , DispatcherPriority . Normal ) ;
dispatcher . RunJobs ( null , TestContext . Current . CancellationToken ) ;
_ uiThread . RunJobs ( null , TestContext . Current . CancellationToken ) ;
}
}
catch ( Exception )
catch ( Exception )
{
{
@ -131,20 +156,17 @@ public partial class DispatcherTests
[Fact]
[Fact]
public void InvokeMethodDoesntTriggerUnhandledException ( )
public void InvokeMethodDoesntTriggerUnhandledException ( )
{
{
var impl = new ManagedDispatcherImpl ( null ) ;
_ uiThread . UnhandledExceptionFilter + =
var dispatcher = new Dispatcher ( impl ) ;
dispatcher . UnhandledExceptionFilter + =
HandlerOnUnhandledExceptionFilterRequestCatch ;
HandlerOnUnhandledExceptionFilterRequestCatch ;
dispatcher . UnhandledException + =
_ uiThread . UnhandledException + =
HandlerOnUnhandledExceptionHandled ;
HandlerOnUnhandledExceptionHandled ;
var caughtCorrectException = false ;
var caughtCorrectException = false ;
try
try
{
{
// Since both Invoke and InvokeAsync can throw exception, there is no need to pass them to the UnhandledException.
// Since both Invoke and InvokeAsync can throw exception, there is no need to pass them to the UnhandledException.
dispatcher . Invoke ( ThrowAnException , DispatcherPriority . Normal , TestContext . Current . CancellationToken ) ;
_ uiThread . Invoke ( ThrowAnException , DispatcherPriority . Normal , TestContext . Current . CancellationToken ) ;
dispatcher . RunJobs ( null , TestContext . Current . CancellationToken ) ;
_ uiThread . RunJobs ( null , TestContext . Current . CancellationToken ) ;
}
}
catch ( Exception e )
catch ( Exception e )
{
{
@ -160,21 +182,18 @@ public partial class DispatcherTests
[Fact]
[Fact]
public void InvokeAsyncMethodDoesntTriggerUnhandledException ( )
public void InvokeAsyncMethodDoesntTriggerUnhandledException ( )
{
{
var impl = new ManagedDispatcherImpl ( null ) ;
_ uiThread . UnhandledExceptionFilter + =
var dispatcher = new Dispatcher ( impl ) ;
dispatcher . UnhandledExceptionFilter + =
HandlerOnUnhandledExceptionFilterRequestCatch ;
HandlerOnUnhandledExceptionFilterRequestCatch ;
dispatcher . UnhandledException + =
_ uiThread . UnhandledException + =
HandlerOnUnhandledExceptionHandled ;
HandlerOnUnhandledExceptionHandled ;
var caughtCorrectException = false ;
var caughtCorrectException = false ;
try
try
{
{
// Since both Invoke and InvokeAsync can throw exception, there is no need to pass them to the UnhandledException.
// 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 , TestContext . Current . CancellationToken ) ;
var op = _ uiThread . InvokeAsync ( ThrowAnException , DispatcherPriority . Normal , TestContext . Current . CancellationToken ) ;
op . Wait ( ) ;
op . Wait ( ) ;
dispatcher . RunJobs ( null , TestContext . Current . CancellationToken ) ;
_ uiThread . RunJobs ( null , TestContext . Current . CancellationToken ) ;
}
}
catch ( Exception e )
catch ( Exception e )
{
{
@ -190,19 +209,16 @@ public partial class DispatcherTests
[Fact]
[Fact]
public void CanRethrowExceptionWithUnhandledException ( )
public void CanRethrowExceptionWithUnhandledException ( )
{
{
var impl = new ManagedDispatcherImpl ( null ) ;
_ uiThread . UnhandledExceptionFilter + =
var dispatcher = new Dispatcher ( impl ) ;
dispatcher . UnhandledExceptionFilter + =
HandlerOnUnhandledExceptionFilterRequestCatch ;
HandlerOnUnhandledExceptionFilterRequestCatch ;
dispatcher . UnhandledException + =
_ uiThread . UnhandledException + =
HandlerOnUnhandledExceptionNotHandled ;
HandlerOnUnhandledExceptionNotHandled ;
var caughtCorrectException = false ;
var caughtCorrectException = false ;
try
try
{
{
dispatcher . Post ( ThrowAnException , DispatcherPriority . Normal ) ;
_ uiThread . Post ( ThrowAnException , DispatcherPriority . Normal ) ;
dispatcher . RunJobs ( null , TestContext . Current . CancellationToken ) ;
_ uiThread . RunJobs ( null , TestContext . Current . CancellationToken ) ;
}
}
catch ( Exception e )
catch ( Exception e )
{
{
@ -217,23 +233,20 @@ public partial class DispatcherTests
[Fact]
[Fact]
public void MultipleUnhandledExceptionFilterCannotResetRequestCatchFlag ( )
public void MultipleUnhandledExceptionFilterCannotResetRequestCatchFlag ( )
{
{
var impl = new ManagedDispatcherImpl ( null ) ;
_ uiThread . UnhandledExceptionFilter + =
var dispatcher = new Dispatcher ( impl ) ;
dispatcher . UnhandledExceptionFilter + =
HandlerOnUnhandledExceptionFilterNotRequestCatch ;
HandlerOnUnhandledExceptionFilterNotRequestCatch ;
dispatcher . UnhandledExceptionFilter + =
_ uiThread . UnhandledExceptionFilter + =
HandlerOnUnhandledExceptionFilterRequestCatch ;
HandlerOnUnhandledExceptionFilterRequestCatch ;
dispatcher . UnhandledException + =
_ uiThread . UnhandledException + =
HandlerOnUnhandledExceptionNotHandled ;
HandlerOnUnhandledExceptionNotHandled ;
dispatcher . UnhandledException + =
_ uiThread . UnhandledException + =
HandlerOnUnhandledExceptionHandled ;
HandlerOnUnhandledExceptionHandled ;
var caughtCorrectException = false ;
var caughtCorrectException = false ;
try
try
{
{
dispatcher . Post ( ThrowAnException , DispatcherPriority . Normal ) ;
_ uiThread . Post ( ThrowAnException , DispatcherPriority . Normal ) ;
dispatcher . RunJobs ( null , TestContext . Current . CancellationToken ) ;
_ uiThread . RunJobs ( null , TestContext . Current . CancellationToken ) ;
}
}
catch ( Exception e )
catch ( Exception e )
{
{
@ -248,22 +261,19 @@ public partial class DispatcherTests
[Fact]
[Fact]
public void MultipleUnhandledExceptionCannotResetHandleFlag ( )
public void MultipleUnhandledExceptionCannotResetHandleFlag ( )
{
{
var impl = new ManagedDispatcherImpl ( null ) ;
_ uiThread . UnhandledExceptionFilter + =
var dispatcher = new Dispatcher ( impl ) ;
dispatcher . UnhandledExceptionFilter + =
HandlerOnUnhandledExceptionFilterRequestCatch ;
HandlerOnUnhandledExceptionFilterRequestCatch ;
dispatcher . UnhandledException + =
_ uiThread . UnhandledException + =
HandlerOnUnhandledExceptionHandled ;
HandlerOnUnhandledExceptionHandled ;
dispatcher . UnhandledException + =
_ uiThread . UnhandledException + =
HandlerOnUnhandledExceptionNotHandled ;
HandlerOnUnhandledExceptionNotHandled ;
var caughtCorrectException = true ;
var caughtCorrectException = true ;
try
try
{
{
dispatcher . Post ( ThrowAnException , DispatcherPriority . Normal ) ;
_ uiThread . Post ( ThrowAnException , DispatcherPriority . Normal ) ;
dispatcher . RunJobs ( null , TestContext . Current . CancellationToken ) ;
_ uiThread . RunJobs ( null , TestContext . Current . CancellationToken ) ;
}
}
catch ( Exception )
catch ( Exception )
{
{
@ -279,19 +289,16 @@ public partial class DispatcherTests
[Fact]
[Fact]
public void CanPushFrameAndShutdownDispatcherFromUnhandledException ( )
public void CanPushFrameAndShutdownDispatcherFromUnhandledException ( )
{
{
var impl = new ManagedDispatcherImpl ( null ) ;
_ uiThread . UnhandledExceptionFilter + =
var dispatcher = new Dispatcher ( impl ) ;
dispatcher . UnhandledExceptionFilter + =
HandlerOnUnhandledExceptionFilterNotRequestCatchPushFrame ;
HandlerOnUnhandledExceptionFilterNotRequestCatchPushFrame ;
dispatcher . UnhandledException + =
_ uiThread . UnhandledException + =
HandlerOnUnhandledExceptionHandledPushFrame ;
HandlerOnUnhandledExceptionHandledPushFrame ;
var caughtCorrectException = false ;
var caughtCorrectException = false ;
try
try
{
{
dispatcher . Post ( ThrowAnException , DispatcherPriority . Normal ) ;
_ uiThread . Post ( ThrowAnException , DispatcherPriority . Normal ) ;
dispatcher . RunJobs ( null , TestContext . Current . CancellationToken ) ;
_ uiThread . RunJobs ( null , TestContext . Current . CancellationToken ) ;
}
}
catch ( Exception e )
catch ( Exception e )
{
{