diff --git a/Internal/Xcepto.Internal.Http/Builders/HttpStateBuilder.T.cs b/Internal/Xcepto.Internal.Http/Builders/HttpStateBuilder.T.cs index f46cb28..6a48c34 100644 --- a/Internal/Xcepto.Internal.Http/Builders/HttpStateBuilder.T.cs +++ b/Internal/Xcepto.Internal.Http/Builders/HttpStateBuilder.T.cs @@ -27,7 +27,24 @@ public abstract class HttpStateBuilderIdentity : AbstractStateBuilderI protected HttpStateBuilderIdentity(IStateMachineBuilder stateMachineBuilder, IStateBuilderIdentity stateBuilderIdentity) : base(stateMachineBuilder, stateBuilderIdentity) { } protected HttpStateBuilderIdentity(IStateMachineBuilder stateMachineBuilder) : base(stateMachineBuilder) { } - + + protected override string DefaultName + { + get + { + string url; + try + { + url = Url().ToString(); + } + catch (Exception) + { + url = "promised url"; + } + return $"Http {MethodVerb} request to {url}"; + } + } + /// /// Based on Http Verb idempotency /// diff --git a/Samples/Compartments/Compartments.Tests/Test/AdapterCompartmentAccessTests.cs b/Samples/Compartments/Compartments.Tests/Test/AdapterCompartmentAccessTests.cs index 94a86a3..b9c2e52 100644 --- a/Samples/Compartments/Compartments.Tests/Test/AdapterCompartmentAccessTests.cs +++ b/Samples/Compartments/Compartments.Tests/Test/AdapterCompartmentAccessTests.cs @@ -51,7 +51,7 @@ await XceptoTest.Given(new CompartmentalizationScenario(), TimeoutConfig.FromSec [Test] public void CompartmentRepositoryDoesNotExist() { - Assert.CatchAsync(async () => + Assert.That(async () => { await XceptoTest.Given(new SharedSyncScenario(), TimeoutConfig.FromSeconds(3), builder => { @@ -59,7 +59,7 @@ await XceptoTest.Given(new SharedSyncScenario(), TimeoutConfig.FromSeconds(3), b compartmentAccessAdapter.JoinedCompartmentExpectation("service1", "service2", (_, _) => true); }); - }); + }, Throws.InnerException.TypeOf()); } diff --git a/Samples/Compartments/Compartments.Tests/Test/DifferentServiceTests.cs b/Samples/Compartments/Compartments.Tests/Test/DifferentServiceTests.cs index 08c61ab..417b848 100644 --- a/Samples/Compartments/Compartments.Tests/Test/DifferentServiceTests.cs +++ b/Samples/Compartments/Compartments.Tests/Test/DifferentServiceTests.cs @@ -5,6 +5,7 @@ using Xcepto.Adapters; using Xcepto.Builder; using Xcepto.Config; +using Xcepto.Exceptions; namespace Compartments.Tests.Test; @@ -33,7 +34,7 @@ public async Task ServicesDontAffectEachOther() [Test] public void ServicesAffectEachOther() { - Assert.CatchAsync(async () => + Assert.CatchAsync(async () => { await XceptoTest.Given(new SharedSyncScenario(), TimeoutConfig.FromSeconds(3), Definition); }); diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Adapters/FailingCleanupAdapter.cs b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Adapters/FailingCleanupAdapter.cs new file mode 100644 index 0000000..642e3b8 --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Adapters/FailingCleanupAdapter.cs @@ -0,0 +1,11 @@ +using Xcepto.Adapters; + +namespace Samples.ExceptionDetail.Tests.Adapters; + +public class FailingCleanupAdapter: XceptoAdapter +{ + protected override Task Cleanup(IServiceProvider serviceProvider) + { + throw new Exception(); + } +} \ No newline at end of file diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Adapters/FailingConstructionAdapter.cs b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Adapters/FailingConstructionAdapter.cs new file mode 100644 index 0000000..5de1b6a --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Adapters/FailingConstructionAdapter.cs @@ -0,0 +1,11 @@ +using Xcepto.Adapters; + +namespace Samples.ExceptionDetail.Tests.Adapters; + +public class FailingConstructionAdapter: XceptoAdapter +{ + public FailingConstructionAdapter() + { + throw new Exception(); + } +} \ No newline at end of file diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Adapters/FailingInitAdapter.cs b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Adapters/FailingInitAdapter.cs new file mode 100644 index 0000000..8076938 --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Adapters/FailingInitAdapter.cs @@ -0,0 +1,11 @@ +using Xcepto.Adapters; + +namespace Samples.ExceptionDetail.Tests.Adapters; + +public class FailingInitAdapter: XceptoAdapter +{ + protected override Task Initialize(IServiceProvider serviceProvider) + { + throw new Exception(); + } +} \ No newline at end of file diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Samples.ExceptionDetail.Tests.csproj b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Samples.ExceptionDetail.Tests.csproj new file mode 100644 index 0000000..1c0d88c --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Samples.ExceptionDetail.Tests.csproj @@ -0,0 +1,27 @@ + + + + enable + enable + + false + true + net9.0 + + + + + + + + + + + + + + + + + + diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Scenarios/CleanScenario.cs b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Scenarios/CleanScenario.cs new file mode 100644 index 0000000..60b04e0 --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Scenarios/CleanScenario.cs @@ -0,0 +1,10 @@ +using Xcepto.Builder; +using Xcepto.Data; +using Xcepto.Scenarios; + +namespace Samples.ExceptionDetail.Tests.Scenarios; + +public class CleanScenario: XceptoScenario +{ + protected override ScenarioSetup Setup(ScenarioSetupBuilder builder) => builder.Build(); +} \ No newline at end of file diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Scenarios/FailingCleanupScenario.cs b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Scenarios/FailingCleanupScenario.cs new file mode 100644 index 0000000..f3ad17d --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Scenarios/FailingCleanupScenario.cs @@ -0,0 +1,15 @@ +using Xcepto.Builder; +using Xcepto.Data; +using Xcepto.Scenarios; + +namespace Samples.ExceptionDetail.Tests.Scenarios; + +public class FailingCleanupScenario: XceptoScenario +{ + protected override ScenarioSetup Setup(ScenarioSetupBuilder builder) => builder.Build(); + + protected override ScenarioCleanup Cleanup(ScenarioCleanupBuilder builder) + { + throw new IOException(); + } +} \ No newline at end of file diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Scenarios/FailingInitScenario.cs b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Scenarios/FailingInitScenario.cs new file mode 100644 index 0000000..def118f --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Scenarios/FailingInitScenario.cs @@ -0,0 +1,15 @@ +using Xcepto.Builder; +using Xcepto.Data; +using Xcepto.Scenarios; + +namespace Samples.ExceptionDetail.Tests.Scenarios; + +public class FailingInitScenario: XceptoScenario +{ + protected override ScenarioSetup Setup(ScenarioSetupBuilder builder) => builder.Build(); + + protected override ScenarioInitialization Initialize(ScenarioInitializationBuilder builder) + { + throw new Exception(); + } +} \ No newline at end of file diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Scenarios/FailingSetupScenario.cs b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Scenarios/FailingSetupScenario.cs new file mode 100644 index 0000000..559febc --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Scenarios/FailingSetupScenario.cs @@ -0,0 +1,13 @@ +using Xcepto.Builder; +using Xcepto.Data; +using Xcepto.Scenarios; + +namespace Samples.ExceptionDetail.Tests.Scenarios; + +public class FailingSetupScenario: XceptoScenario +{ + protected override ScenarioSetup Setup(ScenarioSetupBuilder builder) + { + throw new Exception(); + } +} \ No newline at end of file diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/FailingEnterState.cs b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/FailingEnterState.cs new file mode 100644 index 0000000..444e1a8 --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/FailingEnterState.cs @@ -0,0 +1,16 @@ +using Xcepto.States; + +namespace Samples.ExceptionDetail.Tests.States; + +public class FailingEnterState: XceptoState +{ + public FailingEnterState(string name) : base(name) { } + + public override Task EvaluateConditionsForTransition(IServiceProvider serviceProvider) => + Task.FromResult(true); + + public override Task OnEnter(IServiceProvider serviceProvider) + { + throw new Exception(); + } +} \ No newline at end of file diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/FailingInitState.cs b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/FailingInitState.cs new file mode 100644 index 0000000..6a2bec1 --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/FailingInitState.cs @@ -0,0 +1,18 @@ +using Xcepto.States; + +namespace Samples.ExceptionDetail.Tests.States; + +public class FailingInitState: XceptoState +{ + public FailingInitState(string name) : base(name) { } + + public override Task EvaluateConditionsForTransition(IServiceProvider serviceProvider) => + Task.FromResult(true); + + public override Task OnEnter(IServiceProvider serviceProvider) => Task.CompletedTask; + + public override Task Initialize(IServiceProvider serviceProvider) + { + throw new Exception(); + } +} \ No newline at end of file diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/FailingSetupState.cs b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/FailingSetupState.cs new file mode 100644 index 0000000..61bf024 --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/FailingSetupState.cs @@ -0,0 +1,16 @@ +using Xcepto.States; + +namespace Samples.ExceptionDetail.Tests.States; + +public class FailingSetupState: XceptoState +{ + public FailingSetupState(string name) : base(name) + { + throw new Exception(); + } + + public override Task EvaluateConditionsForTransition(IServiceProvider serviceProvider) => + Task.FromResult(true); + + public override Task OnEnter(IServiceProvider serviceProvider) => Task.CompletedTask; +} \ No newline at end of file diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/FailingTransitionState.cs b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/FailingTransitionState.cs new file mode 100644 index 0000000..ca85e6c --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/FailingTransitionState.cs @@ -0,0 +1,15 @@ +using Xcepto.States; + +namespace Samples.ExceptionDetail.Tests.States; + +public class FailingTransitionState: XceptoState +{ + public FailingTransitionState(string name) : base(name) { } + + public override Task EvaluateConditionsForTransition(IServiceProvider serviceProvider) + { + throw new Exception(); + } + + public override Task OnEnter(IServiceProvider serviceProvider) => Task.CompletedTask; +} \ No newline at end of file diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/LongEnterState.cs b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/LongEnterState.cs new file mode 100644 index 0000000..af89b3f --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/LongEnterState.cs @@ -0,0 +1,18 @@ +using Xcepto.States; + +namespace Samples.ExceptionDetail.Tests.States; + +public class LongEnterState: XceptoState +{ + public LongEnterState(string name) : base(name) { } + + public override async Task EvaluateConditionsForTransition(IServiceProvider serviceProvider) + { + return true; + } + + public override async Task OnEnter(IServiceProvider serviceProvider) + { + await Task.Delay(TimeSpan.FromSeconds(5)); + } +} \ No newline at end of file diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/LongFailingEnterState.cs b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/LongFailingEnterState.cs new file mode 100644 index 0000000..0a7495f --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/LongFailingEnterState.cs @@ -0,0 +1,20 @@ +using Xcepto.Data; +using Xcepto.States; + +namespace Samples.ExceptionDetail.Tests.States; + +public class LongFailingEnterState: XceptoState +{ + public LongFailingEnterState(string name) : base(name) { } + + public override Task EvaluateConditionsForTransition(IServiceProvider serviceProvider) + { + return Task.FromResult(true); + } + + public override async Task OnEnter(IServiceProvider serviceProvider) + { + MostRecentFailingResult = new ConditionResult(new { }, "big problem!"); + await Task.Delay(TimeSpan.FromSeconds(5)); + } +} \ No newline at end of file diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/LongFailingTransitionState.cs b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/LongFailingTransitionState.cs new file mode 100644 index 0000000..abe731d --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/LongFailingTransitionState.cs @@ -0,0 +1,18 @@ +using Xcepto.Data; +using Xcepto.States; + +namespace Samples.ExceptionDetail.Tests.States; + +public class LongFailingTransitionState: XceptoState +{ + public LongFailingTransitionState(string name) : base(name) { } + + public override async Task EvaluateConditionsForTransition(IServiceProvider serviceProvider) + { + MostRecentFailingResult = new ConditionResult(new { }, "big problem!"); + await Task.Delay(TimeSpan.FromSeconds(5)); + return true; + } + + public override Task OnEnter(IServiceProvider serviceProvider) => Task.CompletedTask; +} \ No newline at end of file diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/LongTransitionState.cs b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/LongTransitionState.cs new file mode 100644 index 0000000..844ac22 --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/States/LongTransitionState.cs @@ -0,0 +1,16 @@ +using Xcepto.States; + +namespace Samples.ExceptionDetail.Tests.States; + +public class LongTransitionState: XceptoState +{ + public LongTransitionState(string name) : base(name) { } + + public override async Task EvaluateConditionsForTransition(IServiceProvider serviceProvider) + { + await Task.Delay(TimeSpan.FromSeconds(5)); + return true; + } + + public override Task OnEnter(IServiceProvider serviceProvider) => Task.CompletedTask; +} \ No newline at end of file diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Test/AdapterExceptionTests.cs b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Test/AdapterExceptionTests.cs new file mode 100644 index 0000000..cb10c37 --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Test/AdapterExceptionTests.cs @@ -0,0 +1,55 @@ +using Samples.ExceptionDetail.Tests.Adapters; +using Samples.ExceptionDetail.Tests.Scenarios; +using Xcepto; +using Xcepto.Exceptions; +using Xcepto.Strategies; +using Xcepto.Strategies.Execution; + +namespace Samples.ExceptionDetail.Tests.Test +{ + [TestFixtureSource(typeof(StrategyCombinations), nameof(StrategyCombinations.AllCombinations))] + public class AdapterExceptionTests + { + private XceptoTest _xceptoTest; + public AdapterExceptionTests(BaseExecutionStrategy executionStrategy) + { + _xceptoTest = new XceptoTest(executionStrategy); + } + + [Test] + public void Failing_Setup() + { + Assert.ThrowsAsync(async () => + { + await _xceptoTest.GivenWithStrategies(new CleanScenario(), builder => + { + _ = builder.RegisterAdapter(new FailingConstructionAdapter()); + }); + }); + } + + [Test] + public void Failing_Init() + { + Assert.ThrowsAsync(async () => + { + await _xceptoTest.GivenWithStrategies(new CleanScenario(), builder => + { + _ = builder.RegisterAdapter(new FailingInitAdapter()); + }); + }); + } + + [Test] + public void Failing_Cleanup() + { + Assert.ThrowsAsync(async () => + { + await _xceptoTest.GivenWithStrategies(new CleanScenario(), builder => + { + _ = builder.RegisterAdapter(new FailingCleanupAdapter()); + }); + }); + } + } +} \ No newline at end of file diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Test/PreserveInnerExceptionTests.cs b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Test/PreserveInnerExceptionTests.cs new file mode 100644 index 0000000..491e5c8 --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Test/PreserveInnerExceptionTests.cs @@ -0,0 +1,26 @@ +using Samples.ExceptionDetail.Tests.Scenarios; +using Xcepto; +using Xcepto.Strategies; +using Xcepto.Strategies.Execution; + +namespace Samples.ExceptionDetail.Tests.Test +{ + [TestFixtureSource(typeof(StrategyCombinations), nameof(StrategyCombinations.AllCombinations))] + public class PreserveInnerExceptionTests + { + private XceptoTest _xceptoTest; + public PreserveInnerExceptionTests(BaseExecutionStrategy executionStrategy) + { + _xceptoTest = new XceptoTest(executionStrategy); + } + + [Test] + public void TestCatchInner() + { + Assert.That(async () => + { + await _xceptoTest.GivenWithStrategies(new FailingCleanupScenario(), _ => { }); + }, Throws.Exception.InnerException.TypeOf()); + } + } +} \ No newline at end of file diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Test/ScenarioExceptionTests.cs b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Test/ScenarioExceptionTests.cs new file mode 100644 index 0000000..8f69f00 --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Test/ScenarioExceptionTests.cs @@ -0,0 +1,56 @@ +using Samples.ExceptionDetail.Tests.Scenarios; +using Xcepto; +using Xcepto.Exceptions; +using Xcepto.Strategies; +using Xcepto.Strategies.Execution; + +namespace Samples.ExceptionDetail.Tests.Test +{ + [TestFixtureSource(typeof(StrategyCombinations), nameof(StrategyCombinations.AllCombinations))] + public class ScenarioExceptionTests + { + private XceptoTest _xceptoTest; + public ScenarioExceptionTests(BaseExecutionStrategy executionStrategy) + { + _xceptoTest = new XceptoTest(executionStrategy); + } + + + [Test] + public void TestCatchInner() + { + Assert.That(async () => + { + await _xceptoTest.GivenWithStrategies(new FailingCleanupScenario(), _ => { }); + }, Throws.Exception.InnerException.TypeOf()); + } + + + [Test] + public void Failing_Setup() + { + Assert.ThrowsAsync(async () => + { + await _xceptoTest.GivenWithStrategies(new FailingSetupScenario(), _ => { }); + }); + } + + [Test] + public void Failing_Init() + { + Assert.ThrowsAsync(async () => + { + await _xceptoTest.GivenWithStrategies(new FailingInitScenario(), _ => { }); + }); + } + + [Test] + public void Failing_Cleanup() + { + Assert.ThrowsAsync(async () => + { + await _xceptoTest.GivenWithStrategies(new FailingCleanupScenario(), _ => { }); + }); + } + } +} \ No newline at end of file diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Test/StateExceptionTests.cs b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Test/StateExceptionTests.cs new file mode 100644 index 0000000..461da39 --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Test/StateExceptionTests.cs @@ -0,0 +1,102 @@ +using Samples.ExceptionDetail.Tests.Scenarios; +using Samples.ExceptionDetail.Tests.States; +using Xcepto; +using Xcepto.Exceptions; +using Xcepto.Interfaces; +using Xcepto.Strategies; +using Xcepto.Strategies.Execution; + +namespace Samples.ExceptionDetail.Tests.Test; + +[TestFixtureSource(typeof(StrategyCombinations), nameof(StrategyCombinations.AllCombinations))] +public class StateExceptionTests: IStateBuilderIdentity +{ + private XceptoTest _xceptoTest; + public StateExceptionTests(BaseExecutionStrategy executionStrategy) + { + _xceptoTest = new XceptoTest(executionStrategy); + } + + + [Test] + public void Direct_FailingStateSetup_Throws() + { + Assert.ThrowsAsync(async () => await _xceptoTest.GivenWithStrategies(new CleanScenario(), builder => + { + builder.AddStep(new FailingSetupState("test construction state")); + }) + ); + } + + [Test] + public void Future_FailingStateSetup_Throws() + { + Assert.ThrowsAsync(async () => await _xceptoTest.GivenWithStrategies(new CleanScenario(), builder => + { + builder.AddFutureStep(() => new FailingSetupState("test construction state"), this); + }) + ); + } + + [Test] + public void Direct_FailingStateInit_Throws() + { + Assert.ThrowsAsync(async () => await _xceptoTest.GivenWithStrategies(new CleanScenario(), builder => + { + builder.AddStep(new FailingInitState("test init state")); + }) + ); + } + + [Test] + public void Future_FailingStateInit_Throws() + { + Assert.ThrowsAsync(async () => await _xceptoTest.GivenWithStrategies(new CleanScenario(), builder => + { + builder.AddFutureStep(() => new FailingInitState("test init state"), this); + }) + ); + } + + + [Test] + public void Direct_FailingStateEnter_Throws() + { + Assert.ThrowsAsync(async () => await _xceptoTest.GivenWithStrategies(new CleanScenario(), builder => + { + builder.AddStep(new FailingEnterState("test enter state")); + }) + ); + } + + + [Test] + public void Future_FailingStateEnter_Throws() + { + Assert.ThrowsAsync(async () => await _xceptoTest.GivenWithStrategies(new CleanScenario(), builder => + { + builder.AddFutureStep(() => new FailingEnterState("test enter state"), this); + }) + ); + } + + [Test] + public void Direct_FailingStateTransition_Throws() + { + Assert.ThrowsAsync(async () => await _xceptoTest.GivenWithStrategies(new CleanScenario(), builder => + { + builder.AddStep(new FailingTransitionState("test transition state")); + }) + ); + } + + [Test] + public void Future_FailingStateTransition_Throws() + { + Assert.ThrowsAsync(async () => await _xceptoTest.GivenWithStrategies(new CleanScenario(), builder => + { + builder.AddFutureStep(() => new FailingTransitionState("test transition state"), this); + }) + ); + } +} \ No newline at end of file diff --git a/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Test/StateTimeoutExceptionTests.cs b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Test/StateTimeoutExceptionTests.cs new file mode 100644 index 0000000..a22a964 --- /dev/null +++ b/Samples/ExceptionDetail/Samples.ExceptionDetail.Tests/Test/StateTimeoutExceptionTests.cs @@ -0,0 +1,109 @@ +using Samples.ExceptionDetail.Tests.Scenarios; +using Samples.ExceptionDetail.Tests.States; +using Xcepto; +using Xcepto.Config; +using Xcepto.Exceptions; +using Xcepto.Interfaces; +using Xcepto.Strategies; +using Xcepto.Strategies.Execution; + +namespace Samples.ExceptionDetail.Tests.Test; + +[TestFixtureSource(typeof(StrategyCombinations), nameof(StrategyCombinations.AllCombinations))] +public class StateTimeoutExceptionTests: IStateBuilderIdentity +{ + private XceptoTest _xceptoTest; + public StateTimeoutExceptionTests(BaseExecutionStrategy executionStrategy) + { + _xceptoTest = new XceptoTest(executionStrategy); + } + + + [Test] + public void LongTransition_Throws_Total() + { + var timeoutConfig = TimeoutConfig.FromSeconds(1, 30); + Assert.ThrowsAsync(async () => await _xceptoTest.GivenWithStrategies(new CleanScenario(), timeoutConfig, builder => + { + builder.AddStep(new LongTransitionState("long transition state")); + }) + ); + } + + [Test] + public void LongFailingTransition_Throws_Total() + { + var timeoutConfig = TimeoutConfig.FromSeconds(1, 30); + Assert.ThrowsAsync(async () => await _xceptoTest.GivenWithStrategies(new CleanScenario(), timeoutConfig, builder => + { + builder.AddStep(new LongFailingTransitionState("long failing transition state")); + }) + ); + } + + [Test] + public void LongTransition_Throws_Test() + { + var timeoutConfig = TimeoutConfig.FromSeconds(30, 1); + Assert.ThrowsAsync(async () => await _xceptoTest.GivenWithStrategies(new CleanScenario(), timeoutConfig, builder => + { + builder.AddStep(new LongTransitionState("long transition state")); + }) + ); + } + + [Test] + public void LongFailingTransition_Throws_Test() + { + var timeoutConfig = TimeoutConfig.FromSeconds(30, 1); + Assert.ThrowsAsync(async () => await _xceptoTest.GivenWithStrategies(new CleanScenario(), timeoutConfig, builder => + { + builder.AddStep(new LongFailingTransitionState("long failing transition state")); + }) + ); + } + + [Test] + public void LongEnter_Throws_Total() + { + var timeoutConfig = TimeoutConfig.FromSeconds(1, 30); + Assert.ThrowsAsync(async () => await _xceptoTest.GivenWithStrategies(new CleanScenario(), timeoutConfig, builder => + { + builder.AddStep(new LongEnterState("long enter state")); + }) + ); + } + + [Test] + public void LongFailingEnter_Throws_Total() + { + var timeoutConfig = TimeoutConfig.FromSeconds(1, 30); + Assert.ThrowsAsync(async () => await _xceptoTest.GivenWithStrategies(new CleanScenario(), timeoutConfig, builder => + { + builder.AddStep(new LongFailingEnterState("long failing enter state")); + }) + ); + } + + [Test] + public void LongEnter_Throws_Test() + { + var timeoutConfig = TimeoutConfig.FromSeconds(30, 1); + Assert.ThrowsAsync(async () => await _xceptoTest.GivenWithStrategies(new CleanScenario(), timeoutConfig, builder => + { + builder.AddStep(new LongEnterState("long enter state")); + }) + ); + } + + [Test] + public void LongFailingEnter_Throws_Test() + { + var timeoutConfig = TimeoutConfig.FromSeconds(30, 1); + Assert.ThrowsAsync(async () => await _xceptoTest.GivenWithStrategies(new CleanScenario(), timeoutConfig, builder => + { + builder.AddStep(new LongFailingEnterState("long failing enter state")); + }) + ); + } +} \ No newline at end of file diff --git a/Samples/LoggerDisposal/LoggerDisposalTests/AdapterLoggerDisposalTests.cs b/Samples/LoggerDisposal/LoggerDisposalTests/AdapterLoggerDisposalTests.cs index 388a645..6d664f4 100644 --- a/Samples/LoggerDisposal/LoggerDisposalTests/AdapterLoggerDisposalTests.cs +++ b/Samples/LoggerDisposal/LoggerDisposalTests/AdapterLoggerDisposalTests.cs @@ -31,17 +31,17 @@ private void Run(Action builder) TimeoutConfig.FromSeconds(5), builder); if (_executionStrategy is AsyncExecutionStrategy asyncExecutionStrategy) { - Assert.ThrowsAsync(async () => + Assert.That(async () => { await asyncExecutionStrategy.RunAsync(); - }); + }, Throws.InnerException.TypeOf()); } else if (_executionStrategy is EnumeratedExecutionStrategy enumeratedExecutionStrategy) { - Assert.Throws(() => + Assert.That(() => { EnumeratedTestRunner.RunEnumerator(enumeratedExecutionStrategy.RunEnumerated()); - }); + }, Throws.InnerException.TypeOf()); } Assert.That(mockedLoggingProvider.Flushed(_message)); diff --git a/Samples/LoggerDisposal/LoggerDisposalTests/ScenarioLoggerDisposalTests.cs b/Samples/LoggerDisposal/LoggerDisposalTests/ScenarioLoggerDisposalTests.cs index dc548c2..69324c3 100644 --- a/Samples/LoggerDisposal/LoggerDisposalTests/ScenarioLoggerDisposalTests.cs +++ b/Samples/LoggerDisposal/LoggerDisposalTests/ScenarioLoggerDisposalTests.cs @@ -33,17 +33,17 @@ private void Run(Func scenarioBuilder) TimeoutConfig.FromSeconds(5), _ => {}); if (_executionStrategy is AsyncExecutionStrategy asyncExecutionStrategy) { - Assert.ThrowsAsync(async () => + Assert.That(async () => { await asyncExecutionStrategy.RunAsync(); - }); + }, Throws.InnerException.TypeOf()); } else if (_executionStrategy is EnumeratedExecutionStrategy enumeratedExecutionStrategy) { - Assert.Throws(() => + Assert.That(() => { EnumeratedTestRunner.RunEnumerator(enumeratedExecutionStrategy.RunEnumerated()); - }); + }, Throws.InnerException.TypeOf()); } Assert.That(mockedLoggingProvider.Flushed(_message)); diff --git a/Samples/LoggerDisposal/LoggerDisposalTests/StateLoggerDisposalTests.cs b/Samples/LoggerDisposal/LoggerDisposalTests/StateLoggerDisposalTests.cs index eca5eb9..98284c7 100644 --- a/Samples/LoggerDisposal/LoggerDisposalTests/StateLoggerDisposalTests.cs +++ b/Samples/LoggerDisposal/LoggerDisposalTests/StateLoggerDisposalTests.cs @@ -31,17 +31,17 @@ private void Run(Action builder) TimeoutConfig.FromSeconds(5), builder); if (_executionStrategy is AsyncExecutionStrategy asyncExecutionStrategy) { - Assert.ThrowsAsync(async () => + Assert.That(async () => { await asyncExecutionStrategy.RunAsync(); - }); + }, Throws.InnerException.TypeOf()); } else if (_executionStrategy is EnumeratedExecutionStrategy enumeratedExecutionStrategy) { - Assert.Throws(() => + Assert.That(() => { EnumeratedTestRunner.RunEnumerator(enumeratedExecutionStrategy.RunEnumerated()); - }); + }, Throws.InnerException.TypeOf()); } Assert.That(mockedLoggingProvider.Flushed(_message)); diff --git a/Xcepto.NET.sln b/Xcepto.NET.sln index bb1ea94..8dfa778 100644 --- a/Xcepto.NET.sln +++ b/Xcepto.NET.sln @@ -55,6 +55,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Internal", "Internal", "{31 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xcepto.Internal.Http", "Internal\Xcepto.Internal.Http\Xcepto.Internal.Http.csproj", "{4315D42A-16FC-438E-8439-238FAB7FC248}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ExceptionDetail", "ExceptionDetail", "{33A8048C-6324-475C-AC7F-F27B47805228}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.ExceptionDetail.Tests", "Samples\ExceptionDetail\Samples.ExceptionDetail.Tests\Samples.ExceptionDetail.Tests.csproj", "{A1F50247-A6CB-479E-98C8-1E3AC815C0E6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -128,6 +132,10 @@ Global {4315D42A-16FC-438E-8439-238FAB7FC248}.Debug|Any CPU.Build.0 = Debug|Any CPU {4315D42A-16FC-438E-8439-238FAB7FC248}.Release|Any CPU.ActiveCfg = Release|Any CPU {4315D42A-16FC-438E-8439-238FAB7FC248}.Release|Any CPU.Build.0 = Release|Any CPU + {A1F50247-A6CB-479E-98C8-1E3AC815C0E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1F50247-A6CB-479E-98C8-1E3AC815C0E6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1F50247-A6CB-479E-98C8-1E3AC815C0E6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1F50247-A6CB-479E-98C8-1E3AC815C0E6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {BABA2E33-65CD-4EB3-8FDC-3DF7B2DC2EAD} = {1C2E2691-2BCB-4F59-9222-DDA2C58EC928} @@ -149,5 +157,7 @@ Global {88DEA142-374E-4EF6-8AE5-3767B1F11E6E} = {BEF5440C-B055-4C75-9295-BC18BADD2578} {B10B888E-F849-4269-A28E-96FBC9CC0FB9} = {BEF5440C-B055-4C75-9295-BC18BADD2578} {4315D42A-16FC-438E-8439-238FAB7FC248} = {31BF92D6-8D80-4E2C-8B58-2E212C86F5A2} + {33A8048C-6324-475C-AC7F-F27B47805228} = {1C2E2691-2BCB-4F59-9222-DDA2C58EC928} + {A1F50247-A6CB-479E-98C8-1E3AC815C0E6} = {33A8048C-6324-475C-AC7F-F27B47805228} EndGlobalSection EndGlobal diff --git a/Xcepto.Rest/Builders/RestStateBuilder.T.cs b/Xcepto.Rest/Builders/RestStateBuilder.T.cs index 665c035..7d7a324 100644 --- a/Xcepto.Rest/Builders/RestStateBuilder.T.cs +++ b/Xcepto.Rest/Builders/RestStateBuilder.T.cs @@ -23,8 +23,8 @@ public abstract class RestStateBuilderIdentity: HttpStateBuilderIdenti internal RestStateBuilderIdentity(IStateMachineBuilder stateMachineBuilder) : base(stateMachineBuilder) { } internal RestStateBuilderIdentity(IStateMachineBuilder stateMachineBuilder, IStateBuilderIdentity stateBuilderIdentity) : base(stateMachineBuilder, stateBuilderIdentity) { } - - protected override string DefaultName => $"REST {MethodVerb} request state to {PathString}"; + + protected override string DefaultName => "REST " + base.DefaultName; public TBuilder WithRequestBody(Func requestBodyProducer) where TRequestBody: notnull diff --git a/Xcepto.SSR/Builders/SsrStateBuilderIdentity.cs b/Xcepto.SSR/Builders/SsrStateBuilderIdentity.cs index 62494b3..9eb3b5d 100644 --- a/Xcepto.SSR/Builders/SsrStateBuilderIdentity.cs +++ b/Xcepto.SSR/Builders/SsrStateBuilderIdentity.cs @@ -19,7 +19,7 @@ public SsrStateBuilderIdentity(IStateMachineBuilder stateMachineBuilder) : base( { } - protected override string DefaultName => $"SSR {MethodVerb} state"; + protected override string DefaultName => "SSR " + base.DefaultName; public SsrStateBuilderIdentity WithFormContent(FormUrlEncodedContent formContent) { diff --git a/Xcepto/Builder/TransitionBuilder.cs b/Xcepto/Builder/TransitionBuilder.cs index c841a13..ecdfdc5 100644 --- a/Xcepto/Builder/TransitionBuilder.cs +++ b/Xcepto/Builder/TransitionBuilder.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using Xcepto.Adapters; +using Xcepto.Exceptions; using Xcepto.Interfaces; using Xcepto.Internal; using Xcepto.States; diff --git a/Xcepto/Exceptions/AdapterCleanupException.cs b/Xcepto/Exceptions/AdapterCleanupException.cs new file mode 100644 index 0000000..a968ce2 --- /dev/null +++ b/Xcepto/Exceptions/AdapterCleanupException.cs @@ -0,0 +1,5 @@ +using System; + +namespace Xcepto.Exceptions; + +public class AdapterCleanupException(string message) : XceptoStageException(message); \ No newline at end of file diff --git a/Xcepto/Exceptions/AdapterInitException.cs b/Xcepto/Exceptions/AdapterInitException.cs new file mode 100644 index 0000000..dcb1739 --- /dev/null +++ b/Xcepto/Exceptions/AdapterInitException.cs @@ -0,0 +1,5 @@ +using System; + +namespace Xcepto.Exceptions; + +public class AdapterInitException(string message) : XceptoStageException(message); \ No newline at end of file diff --git a/Xcepto/Exceptions/ArrangeTestException.cs b/Xcepto/Exceptions/ArrangeTestException.cs new file mode 100644 index 0000000..f7e88d0 --- /dev/null +++ b/Xcepto/Exceptions/ArrangeTestException.cs @@ -0,0 +1,5 @@ +using System; + +namespace Xcepto.Exceptions; + +public class ArrangeTestException(string message) : XceptoStageException(message); \ No newline at end of file diff --git a/Xcepto/Exceptions/ScenarioCleanupException.cs b/Xcepto/Exceptions/ScenarioCleanupException.cs new file mode 100644 index 0000000..8cfd97c --- /dev/null +++ b/Xcepto/Exceptions/ScenarioCleanupException.cs @@ -0,0 +1,5 @@ +using System; + +namespace Xcepto.Exceptions; + +public class ScenarioCleanupException(string message) : XceptoStageException(message); \ No newline at end of file diff --git a/Xcepto/Exceptions/ScenarioInitException.cs b/Xcepto/Exceptions/ScenarioInitException.cs new file mode 100644 index 0000000..b93d724 --- /dev/null +++ b/Xcepto/Exceptions/ScenarioInitException.cs @@ -0,0 +1,5 @@ +using System; + +namespace Xcepto.Exceptions; + +public class ScenarioInitException(string message) : XceptoStageException(message); \ No newline at end of file diff --git a/Xcepto/Exceptions/ScenarioSetupException.cs b/Xcepto/Exceptions/ScenarioSetupException.cs new file mode 100644 index 0000000..532158f --- /dev/null +++ b/Xcepto/Exceptions/ScenarioSetupException.cs @@ -0,0 +1,5 @@ +using System; + +namespace Xcepto.Exceptions; + +public class ScenarioSetupException(string message) : XceptoStageException(message); \ No newline at end of file diff --git a/Xcepto/Exceptions/StateEnterException.cs b/Xcepto/Exceptions/StateEnterException.cs new file mode 100644 index 0000000..47bec85 --- /dev/null +++ b/Xcepto/Exceptions/StateEnterException.cs @@ -0,0 +1,3 @@ +namespace Xcepto.Exceptions; + +public class StateEnterException(string message) : XceptoStageException(message); \ No newline at end of file diff --git a/Xcepto/Exceptions/StateInitException.cs b/Xcepto/Exceptions/StateInitException.cs new file mode 100644 index 0000000..af87255 --- /dev/null +++ b/Xcepto/Exceptions/StateInitException.cs @@ -0,0 +1,3 @@ +namespace Xcepto.Exceptions; + +public class StateInitException(string message) : XceptoStageException(message); \ No newline at end of file diff --git a/Xcepto/Exceptions/StateTransitionException.cs b/Xcepto/Exceptions/StateTransitionException.cs new file mode 100644 index 0000000..559ca68 --- /dev/null +++ b/Xcepto/Exceptions/StateTransitionException.cs @@ -0,0 +1,3 @@ +namespace Xcepto.Exceptions; + +public class StateTransitionException(string message) : XceptoStageException(message); \ No newline at end of file diff --git a/Xcepto/Exceptions/TestTimeoutException.cs b/Xcepto/Exceptions/TestTimeoutException.cs index 0212403..0e35eee 100644 --- a/Xcepto/Exceptions/TestTimeoutException.cs +++ b/Xcepto/Exceptions/TestTimeoutException.cs @@ -2,12 +2,8 @@ namespace Xcepto.Exceptions; -public class TestTimeoutException : TimeoutException +public class TestTimeoutException : XceptoTimeoutException { - public TestTimeoutException(string message, AssertionException innerException) : base(message, innerException) - { - } - public TestTimeoutException(string message) : base(message) { } diff --git a/Xcepto/Exceptions/TotalTimeoutException.cs b/Xcepto/Exceptions/TotalTimeoutException.cs index 1ec7c56..1557d66 100644 --- a/Xcepto/Exceptions/TotalTimeoutException.cs +++ b/Xcepto/Exceptions/TotalTimeoutException.cs @@ -2,11 +2,8 @@ namespace Xcepto.Exceptions; -public class TotalTimeoutException : TimeoutException +public class TotalTimeoutException : XceptoTimeoutException { - public TotalTimeoutException(string message, AssertionException innerException) : base(message, innerException) - { - } public TotalTimeoutException(string message) : base(message) { } diff --git a/Xcepto/Exceptions/XceptoStageException.cs b/Xcepto/Exceptions/XceptoStageException.cs new file mode 100644 index 0000000..b813a84 --- /dev/null +++ b/Xcepto/Exceptions/XceptoStageException.cs @@ -0,0 +1,5 @@ +using System; + +namespace Xcepto.Exceptions; + +public abstract class XceptoStageException(string message): Exception(message); \ No newline at end of file diff --git a/Xcepto/Exceptions/XceptoTimeoutException.cs b/Xcepto/Exceptions/XceptoTimeoutException.cs new file mode 100644 index 0000000..61ae3fc --- /dev/null +++ b/Xcepto/Exceptions/XceptoTimeoutException.cs @@ -0,0 +1,6 @@ +namespace Xcepto.Exceptions; + +public class XceptoTimeoutException(string message) : XceptoStageException(message) +{ + +} \ No newline at end of file diff --git a/Xcepto/Internal/AcceptanceStateMachine.cs b/Xcepto/Internal/AcceptanceStateMachine.cs index cf25bf4..08545ca 100644 --- a/Xcepto/Internal/AcceptanceStateMachine.cs +++ b/Xcepto/Internal/AcceptanceStateMachine.cs @@ -1,8 +1,10 @@ using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Xcepto.Exceptions; using Xcepto.Interfaces; using Xcepto.States; +using Xcepto.Util; namespace Xcepto.Internal { @@ -38,17 +40,50 @@ internal async Task TryTransition(IServiceProvider serviceProvider) if(_currentXceptoState.NextXceptoState is null) return; // no more transitions - var allConditionsMet = await _currentXceptoState.EvaluateConditionsForTransition(serviceProvider); + var allConditionsMet = false; + + try + { + allConditionsMet = await _currentXceptoState.EvaluateConditionsForTransition(serviceProvider); + } + catch (Exception e) + { + throw new StateTransitionException($"State failed to transition: [{_currentXceptoState.Name}] ({_currentXceptoState.GetType().Name}, state #{DetermineCurrentStateNumber()})").Promote(e); + } if (allConditionsMet) { _currentXceptoState = _currentXceptoState.NextXceptoState; - await _currentXceptoState.OnEnter(serviceProvider); + try + { + await _currentXceptoState.OnEnter(serviceProvider); + } + catch (Exception e) + { + throw new StateEnterException($"State failed on enter: [{_currentXceptoState.Name}] ({_currentXceptoState.GetType().Name}, state #{DetermineCurrentStateNumber()})").Promote(e); + } var loggingProvider = serviceProvider.GetRequiredService(); loggingProvider.LogDebug($"Transitioned to: {_currentXceptoState.Name}"); } } + private int DetermineCurrentStateNumber() + { + int count = 0; + XceptoState? current = _startXceptoState; + while (true) + { + if (current is null) + throw new Exception("Failed determining the current state number"); + if(current == _currentXceptoState) + break; + current = current?.NextXceptoState; + count++; + } + + return count; + } + internal async Task Start(IServiceProvider serviceProvider) { await _currentXceptoState.OnEnter(serviceProvider); diff --git a/Xcepto/Internal/TestInstance.cs b/Xcepto/Internal/TestInstance.cs index 5e404f4..a8b7130 100644 --- a/Xcepto/Internal/TestInstance.cs +++ b/Xcepto/Internal/TestInstance.cs @@ -1,14 +1,19 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using System.Reflection; +using System.Runtime.ExceptionServices; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Xcepto.Adapters; using Xcepto.Builder; using Xcepto.Config; +using Xcepto.Exceptions; using Xcepto.Interfaces; using Xcepto.Scenarios; using Xcepto.States; +using Xcepto.Util; namespace Xcepto.Internal; @@ -35,16 +40,37 @@ internal TestInstance(TimeoutConfig timeout, XceptoScenario scenario, Transition internal async Task InitializeAsync() { - ServiceProvider = await _scenario.CallSetup(); + try + { + ServiceProvider = await _scenario.CallSetup(); + } + catch (Exception e) + { + throw new ScenarioSetupException($"Scenario setup failed: {_scenario.GetType().Name}").Promote(e); + } var loggingProvider = ServiceProvider.GetRequiredService(); loggingProvider.LogDebug("Setting up acceptance test"); loggingProvider.LogDebug(""); - await _scenario.CallInitialize(); + try + { + await _scenario.CallInitialize(); + } + catch (Exception e) + { + throw new ScenarioInitException($"Scenario initialization failed: {_scenario.GetType().Name}").Promote(e); + } loggingProvider.LogDebug("Initialized scenario successfully ✅"); loggingProvider.LogDebug(""); - StateMachine = _transitionBuilder.Build(); + try + { + StateMachine = _transitionBuilder.Build(); + } + catch (Exception e) + { + throw new ArrangeTestException("Failed arranging the test").Promote(e); + } _states = _transitionBuilder.GetStates().ToArray(); _adapters = _transitionBuilder.GetAdapters().ToArray(); if (StateMachine is null @@ -56,7 +82,14 @@ internal async Task InitializeAsync() loggingProvider.LogDebug($"State initialized: Start (1/{_states.Count()+2})"); foreach (var (state, counter) in _states.Select((state, counter) => (state, counter+2))) { - await state.Initialize(ServiceProvider); + try + { + await state.Initialize(ServiceProvider); + } + catch (Exception e) + { + throw new StateInitException($"State failed to initialize: [{state.Name}] ({state.GetType().Name}, state #{counter})").Promote(e); + } loggingProvider.LogDebug($"State initialized: {state} ({counter}/{_states.Count()+2})"); } loggingProvider.LogDebug($"State initialized: Final ({_states.Count()+2}/{_states.Count()+2})"); @@ -66,7 +99,14 @@ internal async Task InitializeAsync() loggingProvider.LogDebug("Initializing adapters:"); foreach (var (adapter, counter) in _adapters.Select((adapter, i) => (adapter, i+1))) { - await adapter.CallInitialize(ServiceProvider); + try + { + await adapter.CallInitialize(ServiceProvider); + } + catch (Exception e) + { + throw new AdapterInitException($"Adapter #{counter} failed to initialize: {adapter.GetType().Name}").Promote(e); + } loggingProvider.LogDebug($"Adapter initialized: {adapter} ({counter}/{_adapters.Count()})"); } @@ -118,6 +158,11 @@ internal async Task CleanupAsync(IServiceProvider serviceProvider) { await adapter.CallCleanup(serviceProvider); } + catch (Exception e) + { + throw new AdapterCleanupException($"Failed to cleanup adapter #{counter}: {adapter.GetType().Name}") + .Promote(e); + } finally { disposeProvider?.DisposeAll(); @@ -126,7 +171,14 @@ internal async Task CleanupAsync(IServiceProvider serviceProvider) } loggingProvider.LogDebug($"All {_adapters.Count()} adapters were successfully cleaned up ✅"); - await _scenario.CallCleanup(); + try + { + await _scenario.CallCleanup(); + } + catch (Exception e) + { + throw new ScenarioCleanupException($"Scenario cleanup failed: {_scenario.GetType().Name}").Promote(e); + } loggingProvider.LogDebug(""); loggingProvider.LogDebug(""); diff --git a/Xcepto/Strategies/Execution/BaseExecutionStrategy.cs b/Xcepto/Strategies/Execution/BaseExecutionStrategy.cs index 328b5c5..74e953b 100644 --- a/Xcepto/Strategies/Execution/BaseExecutionStrategy.cs +++ b/Xcepto/Strategies/Execution/BaseExecutionStrategy.cs @@ -7,6 +7,7 @@ using Xcepto.Exceptions; using Xcepto.Interfaces; using Xcepto.Internal; +using Xcepto.Util; namespace Xcepto.Strategies.Execution; @@ -25,10 +26,11 @@ protected void CheckTestTimeout() if (DateTime.UtcNow >= (_testStartTime + _testInstance.GetTimeout().Test)) { var failingResult = _testInstance.StateMachine?.CurrentXceptoState.MostRecentFailingResult; - var timeoutMessage = $"Test exceeded TEST timeout: {_testInstance.GetTimeout().Test}"; + string currentState = _testInstance?.StateMachine?.CurrentXceptoState.Name ?? ""; + var timeoutMessage = $"Test exceeded TEST timeout: {_testInstance.GetTimeout().Test} during [{currentState}]"; if(failingResult is null) throw new TestTimeoutException(timeoutMessage); - throw new TestTimeoutException(timeoutMessage, new AssertionException(failingResult.FailureDescription)); + throw new TestTimeoutException(timeoutMessage).Promote(new AssertionException(failingResult.FailureDescription)); } } protected void CheckTimeouts(DateTime deadline) @@ -40,7 +42,7 @@ protected void CheckTimeouts(DateTime deadline) var timeoutMessage = $"Test exceeded TOTAL timeout: {_testInstance.GetTimeout().Total}"; if(failingResult is null) throw new TotalTimeoutException(timeoutMessage); - throw new TotalTimeoutException(timeoutMessage, new AssertionException(failingResult.FailureDescription)); + throw new TotalTimeoutException(timeoutMessage).Promote(new AssertionException(failingResult.FailureDescription)); } } diff --git a/Xcepto/Strategies/Execution/EnumeratedExecutionStrategy.cs b/Xcepto/Strategies/Execution/EnumeratedExecutionStrategy.cs index 54e8530..1f2c089 100644 --- a/Xcepto/Strategies/Execution/EnumeratedExecutionStrategy.cs +++ b/Xcepto/Strategies/Execution/EnumeratedExecutionStrategy.cs @@ -33,6 +33,7 @@ public IEnumerator RunEnumerated() var serviceProvider = init.GetAwaiter().GetResult(); // EXECUTION LOOP + StartTest(); while (true) { var stepTask = _testInstance.StepAsync(serviceProvider); @@ -40,9 +41,11 @@ public IEnumerator RunEnumerated() while (!stepTask.IsCompleted) { yield return null; + CheckTestTimeout(); CheckTimeouts(deadline); CheckPropagated(propagatedTasksSupplier); } + CheckTestTimeout(); CheckTimeouts(deadline); CheckPropagated(propagatedTasksSupplier); @@ -51,6 +54,7 @@ public IEnumerator RunEnumerated() // a frame passes yield return null; + CheckTestTimeout(); CheckTimeouts(deadline); CheckPropagated(propagatedTasksSupplier); } diff --git a/Xcepto/Util/XceptoExceptionExtensions.cs b/Xcepto/Util/XceptoExceptionExtensions.cs new file mode 100644 index 0000000..338a7aa --- /dev/null +++ b/Xcepto/Util/XceptoExceptionExtensions.cs @@ -0,0 +1,51 @@ +using System; +using System.Reflection; +using Xcepto.Exceptions; + +namespace Xcepto.Util; + +public static class XceptoExceptionExtensions +{ + public static XceptoStageException Promote(this XceptoStageException outer, Exception inner) + { + CopyStackTrace(inner, outer); + SetInnerException(outer, inner); + ClearStackTrace(inner); + + return outer; + } + + private static void SetInnerException(Exception outer, Exception inner) + { + var field = typeof(Exception) + .GetField("_innerException", + BindingFlags.Instance | BindingFlags.NonPublic); + + field!.SetValue(outer, inner); + } + + private static void ClearStackTrace(Exception ex) + { + var stackTraceField = + typeof(Exception).GetField("_stackTrace", + BindingFlags.Instance | BindingFlags.NonPublic); + + var stackTraceStringField = + typeof(Exception).GetField("_stackTraceString", + BindingFlags.Instance | BindingFlags.NonPublic); + + stackTraceField?.SetValue(ex, null); + stackTraceStringField?.SetValue(ex, null); + } + + private static void CopyStackTrace(Exception source, Exception target) + { + var field = typeof(Exception) + .GetField("_remoteStackTraceString", + BindingFlags.Instance | BindingFlags.NonPublic); + + var stackTrace = source.StackTrace + Environment.NewLine; + + field!.SetValue(target, stackTrace); + } +} \ No newline at end of file