From 59414c944efec205884feadb58bbefa81b2ddf30 Mon Sep 17 00:00:00 2001 From: Neil Rees <282424+neilrees@users.noreply.github.com> Date: Sun, 23 Jun 2024 23:02:07 +0100 Subject: [PATCH] Capture stacktrace of exceptions thrown from async specifications (#522) * Capture stacktrace of exceptions thrown from async specifications * Update spec projects target to net8.0 as netcoreapp3.1 is no longer available --- .../Machine.Specifications.Core.Specs.csproj | 2 +- .../Runner/AsyncDelegateRunnerSpecs.cs | 6 ++++++ .../Runner/Impl/AsyncSynchronizationContext.cs | 11 ++++++----- .../Runner/Impl/DelegateRunner.cs | 7 ++----- src/Machine.Specifications.Fixtures/RandomFixture.cs | 4 ++++ ...Machine.Specifications.Runner.Utility.Specs.csproj | 2 +- .../Machine.Specifications.Should.Specs.csproj | 2 +- 7 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/Machine.Specifications.Core.Specs/Machine.Specifications.Core.Specs.csproj b/src/Machine.Specifications.Core.Specs/Machine.Specifications.Core.Specs.csproj index a8b88ae5..e0c4d44b 100644 --- a/src/Machine.Specifications.Core.Specs/Machine.Specifications.Core.Specs.csproj +++ b/src/Machine.Specifications.Core.Specs/Machine.Specifications.Core.Specs.csproj @@ -1,7 +1,7 @@ - net472;netcoreapp3.1 + net472;net8.0 false diff --git a/src/Machine.Specifications.Core.Specs/Runner/AsyncDelegateRunnerSpecs.cs b/src/Machine.Specifications.Core.Specs/Runner/AsyncDelegateRunnerSpecs.cs index 7796c373..ec76a5ef 100644 --- a/src/Machine.Specifications.Core.Specs/Runner/AsyncDelegateRunnerSpecs.cs +++ b/src/Machine.Specifications.Core.Specs/Runner/AsyncDelegateRunnerSpecs.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Example.Exceptions; using Machine.Specifications.Factories; using Machine.Specifications.Runner; using Machine.Specifications.Runner.Impl; @@ -70,6 +71,11 @@ class when_running_async_specifications_with_exceptions : RunnerSpecs It should_have_failures = () => results.ShouldEachConformTo(x => !x.Passed); + + It should_have_exception_details = () => + results.ShouldEachConformTo(r => r.Exception.TypeName == nameof(InvalidOperationException) && + r.Exception.Message == "something went wrong" && + r.Exception.StackTrace.Contains(typeof(AsyncSpecificationsWithExceptions).FullName)); } [Subject("Async Delegate Runner")] diff --git a/src/Machine.Specifications.Core/Runner/Impl/AsyncSynchronizationContext.cs b/src/Machine.Specifications.Core/Runner/Impl/AsyncSynchronizationContext.cs index 9fed366f..45d87567 100644 --- a/src/Machine.Specifications.Core/Runner/Impl/AsyncSynchronizationContext.cs +++ b/src/Machine.Specifications.Core/Runner/Impl/AsyncSynchronizationContext.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; @@ -12,7 +13,7 @@ internal class AsyncSynchronizationContext : SynchronizationContext private int callCount; - private Exception exception; + private ExceptionDispatchInfo exceptionDispatchInfo; public AsyncSynchronizationContext(SynchronizationContext inner) { @@ -27,7 +28,7 @@ private void Execute(SendOrPostCallback callback, object state) } catch (Exception ex) { - exception = ex; + exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex); } finally { @@ -88,15 +89,15 @@ public override void Send(SendOrPostCallback d, object state) } catch (Exception ex) { - exception = ex; + exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex);; } } - public async Task WaitAsync() + public async Task WaitAsync() { await events.WaitAsync(); - return exception; + return exceptionDispatchInfo; } } } diff --git a/src/Machine.Specifications.Core/Runner/Impl/DelegateRunner.cs b/src/Machine.Specifications.Core/Runner/Impl/DelegateRunner.cs index d55121e3..4eaf6a6f 100644 --- a/src/Machine.Specifications.Core/Runner/Impl/DelegateRunner.cs +++ b/src/Machine.Specifications.Core/Runner/Impl/DelegateRunner.cs @@ -36,12 +36,9 @@ public void Execute() { target.DynamicInvoke(args); - var exception = context.WaitAsync().Result; + var exceptionDispatchInfo = context.WaitAsync().Result; - if (exception != null) - { - throw exception; - } + exceptionDispatchInfo?.Throw(); } finally { diff --git a/src/Machine.Specifications.Fixtures/RandomFixture.cs b/src/Machine.Specifications.Fixtures/RandomFixture.cs index 9540c97e..1545c2aa 100644 --- a/src/Machine.Specifications.Fixtures/RandomFixture.cs +++ b/src/Machine.Specifications.Fixtures/RandomFixture.cs @@ -1139,6 +1139,10 @@ public static ValueTask Test() Cleanup after = async () => cleanup_value = await Test(); } +} + +namespace Example.Exceptions +{ public class AsyncSpecificationsWithExceptions { diff --git a/src/Machine.Specifications.Runner.Utility.Specs/Machine.Specifications.Runner.Utility.Specs.csproj b/src/Machine.Specifications.Runner.Utility.Specs/Machine.Specifications.Runner.Utility.Specs.csproj index 4b036732..95aee073 100644 --- a/src/Machine.Specifications.Runner.Utility.Specs/Machine.Specifications.Runner.Utility.Specs.csproj +++ b/src/Machine.Specifications.Runner.Utility.Specs/Machine.Specifications.Runner.Utility.Specs.csproj @@ -1,7 +1,7 @@ - net472;netcoreapp3.1 + net472;net8.0 false diff --git a/src/Machine.Specifications.Should.Specs/Machine.Specifications.Should.Specs.csproj b/src/Machine.Specifications.Should.Specs/Machine.Specifications.Should.Specs.csproj index 03d5f0cf..8dc0dfb7 100644 --- a/src/Machine.Specifications.Should.Specs/Machine.Specifications.Should.Specs.csproj +++ b/src/Machine.Specifications.Should.Specs/Machine.Specifications.Should.Specs.csproj @@ -1,7 +1,7 @@ - net472;netcoreapp3.1 + net472;net8.0 false