Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 12 additions & 19 deletions source/Lite.StateMachine.Tests/StateTests/BasicStateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,16 @@

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Lite.StateMachine.Tests.TestData;
using Lite.StateMachine.Tests.TestData.States;

namespace Lite.StateMachine.Tests.StateTests;

[TestClass]
public class BasicStateTests
public class BasicStateTests : TestBase
{
public TestContext TestContext { get; set; }

/// <summary>Standard synchronous state registration exiting to completion.</summary>
[TestMethod]
public void Basic_RegisterState_Executes123_SuccessTest()
Expand All @@ -35,8 +34,9 @@ public void Basic_RegisterState_Executes123_SuccessTest()
task.GetAwaiter().GetResult();

// Assert Results
Assert.IsNotNull(machine);
Assert.IsNull(machine.Context);
AssertMachineNotNull(machine);

Assert.AreEqual(0, machine.Context.Parameters.Count);

// Ensure all states are registered
var enums = Enum.GetValues<BasicStateId>().Cast<BasicStateId>();
Expand Down Expand Up @@ -68,8 +68,7 @@ public async Task Basic_RegisterState_Executes123_SuccessTestAsync()
await machine.RunAsync(BasicStateId.State1, ctxProperties);

// Assert Results
Assert.IsNotNull(machine);
Assert.IsNull(machine.Context);
AssertMachineNotNull(machine);

// Ensure all states are registered
var enums = Enum.GetValues<BasicStateId>().Cast<BasicStateId>();
Expand All @@ -94,8 +93,7 @@ public async Task Basic_RegisterState_Executes132_SuccessTestAsync()
await machine.RunAsync(BasicStateId.State1, ctxProperties);

// Assert Results
Assert.IsNotNull(machine);
Assert.IsNull(machine.Context);
AssertMachineNotNull(machine);

// Ensure all states are registered
var enums = Enum.GetValues<BasicStateId>().Cast<BasicStateId>();
Expand Down Expand Up @@ -126,8 +124,7 @@ public async Task Basic_RegisterState_Fluent_SuccessTestAsync()
.RunAsync(BasicStateId.State1, ctxProperties, cancellationToken: TestContext.CancellationToken);

// Assert Results
Assert.IsNotNull(machine);
Assert.IsNull(machine.Context);
AssertMachineNotNull(machine);

// Ensure all states are registered
var enums = Enum.GetValues<BasicStateId>().Cast<BasicStateId>();
Expand Down Expand Up @@ -190,8 +187,7 @@ public async Task HungState_Proceeds_DefaultStateTimeout_SuccessTestAsync()
await machine.RunAsync(BasicStateId.State1, ctxProperties);

// Assert Results
Assert.IsNotNull(machine);
Assert.IsNull(machine.Context);
AssertMachineNotNull(machine);

// Ensure all states are registered
var enums = Enum.GetValues<BasicStateId>().Cast<BasicStateId>();
Expand All @@ -205,8 +201,6 @@ public async Task HungState_Proceeds_DefaultStateTimeout_SuccessTestAsync()
[Ignore("vNext - Currently StateMachine destroys context after run completes.")]
public async Task RegisterState_ReturnsContext_SuccessTestAsync()
{
const string TestValue = "success";

// Assemble
var ctxProperties = new PropertyBag()
{
Expand All @@ -224,15 +218,14 @@ public async Task RegisterState_ReturnsContext_SuccessTestAsync()
await task; // Non async method: task.GetAwaiter().GetResult();

// Assert Results
Assert.IsNotNull(machine);
Assert.IsNotNull(machine.Context);
AssertMachineNotNull(machine);

var ctxFinalParams = machine.Context.Parameters;
Assert.IsNotNull(ctxFinalParams);
Assert.AreEqual(TestValue, ctxFinalParams[ParameterType.KeyTest]);
Assert.AreNotEqual(0, ctxFinalParams.Count);

// NOTE: This should be 9 because each state has 3 hooks that increment the counter
// TODO (2025-12-22 DS): Fix last state not calling OnExit.
Assert.AreEqual(9, ctxFinalParams[ParameterType.Counter]);
////Assert.AreEqual(9, ctxFinalParams[ParameterType.Counter]);
}
}
91 changes: 89 additions & 2 deletions source/Lite.StateMachine.Tests/StateTests/CommandStateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Lite.StateMachine.Tests.TestData;
using Lite.StateMachine.Tests.TestData.Models;
Expand Down Expand Up @@ -82,12 +84,97 @@ public async Task BasicState_Override_Executes_SuccessAsync()
await machine.RunAsync(StateId.State1, ctxProperties, null, TestContext.CancellationToken);

// Assert Results
Assert.IsNotNull(machine);
Assert.IsNull(machine.Context);
AssertMachineNotNull(machine);

Assert.AreEqual(29, msgService.Counter1);
Assert.AreEqual(13, msgService.Counter2, "State2 Context.Param Count");
Assert.AreEqual(12, msgService.Counter3);
Assert.AreEqual(2, msgService.Counter4);
}

