diff --git a/Internal/Xcepto.Internal.Http/Xcepto.Internal.Http.csproj b/Internal/Xcepto.Internal.Http/Xcepto.Internal.Http.csproj
index b3a48fa..d1f4155 100644
--- a/Internal/Xcepto.Internal.Http/Xcepto.Internal.Http.csproj
+++ b/Internal/Xcepto.Internal.Http/Xcepto.Internal.Http.csproj
@@ -15,11 +15,13 @@
https://github.com/xcepto/Xcepto.NET
git
Copyright © 2025 themassiveone, Xcepto
+ xcepto256.png
+
diff --git a/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Adapters/FailingInitAdapter.cs b/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Adapters/FailingInitAdapter.cs
new file mode 100644
index 0000000..7ff3bb5
--- /dev/null
+++ b/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Adapters/FailingInitAdapter.cs
@@ -0,0 +1,9 @@
+namespace Samples.CleanupExecution.Tests.Adapters;
+
+public class FailingInitAdapter: TrackableCleanupAdapter
+{
+ protected override Task Initialize(IServiceProvider serviceProvider)
+ {
+ throw new Exception();
+ }
+}
\ No newline at end of file
diff --git a/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Adapters/SuccessfulAdapter.cs b/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Adapters/SuccessfulAdapter.cs
new file mode 100644
index 0000000..ca8cffc
--- /dev/null
+++ b/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Adapters/SuccessfulAdapter.cs
@@ -0,0 +1,6 @@
+namespace Samples.CleanupExecution.Tests.Adapters;
+
+public class SuccessfulAdapter: TrackableCleanupAdapter
+{
+
+}
\ No newline at end of file
diff --git a/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Adapters/TrackableCleanupAdapter.cs b/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Adapters/TrackableCleanupAdapter.cs
new file mode 100644
index 0000000..4dddfba
--- /dev/null
+++ b/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Adapters/TrackableCleanupAdapter.cs
@@ -0,0 +1,13 @@
+using Xcepto.Adapters;
+
+namespace Samples.CleanupExecution.Tests.Adapters;
+
+public abstract class TrackableCleanupAdapter: XceptoAdapter
+{
+ public bool CleanedUp { get; private set; } = false;
+ protected override Task Cleanup(IServiceProvider serviceProvider)
+ {
+ CleanedUp = true;
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Samples.CleanupExecution.Tests.csproj b/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Samples.CleanupExecution.Tests.csproj
new file mode 100644
index 0000000..bcfd0a5
--- /dev/null
+++ b/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Samples.CleanupExecution.Tests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ enable
+ enable
+
+ false
+ true
+ net9.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Scenario/FailingInitDoScenario.cs b/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Scenario/FailingInitDoScenario.cs
new file mode 100644
index 0000000..a66ef48
--- /dev/null
+++ b/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Scenario/FailingInitDoScenario.cs
@@ -0,0 +1,14 @@
+using Xcepto.Builder;
+using Xcepto.Data;
+using Xcepto.Scenarios;
+
+namespace Samples.CleanupExecution.Tests.Scenario;
+
+public class FailingInitDoScenario : TrackableCleanupScenario
+{
+ protected override ScenarioSetup Setup(ScenarioSetupBuilder builder) => builder.Build();
+
+ protected override ScenarioInitialization Initialize(ScenarioInitializationBuilder builder) => builder
+ .Do(_ => throw new Exception())
+ .Build();
+}
\ No newline at end of file
diff --git a/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Scenario/FailingInitFireScenario.cs b/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Scenario/FailingInitFireScenario.cs
new file mode 100644
index 0000000..98a6669
--- /dev/null
+++ b/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Scenario/FailingInitFireScenario.cs
@@ -0,0 +1,14 @@
+using Xcepto.Builder;
+using Xcepto.Data;
+using Xcepto.Scenarios;
+
+namespace Samples.CleanupExecution.Tests.Scenario;
+
+public class FailingInitFireScenario : TrackableCleanupScenario
+{
+ protected override ScenarioSetup Setup(ScenarioSetupBuilder builder) => builder.Build();
+
+ protected override ScenarioInitialization Initialize(ScenarioInitializationBuilder builder) => builder
+ .FireAndForget(_ => throw new Exception())
+ .Build();
+}
\ No newline at end of file
diff --git a/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Scenario/SuccessfulScenario.cs b/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Scenario/SuccessfulScenario.cs
new file mode 100644
index 0000000..040dbf5
--- /dev/null
+++ b/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Scenario/SuccessfulScenario.cs
@@ -0,0 +1,9 @@
+using Xcepto.Builder;
+using Xcepto.Data;
+
+namespace Samples.CleanupExecution.Tests.Scenario;
+
+public class SuccessfulScenario: TrackableCleanupScenario
+{
+ protected override ScenarioSetup Setup(ScenarioSetupBuilder builder) => builder.Build();
+}
\ No newline at end of file
diff --git a/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Scenario/TrackableCleanupScenario.cs b/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Scenario/TrackableCleanupScenario.cs
new file mode 100644
index 0000000..99b325f
--- /dev/null
+++ b/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Scenario/TrackableCleanupScenario.cs
@@ -0,0 +1,13 @@
+using Xcepto.Builder;
+using Xcepto.Data;
+using Xcepto.Scenarios;
+
+namespace Samples.CleanupExecution.Tests.Scenario;
+
+public abstract class TrackableCleanupScenario : XceptoScenario
+{
+ public bool CleanupRan { get; private set; } = false;
+ protected override ScenarioCleanup Cleanup(ScenarioCleanupBuilder builder) => builder
+ .Do(() => CleanupRan = true)
+ .Build();
+}
\ No newline at end of file
diff --git a/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Test/AdapterCleanupTests.cs b/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Test/AdapterCleanupTests.cs
new file mode 100644
index 0000000..3833526
--- /dev/null
+++ b/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Test/AdapterCleanupTests.cs
@@ -0,0 +1,47 @@
+using Samples.CleanupExecution.Tests.Adapters;
+using Samples.CleanupExecution.Tests.Scenario;
+using Xcepto;
+using Xcepto.Exceptions;
+using Xcepto.Strategies;
+using Xcepto.Strategies.Execution;
+
+namespace Samples.CleanupExecution.Tests.Test;
+
+
+[TestFixtureSource(typeof(StrategyCombinations), nameof(StrategyCombinations.AllCombinations))]
+public class AdapterCleanupTests
+{
+ private readonly XceptoTest _test;
+ public AdapterCleanupTests(BaseExecutionStrategy executionStrategy)
+ {
+ _test = new XceptoTest(executionStrategy);
+ }
+
+
+ [Test]
+ public async Task SuccessfulAdapter_CleanedUp()
+ {
+ var adapter = new SuccessfulAdapter();
+ await _test.GivenWithStrategies(new SuccessfulScenario(), builder =>
+ {
+ builder.RegisterAdapter(adapter);
+ });
+
+ Assert.That(adapter.CleanedUp, Is.True);
+ }
+
+ [Test]
+ public void FailingInitAdapter_CleanedUp()
+ {
+ var adapter = new FailingInitAdapter();
+ Assert.That(async () =>
+ {
+ await _test.GivenWithStrategies(new SuccessfulScenario(), builder =>
+ {
+ builder.RegisterAdapter(adapter);
+ });
+ }, Throws.InstanceOf());
+
+ Assert.That(adapter.CleanedUp, Is.True);
+ }
+}
\ No newline at end of file
diff --git a/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Test/ScenarioCleanupTests.cs b/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Test/ScenarioCleanupTests.cs
new file mode 100644
index 0000000..eb17348
--- /dev/null
+++ b/Samples/CleanupExecution/Samples.CleanupExecution.Tests/Test/ScenarioCleanupTests.cs
@@ -0,0 +1,52 @@
+using Samples.CleanupExecution.Tests.Scenario;
+using Xcepto;
+using Xcepto.Exceptions;
+using Xcepto.Strategies;
+using Xcepto.Strategies.Execution;
+
+namespace Samples.CleanupExecution.Tests.Test;
+
+[TestFixtureSource(typeof(StrategyCombinations), nameof(StrategyCombinations.AllCombinations))]
+public class ScenarioCleanupTests
+{
+ private readonly XceptoTest _test;
+ public ScenarioCleanupTests(BaseExecutionStrategy executionStrategy)
+ {
+ _test = new XceptoTest(executionStrategy);
+ }
+
+ [Test]
+ public async Task CleanupHappens_OnSuccessfulRun()
+ {
+ SuccessfulScenario scenario = new SuccessfulScenario();
+ await _test.GivenWithStrategies(scenario, _ => { });
+
+ Assert.That(scenario.CleanupRan, Is.True);
+ }
+
+ [Test]
+ public void CleanupHappens_OnFailingScenarioInitDo()
+ {
+ FailingInitDoScenario scenario = new FailingInitDoScenario();
+
+ Assert.That(async () =>
+ {
+ await _test.GivenWithStrategies(scenario, _ => { });
+ }, Throws.InstanceOf());
+
+ Assert.That(scenario.CleanupRan, Is.True);
+ }
+
+ [Test]
+ public void CleanupHappens_OnFailingScenarioInitFire()
+ {
+ FailingInitFireScenario scenario = new FailingInitFireScenario();
+
+ Assert.That(async () =>
+ {
+ await _test.GivenWithStrategies(scenario, _ => { });
+ }, Throws.InstanceOf());
+
+ Assert.That(scenario.CleanupRan, Is.True);
+ }
+}
\ No newline at end of file
diff --git a/Xcepto.NET.sln b/Xcepto.NET.sln
index 8dfa778..f107ca2 100644
--- a/Xcepto.NET.sln
+++ b/Xcepto.NET.sln
@@ -59,6 +59,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ExceptionDetail", "Exceptio
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
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CleanupExecution", "CleanupExecution", "{AA06077D-7B5D-41BC-8822-7FA52BC15191}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.CleanupExecution.Tests", "Samples\CleanupExecution\Samples.CleanupExecution.Tests\Samples.CleanupExecution.Tests.csproj", "{64BC5387-B5C0-4BFE-A5FD-B837B9D983DC}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -136,6 +140,10 @@ Global
{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
+ {64BC5387-B5C0-4BFE-A5FD-B837B9D983DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {64BC5387-B5C0-4BFE-A5FD-B837B9D983DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {64BC5387-B5C0-4BFE-A5FD-B837B9D983DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {64BC5387-B5C0-4BFE-A5FD-B837B9D983DC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{BABA2E33-65CD-4EB3-8FDC-3DF7B2DC2EAD} = {1C2E2691-2BCB-4F59-9222-DDA2C58EC928}
@@ -159,5 +167,7 @@ Global
{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}
+ {AA06077D-7B5D-41BC-8822-7FA52BC15191} = {1C2E2691-2BCB-4F59-9222-DDA2C58EC928}
+ {64BC5387-B5C0-4BFE-A5FD-B837B9D983DC} = {AA06077D-7B5D-41BC-8822-7FA52BC15191}
EndGlobalSection
EndGlobal
diff --git a/Xcepto.NewtonsoftJson/Xcepto.NewtonsoftJson.csproj b/Xcepto.NewtonsoftJson/Xcepto.NewtonsoftJson.csproj
index 4c26fba..1934381 100644
--- a/Xcepto.NewtonsoftJson/Xcepto.NewtonsoftJson.csproj
+++ b/Xcepto.NewtonsoftJson/Xcepto.NewtonsoftJson.csproj
@@ -15,11 +15,13 @@
https://github.com/xcepto/Xcepto.NET
git
Copyright © 2025 themassiveone, Xcepto
+ xcepto256.png
+
diff --git a/Xcepto.Rest/Xcepto.Rest.csproj b/Xcepto.Rest/Xcepto.Rest.csproj
index ec97f94..067dd93 100644
--- a/Xcepto.Rest/Xcepto.Rest.csproj
+++ b/Xcepto.Rest/Xcepto.Rest.csproj
@@ -15,11 +15,13 @@
https://github.com/xcepto/Xcepto.NET
git
Copyright © 2025 themassiveone, Xcepto
+ xcepto256.png
+
diff --git a/Xcepto.SSR/Xcepto.SSR.csproj b/Xcepto.SSR/Xcepto.SSR.csproj
index 227fb38..f596ce8 100644
--- a/Xcepto.SSR/Xcepto.SSR.csproj
+++ b/Xcepto.SSR/Xcepto.SSR.csproj
@@ -15,11 +15,13 @@
https://github.com/xcepto/Xcepto.NET
git
Copyright © 2025 themassiveone, Xcepto
+ xcepto256.png
+
diff --git a/Xcepto.Testcontainers/Xcepto.Testcontainers.csproj b/Xcepto.Testcontainers/Xcepto.Testcontainers.csproj
index 0b44777..f947c0d 100644
--- a/Xcepto.Testcontainers/Xcepto.Testcontainers.csproj
+++ b/Xcepto.Testcontainers/Xcepto.Testcontainers.csproj
@@ -15,11 +15,13 @@
https://github.com/xcepto/Xcepto.NET
git
Copyright © 2025 themassiveone, Xcepto
+ xcepto256.png
+
diff --git a/Xcepto/Internal/TestInstance.cs b/Xcepto/Internal/TestInstance.cs
index a8b7130..b2fec20 100644
--- a/Xcepto/Internal/TestInstance.cs
+++ b/Xcepto/Internal/TestInstance.cs
@@ -27,7 +27,6 @@ internal class TestInstance
private IEnumerable? _states;
private IEnumerable? _adapters;
private Func>? _propagatedTasksSupplier;
- public IServiceProvider ServiceProvider { get; private set; }
public AcceptanceStateMachine? StateMachine { get; private set; }
internal TestInstance(TimeoutConfig timeout, XceptoScenario scenario, TransitionBuilder transitionBuilder)
@@ -38,17 +37,20 @@ internal TestInstance(TimeoutConfig timeout, XceptoScenario scenario, Transition
_timeout = timeout;
}
- internal async Task InitializeAsync()
+ internal async Task SetupAsync()
{
try
{
- ServiceProvider = await _scenario.CallSetup();
+ return await _scenario.CallSetup();
}
catch (Exception e)
{
throw new ScenarioSetupException($"Scenario setup failed: {_scenario.GetType().Name}").Promote(e);
}
- var loggingProvider = ServiceProvider.GetRequiredService();
+ }
+ internal async Task InitializeAsync(IServiceProvider serviceProvider)
+ {
+ var loggingProvider = serviceProvider.GetRequiredService();
loggingProvider.LogDebug("Setting up acceptance test");
loggingProvider.LogDebug("");
@@ -58,9 +60,11 @@ internal async Task InitializeAsync()
}
catch (Exception e)
{
- throw new ScenarioInitException($"Scenario initialization failed: {_scenario.GetType().Name}").Promote(e);
+ var message = $"Scenario initialization failed: {_scenario.GetType().Name}";
+ loggingProvider.LogDebug(message);
+ throw new ScenarioInitException(message).Promote(e);
}
- loggingProvider.LogDebug("Initialized scenario successfully ✅");
+ loggingProvider.LogDebug($"Initialized scenario ({_scenario.GetType().Name}) successfully ✅");
loggingProvider.LogDebug("");
try
@@ -69,7 +73,9 @@ internal async Task InitializeAsync()
}
catch (Exception e)
{
- throw new ArrangeTestException("Failed arranging the test").Promote(e);
+ var message = "Failed arranging the test";
+ loggingProvider.LogDebug(message);
+ throw new ArrangeTestException(message).Promote(e);
}
_states = _transitionBuilder.GetStates().ToArray();
_adapters = _transitionBuilder.GetAdapters().ToArray();
@@ -84,11 +90,13 @@ internal async Task InitializeAsync()
{
try
{
- await state.Initialize(ServiceProvider);
+ await state.Initialize(serviceProvider);
}
catch (Exception e)
{
- throw new StateInitException($"State failed to initialize: [{state.Name}] ({state.GetType().Name}, state #{counter})").Promote(e);
+ string message = $"State failed to initialize: [{state.Name}] ({state.GetType().Name}, state #{counter})";
+ loggingProvider.LogDebug(message);
+ throw new StateInitException(message).Promote(e);
}
loggingProvider.LogDebug($"State initialized: {state} ({counter}/{_states.Count()+2})");
}
@@ -101,11 +109,13 @@ internal async Task InitializeAsync()
{
try
{
- await adapter.CallInitialize(ServiceProvider);
+ await adapter.CallInitialize(serviceProvider);
}
catch (Exception e)
{
- throw new AdapterInitException($"Adapter #{counter} failed to initialize: {adapter.GetType().Name}").Promote(e);
+ string message = $"Adapter #{counter} failed to initialize: {adapter.GetType().Name}";
+ loggingProvider.LogDebug(message);
+ throw new AdapterInitException(message).Promote(e);
}
loggingProvider.LogDebug($"Adapter initialized: {adapter} ({counter}/{_adapters.Count()})");
}
@@ -116,8 +126,7 @@ internal async Task InitializeAsync()
loggingProvider.LogDebug("---------------------------------");
_startTime = DateTime.Now;
- await StateMachine.Start(ServiceProvider);
- return ServiceProvider;
+ await StateMachine.Start(serviceProvider);
}
private bool _firstStep = true;
@@ -148,36 +157,40 @@ internal async Task CleanupAsync(IServiceProvider serviceProvider)
var loggingProvider = serviceProvider.GetRequiredService();
var disposeProvider = serviceProvider.GetRequiredService();
loggingProvider.LogDebug("---------------------------------");
- loggingProvider.LogDebug("Test completed ✅");
- loggingProvider.LogDebug("");
-
loggingProvider.LogDebug("Cleaning up:");
- foreach (var (adapter, counter) in _adapters.Select((adapter, i) => (adapter, i + 1)))
+ if (_adapters is not null)
{
- try
- {
- await adapter.CallCleanup(serviceProvider);
- }
- catch (Exception e)
- {
- throw new AdapterCleanupException($"Failed to cleanup adapter #{counter}: {adapter.GetType().Name}")
- .Promote(e);
- }
- finally
+ foreach (var (adapter, counter) in _adapters.Select((adapter, i) => (adapter, i + 1)))
{
- disposeProvider?.DisposeAll();
+ try
+ {
+ await adapter.CallCleanup(serviceProvider);
+ }
+ catch (Exception e)
+ {
+ string message = $"Failed to cleanup adapter #{counter}: {adapter.GetType().Name}";
+ loggingProvider.LogDebug(message);
+ throw new AdapterCleanupException(message).Promote(e);
+ }
+ finally
+ {
+ disposeProvider?.DisposeAll();
+ }
+ loggingProvider.LogDebug($"Adapter cleanup: {adapter} ({counter}/{_adapters.Count()})");
}
- loggingProvider.LogDebug($"Adapter cleanup: {adapter} ({counter}/{_adapters.Count()})");
}
- loggingProvider.LogDebug($"All {_adapters.Count()} adapters were successfully cleaned up ✅");
+ loggingProvider.LogDebug($"All {(_adapters is not null ? _adapters.Count() : "0")} adapters were successfully cleaned up ✅");
try
{
await _scenario.CallCleanup();
+ loggingProvider.LogDebug($"Scenario ({_scenario.GetType().Name}) was successfully cleaned up ✅");
}
catch (Exception e)
{
- throw new ScenarioCleanupException($"Scenario cleanup failed: {_scenario.GetType().Name}").Promote(e);
+ string message = $"Scenario cleanup failed: {_scenario.GetType().Name}";
+ loggingProvider.LogDebug(message);
+ throw new ScenarioCleanupException(message).Promote(e);
}
loggingProvider.LogDebug("");
diff --git a/Xcepto/Strategies/Execution/AsyncExecutionStrategy.cs b/Xcepto/Strategies/Execution/AsyncExecutionStrategy.cs
index 5ad288c..c7b1f0f 100644
--- a/Xcepto/Strategies/Execution/AsyncExecutionStrategy.cs
+++ b/Xcepto/Strategies/Execution/AsyncExecutionStrategy.cs
@@ -1,9 +1,6 @@
using System;
-using System.Collections.Generic;
using System.Linq;
-using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
-using Xcepto.Data;
using Xcepto.Internal;
namespace Xcepto.Strategies.Execution;
@@ -12,16 +9,15 @@ public sealed class AsyncExecutionStrategy : BaseExecutionStrategy
{
public async Task RunAsync()
{
- if (_testInstance is null)
+ if (testInstance is null)
throw new Exception("Execution strategy not primed yet");
- var propagatedTasksSupplier = _testInstance.GetPropagatedTasksSupplier();
- var timeout = _testInstance.GetTimeout();
+ var propagatedTasksSupplier = testInstance.GetPropagatedTasksSupplier();
+ var timeout = testInstance.GetTimeout();
var totalDeadline = DateTime.UtcNow + timeout.Total;
- // INIT
- var init = _testInstance.InitializeAsync();
- while (!init.IsCompleted)
+ var setup = testInstance.SetupAsync();
+ while (!setup.IsCompleted)
{
await Task.Yield();
CheckTimeouts(totalDeadline);
@@ -29,44 +25,61 @@ public async Task RunAsync()
}
CheckTimeouts(totalDeadline);
CheckPropagated(propagatedTasksSupplier);
- var serviceProvider = init.GetAwaiter().GetResult();;
+ serviceProvider = setup.GetAwaiter().GetResult();
- // EXECUTION LOOP
- StartTest();
- while (true)
+ try
{
- var stepTask = _testInstance.StepAsync(serviceProvider);
-
- while (!stepTask.IsCompleted)
+ // INIT
+ var init = testInstance.InitializeAsync(serviceProvider);
+ while (!init.IsCompleted)
{
await Task.Yield();
- CheckTestTimeout();
CheckTimeouts(totalDeadline);
CheckPropagated(propagatedTasksSupplier);
}
- CheckTestTimeout();
CheckTimeouts(totalDeadline);
CheckPropagated(propagatedTasksSupplier);
- if (stepTask.GetAwaiter().GetResult() == StepResult.Finished)
- break;
+ init.GetAwaiter().GetResult();
- await Task.Yield();
- CheckTestTimeout();
- CheckTimeouts(totalDeadline);
- CheckPropagated(propagatedTasksSupplier);
- }
+ // EXECUTION LOOP
+ StartTest();
+ while (true)
+ {
+ var stepTask = testInstance.StepAsync(serviceProvider);
+
+ while (!stepTask.IsCompleted)
+ {
+ await Task.Yield();
+ CheckTestTimeout();
+ CheckTimeouts(totalDeadline);
+ CheckPropagated(propagatedTasksSupplier);
+ }
+ CheckTestTimeout();
+ CheckTimeouts(totalDeadline);
+ CheckPropagated(propagatedTasksSupplier);
+ if (stepTask.GetAwaiter().GetResult() == StepResult.Finished)
+ break;
- // CLEANUP
- var cleanup = _testInstance.CleanupAsync(serviceProvider);
- while (!cleanup.IsCompleted)
+ await Task.Yield();
+ CheckTestTimeout();
+ CheckTimeouts(totalDeadline);
+ CheckPropagated(propagatedTasksSupplier);
+ }
+ }
+ finally
{
- await Task.Yield();
+ // CLEANUP
+ var cleanup = testInstance.CleanupAsync(serviceProvider);
+ while (!cleanup.IsCompleted)
+ {
+ await Task.Yield();
+ CheckTimeouts(totalDeadline);
+ CheckPropagated(propagatedTasksSupplier);
+ }
+ if (cleanup.IsFaulted)
+ throw cleanup.Exception?.InnerExceptions.First() ?? new Exception("cleanup task failed without exception");
CheckTimeouts(totalDeadline);
CheckPropagated(propagatedTasksSupplier);
}
- if (cleanup.IsFaulted)
- throw cleanup.Exception?.InnerExceptions.First() ?? new Exception("cleanup task failed without exception");
- CheckTimeouts(totalDeadline);
- CheckPropagated(propagatedTasksSupplier);
}
}
diff --git a/Xcepto/Strategies/Execution/BaseExecutionStrategy.cs b/Xcepto/Strategies/Execution/BaseExecutionStrategy.cs
index 74e953b..29d3996 100644
--- a/Xcepto/Strategies/Execution/BaseExecutionStrategy.cs
+++ b/Xcepto/Strategies/Execution/BaseExecutionStrategy.cs
@@ -13,7 +13,8 @@ namespace Xcepto.Strategies.Execution;
public abstract class BaseExecutionStrategy
{
- internal TestInstance? _testInstance;
+ internal TestInstance? testInstance;
+ internal IServiceProvider? serviceProvider;
private DateTime _testStartTime;
protected void StartTest()
@@ -23,11 +24,17 @@ protected void StartTest()
protected void CheckTestTimeout()
{
FlushLogs();
- if (DateTime.UtcNow >= (_testStartTime + _testInstance.GetTimeout().Test))
+ if (DateTime.UtcNow >= (_testStartTime + testInstance.GetTimeout().Test))
{
- var failingResult = _testInstance.StateMachine?.CurrentXceptoState.MostRecentFailingResult;
- string currentState = _testInstance?.StateMachine?.CurrentXceptoState.Name ?? "";
- var timeoutMessage = $"Test exceeded TEST timeout: {_testInstance.GetTimeout().Test} during [{currentState}]";
+ var failingResult = testInstance.StateMachine?.CurrentXceptoState.MostRecentFailingResult;
+ string currentState = testInstance?.StateMachine?.CurrentXceptoState.Name ?? "";
+ var timeoutMessage = $"Test exceeded TEST timeout: {testInstance.GetTimeout().Test} during [{currentState}]";
+ if (serviceProvider is not null)
+ {
+ var loggingProvider = serviceProvider.GetRequiredService();
+ loggingProvider.LogDebug(timeoutMessage);
+ FlushLogs();
+ }
if(failingResult is null)
throw new TestTimeoutException(timeoutMessage);
throw new TestTimeoutException(timeoutMessage).Promote(new AssertionException(failingResult.FailureDescription));
@@ -38,8 +45,14 @@ protected void CheckTimeouts(DateTime deadline)
FlushLogs();
if (DateTime.UtcNow >= deadline)
{
- var failingResult = _testInstance.StateMachine?.CurrentXceptoState.MostRecentFailingResult;
- var timeoutMessage = $"Test exceeded TOTAL timeout: {_testInstance.GetTimeout().Total}";
+ var failingResult = testInstance.StateMachine?.CurrentXceptoState.MostRecentFailingResult;
+ var timeoutMessage = $"Test exceeded TOTAL timeout: {testInstance.GetTimeout().Total}";
+ if (serviceProvider is not null)
+ {
+ var loggingProvider = serviceProvider.GetRequiredService();
+ loggingProvider.LogDebug(timeoutMessage);
+ FlushLogs();
+ }
if(failingResult is null)
throw new TotalTimeoutException(timeoutMessage);
throw new TotalTimeoutException(timeoutMessage).Promote(new AssertionException(failingResult.FailureDescription));
@@ -49,9 +62,9 @@ protected void CheckTimeouts(DateTime deadline)
private void FlushLogs()
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
- if (_testInstance is null || _testInstance.ServiceProvider is null)
+ if (testInstance is null || serviceProvider is null)
return;
- var loggingProvider = _testInstance.ServiceProvider.GetRequiredService();
+ var loggingProvider = serviceProvider.GetRequiredService();
loggingProvider.Flush();
}
@@ -64,15 +77,21 @@ protected void CheckPropagated(Func> propagatedTasksSupplier)
if (firstFaulted is null)
return;
-
+
// Unwrap AggregateException EXACTLY like before
var ex = firstFaulted.Exception;
var inner = ex.InnerException ?? ex;
+ if (serviceProvider is not null)
+ {
+ var loggingProvider = serviceProvider.GetRequiredService();
+ loggingProvider.LogDebug($"Propagated task failed: {inner}");
+ FlushLogs();
+ }
ExceptionDispatchInfo.Capture(inner).Throw();
}
internal void Prime(TestInstance testInstance)
{
- _testInstance = testInstance;
+ this.testInstance = testInstance;
}
}
\ No newline at end of file
diff --git a/Xcepto/Strategies/Execution/EnumeratedExecutionStrategy.cs b/Xcepto/Strategies/Execution/EnumeratedExecutionStrategy.cs
index 1f2c089..a5d671c 100644
--- a/Xcepto/Strategies/Execution/EnumeratedExecutionStrategy.cs
+++ b/Xcepto/Strategies/Execution/EnumeratedExecutionStrategy.cs
@@ -11,65 +11,94 @@ namespace Xcepto.Strategies.Execution;
public sealed class EnumeratedExecutionStrategy: BaseExecutionStrategy
{
+ private DateTime _deadline;
+ private Func> _propagatedTasksSupplier;
+
public IEnumerator RunEnumerated()
{
- if (_testInstance is null)
+ if (testInstance is null)
throw new Exception("Execution strategy not primed yet");
- var propagatedTasksSupplier = _testInstance.GetPropagatedTasksSupplier();
- var timeout = _testInstance.GetTimeout();
- var deadline = DateTime.UtcNow + timeout.Total;
+ _propagatedTasksSupplier = testInstance.GetPropagatedTasksSupplier();
+ var timeout = testInstance.GetTimeout();
+ _deadline = DateTime.UtcNow + timeout.Total;
- // INIT
- var init = _testInstance.InitializeAsync();
- while (!init.IsCompleted)
+ var setup = testInstance.SetupAsync();
+ while (!setup.IsCompleted)
{
yield return null;
- CheckTimeouts(deadline);
- CheckPropagated(propagatedTasksSupplier);
+ CheckTimeouts(_deadline);
}
- CheckTimeouts(deadline);
- CheckPropagated(propagatedTasksSupplier);
- var serviceProvider = init.GetAwaiter().GetResult();
+ CheckTimeouts(_deadline);
+ serviceProvider = setup.GetAwaiter().GetResult();
- // EXECUTION LOOP
- StartTest();
- while (true)
+ try
{
- var stepTask = _testInstance.StepAsync(serviceProvider);
-
- while (!stepTask.IsCompleted)
+ // INIT
+ var init = testInstance.InitializeAsync(serviceProvider);
+ while (!init.IsCompleted)
{
yield return null;
- CheckTestTimeout();
- CheckTimeouts(deadline);
- CheckPropagated(propagatedTasksSupplier);
+ CheckTimeouts(_deadline);
+ CheckPropagated(_propagatedTasksSupplier);
}
- CheckTestTimeout();
- CheckTimeouts(deadline);
- CheckPropagated(propagatedTasksSupplier);
+ CheckTimeouts(_deadline);
+ CheckPropagated(_propagatedTasksSupplier);
+ init.GetAwaiter().GetResult();
- if (stepTask.GetAwaiter().GetResult() == StepResult.Finished)
- break;
+ // EXECUTION LOOP
+ StartTest();
+ while (true)
+ {
+ var stepTask = testInstance.StepAsync(serviceProvider);
- // a frame passes
- yield return null;
- CheckTestTimeout();
- CheckTimeouts(deadline);
- CheckPropagated(propagatedTasksSupplier);
+ while (!stepTask.IsCompleted)
+ {
+ yield return null;
+ CheckTestTimeout();
+ CheckTimeouts(_deadline);
+ CheckPropagated(_propagatedTasksSupplier);
+ }
+ CheckTestTimeout();
+ CheckTimeouts(_deadline);
+ CheckPropagated(_propagatedTasksSupplier);
+
+ if (stepTask.GetAwaiter().GetResult() == StepResult.Finished)
+ break;
+
+ // a frame passes
+ yield return null;
+ CheckTestTimeout();
+ CheckTimeouts(_deadline);
+ CheckPropagated(_propagatedTasksSupplier);
+ }
+ }
+ finally
+ {
+ var enumerator = Cleanup();
+ while (enumerator.MoveNext())
+ {
+ CheckTimeouts(_deadline);
+ CheckPropagated(_propagatedTasksSupplier);
+ }
+ CheckTimeouts(_deadline);
+ CheckPropagated(_propagatedTasksSupplier);
}
+ }
+ private IEnumerator Cleanup()
+ {
// CLEANUP
- var cleanup = _testInstance.CleanupAsync(serviceProvider);
+ var cleanup = testInstance.CleanupAsync(serviceProvider);
while (!cleanup.IsCompleted)
{
yield return null;
- CheckTimeouts(deadline);
- CheckPropagated(propagatedTasksSupplier);
+ CheckTimeouts(_deadline);
+ CheckPropagated(_propagatedTasksSupplier);
}
if (cleanup.IsFaulted)
throw cleanup.Exception?.InnerExceptions.First() ?? new Exception("cleanup task failed without exception");
- CheckTimeouts(deadline);
- CheckPropagated(propagatedTasksSupplier);
+ CheckTimeouts(_deadline);
+ CheckPropagated(_propagatedTasksSupplier);
}
}
\ No newline at end of file
diff --git a/Xcepto/Xcepto.csproj b/Xcepto/Xcepto.csproj
index 97757df..f94799c 100644
--- a/Xcepto/Xcepto.csproj
+++ b/Xcepto/Xcepto.csproj
@@ -15,11 +15,13 @@
https://github.com/xcepto/Xcepto.NET
git
Copyright © 2025 themassiveone, Xcepto
+ xcepto256.png
+
diff --git a/Xcepto/XceptoTest.cs b/Xcepto/XceptoTest.cs
index d236fab..07b8957 100644
--- a/Xcepto/XceptoTest.cs
+++ b/Xcepto/XceptoTest.cs
@@ -53,12 +53,7 @@ public static IEnumerator GivenEnumerated(XceptoScenario scenario, TimeoutConfig
return xceptoTest.GivenEnumeratedWithStrategies(scenario, timeout, builder);
}
- public static IEnumerator GivenEnumerated(XceptoScenario scenario, Action builder) =>
- GivenEnumerated(scenario, DefaultTimeout, builder);
-
- public IEnumerator GivenEnumeratedWithStrategies(XceptoScenario scenario, Action builder) =>
- GivenEnumeratedWithStrategies(scenario, DefaultTimeout, builder);
- public IEnumerator GivenEnumeratedWithStrategies(XceptoScenario scenario, TimeoutConfig timeout, Action builder)
+ private IEnumerator GivenEnumeratedWithStrategies(XceptoScenario scenario, TimeoutConfig timeout, Action builder)
{
if (_executionStrategy is not EnumeratedExecutionStrategy enumeratedExecutionStrategy)
throw new ArgumentException("Only enumerated strategy allowed");
diff --git a/media/xcepto256.png b/media/xcepto256.png
new file mode 100644
index 0000000..17453b7
Binary files /dev/null and b/media/xcepto256.png differ
diff --git a/media/xcepto256.xcf b/media/xcepto256.xcf
new file mode 100644
index 0000000..7933bac
Binary files /dev/null and b/media/xcepto256.xcf differ