elsa-core/test/unit/Elsa.Common.UnitTests/ExceptionExtensionsTests.cs
Sipke Schoorstra d14ba421e0
fix(runtime): close drain cancel-dispose race (#8123)
* fix(runtime): close cancel-dispose race

* fix(runtime): recover disposed active checkpoints

* fix(runtime): guard disposed checkpoint recovery

* test(runtime): share suspended instance fixture

* fix(runtime): serialize cycle CTS lifecycle

* fix(runtime): serialize cycle CTS cleanup

* test(runtime): update lifecycle comment

* fix(runtime): avoid CTS callback disposal deadlock

* fix(runtime): preserve cancellation recovery races

* fix(runtime): preserve cancel state after callback errors

* test(runtime): clean up blocked drain callbacks

* fix(runtime): guard linked token cancellation cleanup

* fix(runtime): preserve fatal cancellation callback failures

* fix(common): classify fatal exceptions inside aggregates

* fix(runtime): await deferred recovery after drain cancellation

* fix(runtime): clarify deferred recovery timeout

* test(runtime): await cancellation cleanup before disposal assertion
2026-09-13 21:28:33 +02:00

80 lines
3 KiB
C#

using Elsa.Common;
using System.Reflection;
using System.Runtime.InteropServices;
using Xunit;
namespace Elsa.Common.UnitTests;
public class ExceptionExtensionsTests
{
[Fact(DisplayName = "Null is not fatal")]
public void NullNotFatal() => Assert.False(((Exception?)null).IsFatal());
[Theory(DisplayName = "Process-fatal exceptions are classified fatal")]
[InlineData(typeof(StackOverflowException))]
[InlineData(typeof(AccessViolationException))]
[InlineData(typeof(SEHException))]
[InlineData(typeof(ThreadAbortException))]
public void FatalExceptions(Type exceptionType)
{
#pragma warning disable SYSLIB0050 // FormatterServices is needed to instantiate fatal exception types without throwing them.
var ex = (Exception)System.Runtime.Serialization.FormatterServices.GetUninitializedObject(exceptionType);
#pragma warning restore SYSLIB0050
Assert.True(ex.IsFatal());
}
[Fact(DisplayName = "OutOfMemoryException is fatal")]
public void OomFatal() => Assert.True(new OutOfMemoryException().IsFatal());
[Fact(DisplayName = "InsufficientMemoryException (recoverable subclass of OOM) is NOT fatal")]
public void InsufficientMemoryNotFatal() => Assert.False(new InsufficientMemoryException().IsFatal());
[Theory(DisplayName = "Common recoverable exceptions are NOT fatal")]
[InlineData(typeof(InvalidOperationException))]
[InlineData(typeof(ArgumentException))]
[InlineData(typeof(TimeoutException))]
[InlineData(typeof(NullReferenceException))]
[InlineData(typeof(IOException))]
public void RecoverableExceptions(Type exceptionType)
{
var ex = (Exception)Activator.CreateInstance(exceptionType)!;
Assert.False(ex.IsFatal());
}
[Fact(DisplayName = "TypeInitializationException wrapping a fatal cause is itself fatal")]
public void WrappedFatalIsFatal()
{
var inner = new StackOverflowException();
var outer = new TypeInitializationException("X", inner);
Assert.True(outer.IsFatal());
}
[Fact(DisplayName = "AggregateException containing a fatal cause is fatal")]
public void AggregateWrappingFatalIsFatal()
{
var outer = new AggregateException(
new InvalidOperationException("recoverable"),
new AggregateException(new OutOfMemoryException("fatal")));
Assert.True(outer.IsFatal());
}
[Fact(DisplayName = "AggregateException containing only recoverable causes is not fatal")]
public void AggregateWrappingRecoverableIsNotFatal()
{
var outer = new AggregateException(
new InvalidOperationException("recoverable"),
new TimeoutException("recoverable"));
Assert.False(outer.IsFatal());
}
[Fact(DisplayName = "TargetInvocationException wrapping a recoverable cause is NOT fatal")]
public void WrappedRecoverableIsNotFatal()
{
var inner = new InvalidOperationException("boom");
var outer = new TargetInvocationException(inner);
Assert.False(outer.IsFatal());
}
}