[TestMethod]
public async Task CancelsInfiniteStateMachineTestAsync()
{
// Assemble with Dependency Injection
var services = new ServiceCollection()
.AddLogging(InlineTraceLogger(LogLevel.Trace))
.AddSingleton<IMessageService, MessageService>()
.AddSingleton<IEventAggregator, EventAggregator>()
.BuildServiceProvider();

var msgService = services.GetRequiredService<IMessageService>();
var events = services.GetRequiredService<IEventAggregator>();

Func<Type, object?> factory = t => ActivatorUtilities.CreateInstance(services, t);

var machine = new StateMachine<StateId>(factory, events)
.RegisterState<InfState1>(StateId.State1, StateId.State2)
.RegisterState<InfState2>(StateId.State2, StateId.State1);

var cts = new CancellationTokenSource();

var counter = 0;
events.Subscribe(msg =>
{
// All messages get received, even `CancelResponse`
if (msg is not CancelCommand cmd)
return;

counter++;
if (counter >= 100)
{
// Get outta here!!
cts.Cancel();
}

// Don't let it hang waiting for a response
events.Publish(new CancelResponse());
});

var result = await machine.RunAsync(StateId.State1, null, null, cts.Token);

// Assert
Assert.IsNotNull(result);
AssertMachineNotNull(machine);
Assert.AreEqual(100, counter);
}

private class InfState1 : IState<StateId>
{
public Task OnEnter(Context<StateId> context)
{
context.NextState(Result.Success);
return Task.CompletedTask;
}

public Task OnEntering(Context<StateId> context) => Task.CompletedTask;

public Task OnExit(Context<StateId> context) => Task.CompletedTask;
}

private class InfState2 : ICommandState<StateId>
{
public IReadOnlyCollection<Type> SubscribedMessageTypes =>
[
typeof(CancelResponse),
];

public Task OnEnter(Context<StateId> context)
{
context.EventAggregator?.Publish(new CancelCommand());
return Task.CompletedTask;
}

public Task OnEntering(Context<StateId> context) => Task.CompletedTask;

public Task OnExit(Context<StateId> context) => Task.CompletedTask;

public Task OnMessage(Context<StateId> context, object message)
{
context.NextState(Result.Success);
return Task.CompletedTask;
}

public Task OnTimeout(Context<StateId> context) => Task.CompletedTask;
}
}
18 changes: 6 additions & 12 deletions source/Lite.StateMachine.Tests/StateTests/CompositeStateTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ public async Task Level1_Basic_RegisterHelpers_SuccessTestAsync()
Console.WriteLine(umlBasic);

// Assert
Assert.IsNotNull(machine);
Assert.IsNull(machine.Context);
AssertMachineNotNull(machine);

// Ensure all states are hit
var enums = Enum.GetValues<CompositeL1StateId>().Cast<CompositeL1StateId>();
Expand All @@ -82,8 +81,7 @@ public async Task Level1_Basic_RegisterState_SuccessTestAsync()
await machine.RunAsync(CompositeL1StateId.State1);

// Assert
Assert.IsNotNull(machine);
Assert.IsNull(machine.Context);
AssertMachineNotNull(machine);

// Ensure all states are hit
var enums = Enum.GetValues<CompositeL1StateId>().Cast<CompositeL1StateId>();
Expand Down Expand Up @@ -132,8 +130,7 @@ public async Task Level1_Fluent_RegisterHelpers_SuccessTestAsync()
.RunAsync(CompositeL1StateId.State1, cancellationToken: TestContext.CancellationToken);

// Assert
Assert.IsNotNull(machine);
Assert.IsNull(machine.Context);
AssertMachineNotNull(machine);

// Ensure all states are hit
var enums = Enum.GetValues<CompositeL1StateId>().Cast<CompositeL1StateId>();
Expand All @@ -159,8 +156,7 @@ public void Level1_Fluent_RegisterState_SuccessTest()
.GetResult();

// Assert
Assert.IsNotNull(machine);
Assert.IsNull(machine.Context);
AssertMachineNotNull(machine);

// Ensure all states are hit
var enums = Enum.GetValues<CompositeL1StateId>().Cast<CompositeL1StateId>();
Expand Down Expand Up @@ -223,8 +219,7 @@ public async Task Level3_IsContextPersistent_False_SuccessTestAsync(bool context
await machine.RunAsync(CompositeL3.State1, cancellationToken: TestContext.CancellationToken);

// Assert
Assert.IsNotNull(machine);
Assert.IsNull(machine.Context);
AssertMachineNotNull(machine);

// Ensure all states are registered
var enums = Enum.GetValues<CompositeL3>().Cast<CompositeL3>();
Expand Down Expand Up @@ -284,8 +279,7 @@ public async Task Level3_PreviousStateId_SuccessTestAsync()
await machine.RunAsync(CompositeL3.State1, ctxProperties, null, TestContext.CancellationToken);

// Assert
Assert.IsNotNull(machine);
Assert.IsNull(machine.Context);
AssertMachineNotNull(machine);
}

private static StateMachine<CompositeL3> GenerateStateMachineL3(StateMachine<CompositeL3> machine)
Expand Down
7 changes: 2 additions & 5 deletions source/Lite.StateMachine.Tests/StateTests/ContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
namespace Lite.StateMachine.Tests.StateTests;

[TestClass]
public class ContextTests
public class ContextTests : TestBase
{
public const string ParameterCounter = "Counter";
public const string ParameterKeyTest = "TestKey";
Expand All @@ -29,8 +29,6 @@ private enum ParameterType
Param3,
}

public TestContext TestContext { get; set; }

/// <summary>Standard synchronous state registration exiting to completion.</summary>
[TestMethod]
public void Basic_RegisterState_Executes123_SuccessTest()
Expand All @@ -48,8 +46,7 @@ public void Basic_RegisterState_Executes123_SuccessTest()
task.GetAwaiter().GetResult();

// Assert Results
Assert.IsNotNull(machine);
Assert.IsNull(machine.Context);
AssertMachineNotNull(machine);

// Ensure all states are registered
var enums = Enum.GetValues<CtxStateId>().Cast<CtxStateId>();
Expand Down
9 changes: 3 additions & 6 deletions source/Lite.StateMachine.Tests/StateTests/CustomStateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ public async Task BasicState_Override_Executes_SuccessAsync(bool skipState3)
await machine.RunAsync(CustomStateId.State1, ctxProperties, cancellationToken: TestContext.CancellationToken);

// Assert Results
Assert.IsNotNull(machine);
Assert.IsNull(machine.Context);
AssertMachineNotNull(machine);

Assert.AreEqual(1, msgService.Counter1);
Assert.AreEqual(0, msgService.Counter2, "State2Dummy should never enter");
Expand Down Expand Up @@ -83,8 +82,7 @@ await Assert.ThrowsExactlyAsync<UnregisteredStateTransitionException>(()
=> machine.RunAsync(CustomStateId.State1, ctxProperties, null, TestContext.CancellationToken));

// Assert Results
Assert.IsNotNull(machine);
Assert.IsNull(machine.Context);
AssertMachineNotNull(machine);

Assert.AreEqual(0, msgService.Counter1);
Assert.AreEqual(0, msgService.Counter2, "State2Dummy should never enter");
Expand Down Expand Up @@ -125,8 +123,7 @@ public async Task Composite_Override_Executes_SuccessAsync(bool skipSubState2)
await machine.RunAsync(CustomStateId.State1, ctxProperties, cancellationToken: TestContext.CancellationToken);

// Assert Results
Assert.IsNotNull(machine);
Assert.IsNull(machine.Context);
AssertMachineNotNull(machine);

Assert.AreEqual(skipSubState2 ? 3 : 4, msgService.Counter1, "State Counter1 failed.");
Assert.AreEqual(0, msgService.Counter2, "State2Dummy should never enter");
Expand Down
11 changes: 6 additions & 5 deletions source/Lite.StateMachine.Tests/StateTests/DiMsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace Lite.StateMachine.Tests.StateTests;
/// <summary>Microsoft Dependency Injection Tests.</summary>
[TestClass]
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1124:Do not use regions", Justification = "Allowed for this test class")]
public class DiMsTests
public class DiMsTests : TestBase
{
[TestMethod]
public async Task Basic_FlatStates_SuccessTestAsync()
Expand All @@ -44,8 +44,7 @@ public async Task Basic_FlatStates_SuccessTestAsync()
var result = await machine.RunAsync(BasicStateId.State1);

Assert.IsNotNull(result);
Assert.IsNotNull(machine);
Assert.IsNull(machine.Context);
AssertMachineNotNull(machine);

var msgService = services.GetRequiredService<IMessageService>();
Assert.AreEqual(9, msgService.Counter1, "Message service should have 9 from the 3 states.");
Expand Down Expand Up @@ -142,8 +141,7 @@ public async Task Basic_LogLevelNone_SuccessTestAsync()
var result = await machine.RunAsync(BasicStateId.State1);

Assert.IsNotNull(result);
Assert.IsNotNull(machine);
Assert.IsNull(machine.Context);
AssertMachineNotNull(machine);

// Ensure all states are registered
var enums = Enum.GetValues<BasicStateId>().Cast<BasicStateId>();
Expand Down Expand Up @@ -277,6 +275,9 @@ public async Task RegisterState_MsDi_EventAggregatorOnly_SuccessTestAsync()

Console.WriteLine("MS.DI workflow finished.");

// Assert
AssertMachineNotNull(machine);

Assert.AreEqual(2, msgService.Counter2);
Assert.AreEqual(42, msgService.Counter1);
}
Expand Down
9 changes: 8 additions & 1 deletion source/Lite.StateMachine.Tests/StateTests/TestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ namespace Lite.StateMachine.Tests.StateTests;

public class TestBase
{
public TestContext TestContext { get; set; }
public required TestContext TestContext { get; set; }

protected static void AssertMachineNotNull<T>(Lite.StateMachine.StateMachine<T> machine)
where T : struct, Enum
{
Assert.IsNotNull(machine);
Assert.IsNotNull(machine.Context);
}

/// <summary>ILogger Helper for generating clean in-line logs.</summary>
/// <param name="logLevel">Log level (Default: Trace).</param>
Expand Down
Loading
Loading