From f8256f8c62760ec52387a8687c202ad587ce976f Mon Sep 17 00:00:00 2001 From: Justin Coulston Date: Wed, 11 Nov 2020 00:25:36 -0500 Subject: [PATCH 1/8] Added ability to dynamically build State Machine with the builder pattern Added SetSuperState to primary State interface to allow for setting the super state when defining the state through the builder pattern Modified scope of protected methods in AutomatonymousStateMachine to `protected internal` to allow the internal builders to access the methods without having to re-create them Added new methods for defining State and Event to AutomatnymousStateMachine required by the builder pattern Added Modify method, BuilderStateMachine class, and static Build method to AutomatonymousStateMachine to allow for the builder pattern to be initiated through the primary state machine. A proposed approach for a simple way to make it dynamic. Added new interfaces and internal classes for the builder pattern. Copied 165 of the 170 current tests, categorized them as "Dynamic Modify" and altered all state machine instances to utilize the builder pattern instead to ensure specs are still maintained. --- .gitignore | 3 +- .../Dynamic Modify/Activity_Specs.cs | 219 +++++++++ .../AnyStateTransition_Specs.cs | 65 +++ .../Dynamic Modify/Anytime_Specs.cs | 90 ++++ .../Dynamic Modify/AsyncActivity_Specs.cs | 72 +++ .../AutomatonymousStateMachine_Specs.cs | 100 ++++ .../Dynamic Modify/Combine_Specs.cs | 178 +++++++ .../CompositeCondition_Specs.cs | 95 ++++ .../Dynamic Modify/CompositeOrder_Specs.cs | 105 ++++ .../Dynamic Modify/Condition_Specs.cs | 199 ++++++++ .../Dynamic Modify/DataActivity_Specs.cs | 75 +++ .../Dynamic Modify/Declarative_Specs.cs | 69 +++ .../Dynamic Modify/Dependency_Specs.cs | 123 +++++ .../Dynamic Modify/EventLift_Specs.cs | 83 ++++ .../Dynamic Modify/EventObservable_Specs.cs | 50 ++ .../Dynamic Modify/Event_Specs.cs | 83 ++++ .../Dynamic Modify/Exception_Specs.cs | 454 ++++++++++++++++++ .../Dynamic Modify/Faulted_Specs.cs | 140 ++++++ .../Dynamic Modify/FilterExpression_Specs.cs | 72 +++ .../Dynamic Modify/Group_Specs.cs | 181 +++++++ .../Dynamic Modify/InstanceLift_Specs.cs | 121 +++++ .../Dynamic Modify/Introspection_Specs.cs | 134 ++++++ .../Models/DynamicModifyTestStateMachine.cs | 8 + .../Dynamic Modify/Observable_Specs.cs | 375 +++++++++++++++ .../Dynamic Modify/RaiseEvent_Specs.cs | 81 ++++ .../Dynamic Modify/Request_Specs.cs | 399 +++++++++++++++ .../Dynamic Modify/SerializeState_Specs.cs | 76 +++ .../Dynamic Modify/StateExpression_Specs.cs | 256 ++++++++++ .../Dynamic Modify/State_Specs.cs | 148 ++++++ .../Dynamic Modify/Telephone_Sample.cs | 247 ++++++++++ .../Dynamic Modify/Transition_Specs.cs | 158 ++++++ .../Dynamic Modify/UnobservedEvent_Specs.cs | 216 +++++++++ .../Dynamic Modify/Visualizer_Specs.cs | 104 ++++ .../AutomatonymousStateMachine.cs | 273 +++++++++-- ...ernalStateMachineEventActivitiesBuilder.cs | 174 +++++++ .../Builders/InternalStateMachineModifier.cs | 230 +++++++++ .../StateMachineEventActivitiesBuilder.cs | 23 + .../Builders/StateMachineModifier.cs | 67 +++ src/Automatonymous/State.cs | 1 + .../States/StateMachineState.cs | 12 +- 40 files changed, 5513 insertions(+), 46 deletions(-) create mode 100644 src/Automatonymous.Tests/Dynamic Modify/Activity_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/AnyStateTransition_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/Anytime_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/AsyncActivity_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/AutomatonymousStateMachine_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/Combine_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/CompositeCondition_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/CompositeOrder_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/Condition_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/DataActivity_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/Declarative_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/Dependency_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/EventLift_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/EventObservable_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/Event_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/Exception_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/Faulted_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/FilterExpression_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/Group_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/InstanceLift_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/Introspection_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/Models/DynamicModifyTestStateMachine.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/Observable_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/RaiseEvent_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/Request_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/SerializeState_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/StateExpression_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/State_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/Telephone_Sample.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/Transition_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/UnobservedEvent_Specs.cs create mode 100644 src/Automatonymous.Tests/Dynamic Modify/Visualizer_Specs.cs create mode 100644 src/Automatonymous/Builders/InternalStateMachineEventActivitiesBuilder.cs create mode 100644 src/Automatonymous/Builders/InternalStateMachineModifier.cs create mode 100644 src/Automatonymous/Builders/StateMachineEventActivitiesBuilder.cs create mode 100644 src/Automatonymous/Builders/StateMachineModifier.cs diff --git a/.gitignore b/.gitignore index e10dac09..aca8fb7b 100644 --- a/.gitignore +++ b/.gitignore @@ -47,4 +47,5 @@ tools/** !tools/packages.config # Artifacts -artifacts/** \ No newline at end of file +artifacts/** +/.vs diff --git a/src/Automatonymous.Tests/Dynamic Modify/Activity_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Activity_Specs.cs new file mode 100644 index 00000000..7412e78f --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/Activity_Specs.cs @@ -0,0 +1,219 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class When_specifying_an_event_activity + { + [Test] + public void Should_transition_to_the_proper_state() + { + Assert.AreEqual(Running, _instance.CurrentState); + } + + State Running; + Event Initialized; + + Instance _instance; + StateMachine _machine; + + [OneTimeSetUp] + public void Specifying_an_event_activity() + { + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .Event("Initialized", out Initialized) + .InstanceState(b => b.CurrentState) + .During(builder.Initial) + .When(Initialized, b => b.TransitionTo(Running)) + ); + + _machine.RaiseEvent(_instance, Initialized) + .Wait(); + } + + + class Instance + { + public State CurrentState { get; set; } + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class When_specifying_an_event_activity_using_initially + { + [Test] + public void Should_transition_to_the_proper_state() + { + Assert.AreEqual(Running, _instance.CurrentState); + } + + State Running; + Event Initialized; + + Instance _instance; + StateMachine _machine; + + [OneTimeSetUp] + public void Specifying_an_event_activity() + { + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .Event("Initialized", out Initialized) + .InstanceState(b => b.CurrentState) + .During(builder.Initial) + .When(Initialized, b => b.TransitionTo(Running)) + ); + + _machine.RaiseEvent(_instance, Initialized); + } + + + class Instance + { + public State CurrentState { get; set; } + } + + + class InstanceStateMachine : + AutomatonymousStateMachine + { + public InstanceStateMachine() + { + Initially( + When(Initialized) + .TransitionTo(Running)); + } + + public State Running { get; private set; } + + public Event Initialized { get; private set; } + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class When_specifying_an_event_activity_using_finally + { + [Test] + public void Should_have_called_the_finally_activity() + { + Assert.AreEqual(Finalized, _instance.Value); + } + + [Test] + public void Should_transition_to_the_proper_state() + { + Assert.AreEqual(_machine.Final, _instance.CurrentState); + } + + const string Finalized = "Finalized"; + + State Running; + Event Initialized; + + Instance _instance; + StateMachine _machine; + + [OneTimeSetUp] + public void Specifying_an_event_activity() + { + const string Finalized = "Finalized"; + + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .Event("Initialized", out Initialized) + .InstanceState(b => b.CurrentState) + .During(builder.Initial) + .When(Initialized, b => b.Finalize()) + .Finally(b => b.Then(context => context.Instance.Value = Finalized)) + ); + + _machine.RaiseEvent(_instance, Initialized) + .Wait(); + } + + + class Instance + { + public string Value { get; set; } + public State CurrentState { get; set; } + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class When_hooking_the_initial_enter_state_event + { + [Test] + public void Should_call_the_activity() + { + Assert.AreEqual(_machine.Final, _instance.CurrentState); + } + + [Test] + public void Should_have_trigger_the_final_before_enter_event() + { + Assert.AreEqual(Running, _instance.FinalState); + } + + [Test] + public void Should_have_triggered_the_after_leave_event() + { + Assert.AreEqual(_machine.Initial, _instance.LeftState); + } + + [Test] + public void Should_have_triggered_the_before_enter_event() + { + Assert.AreEqual(Initializing, _instance.EnteredState); + } + + State Running; + State Initializing; + Event Initialized; + + Instance _instance; + StateMachine _machine; + + [OneTimeSetUp] + public void Specifying_an_event_activity() + { + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .State("Initializing", out Initializing) + .Event("Initialized", out Initialized) + .InstanceState(b => b.CurrentState) + .During(Initializing) + .When(Initialized, b => b.TransitionTo(Running)) + .DuringAny() + .When(builder.Initial.Enter, b => b.TransitionTo(Initializing)) + .When(builder.Initial.AfterLeave, b => b.Then(context => context.Instance.LeftState = context.Data)) + .When(Initializing.BeforeEnter, b => b.Then(context => context.Instance.EnteredState = context.Data)) + .When(Running.Enter, b => b.Finalize()) + .When(builder.Final.BeforeEnter, b => b.Then(context => context.Instance.FinalState = context.Instance.CurrentState)) + ); + + _machine.RaiseEvent(_instance, Initialized) + .Wait(); + } + + class Instance + { + public State CurrentState { get; set; } + public State EnteredState { get; set; } + public State LeftState { get; set; } + public State FinalState { get; set; } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/AnyStateTransition_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/AnyStateTransition_Specs.cs new file mode 100644 index 00000000..88842485 --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/AnyStateTransition_Specs.cs @@ -0,0 +1,65 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class When_any_state_transition_occurs + { + [Test] + public void Should_be_running() + { + Assert.AreEqual(Running, _instance.CurrentState); + } + + [Test] + public void Should_have_entered_running() + { + Assert.AreEqual(Running, _instance.LastEntered); + } + + [Test] + public void Should_have_left_initial() + { + Assert.AreEqual(_machine.Initial, _instance.LastLeft); + } + + State Running; + Event Initialized; + Event Finish; + + Instance _instance; + StateMachine _machine; + + [OneTimeSetUp] + public void Setup() + { + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .Event("Initialized", out Initialized) + .Event("Finish", out Finish) + .InstanceState(b => b.CurrentState) + .During(builder.Initial) + .When(Initialized, b => b.TransitionTo(Running)) + .During(Running) + .When(Finish, b => b.Finalize()) + .BeforeEnterAny(b => b.Then(context => context.Instance.LastEntered = context.Data)) + .AfterLeaveAny(b => b.Then(context => context.Instance.LastLeft = context.Data)) + ); + + _machine.RaiseEvent(_instance, Initialized) + .Wait(); + } + + + class Instance + { + public State CurrentState { get; set; } + + public State LastEntered { get; set; } + public State LastLeft { get; set; } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/Anytime_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Anytime_Specs.cs new file mode 100644 index 00000000..33463b0d --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/Anytime_Specs.cs @@ -0,0 +1,90 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System.Threading.Tasks; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class Anytime_events + { + [Test] + public async Task Should_be_called_regardless_of_state() + { + var instance = new Instance(); + + await _machine.RaiseEvent(instance, Init); + await _machine.RaiseEvent(instance, Hello); + + Assert.IsTrue(instance.HelloCalled); + Assert.AreEqual(_machine.Final, instance.CurrentState); + } + + [Test] + public async Task Should_have_value_of_event_data() + { + var instance = new Instance(); + + await _machine.RaiseEvent(instance, Init); + await _machine.RaiseEvent(instance, EventA, new A + { + Value = "Test" + }); + + Assert.AreEqual("Test", instance.AValue); + Assert.AreEqual(_machine.Final, instance.CurrentState); + } + + [Test] + public void Should_not_be_handled_on_initial() + { + var instance = new Instance(); + + Assert.That(async () => await _machine.RaiseEvent(instance, Hello), Throws.TypeOf()); + + Assert.IsFalse(instance.HelloCalled); + Assert.AreEqual(_machine.Initial, instance.CurrentState); + } + + State Ready; + Event Init; + Event Hello; + Event EventA; + + StateMachine _machine; + + [OneTimeSetUp] + public void A_state_is_declared() + { + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Ready", out Ready) + .Event("Init", out Init) + .Event("Hello", out Hello) + .Event("EventA", out EventA) + .Initially() + .When(Init, b => b.TransitionTo(Ready)) + .DuringAny() + .When(Hello, b => b + .Then(context => context.Instance.HelloCalled = true) + .Finalize() + ) + .When(EventA, b => b + .Then(context => context.Instance.AValue = context.Data.Value) + .Finalize() + ) + ); + } + + class A + { + public string Value { get; set; } + } + + class Instance + { + public bool HelloCalled { get; set; } + public string AValue { get; set; } + public State CurrentState { get; set; } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/AsyncActivity_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/AsyncActivity_Specs.cs new file mode 100644 index 00000000..4b056a00 --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/AsyncActivity_Specs.cs @@ -0,0 +1,72 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System.Threading.Tasks; + using GreenPipes; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class Using_an_asynchronous_activity + { + [Test] + public async Task Should_capture_the_value() + { + Event Create = null; + + var claim = new TestInstance(); + var machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out State Running) + .Event("Create", out Create) + .InstanceState(b => b.CurrentState) + .During(builder.Initial) + .When(Create, b => b + .Execute(context => new SetValueAsyncActivity()) + .TransitionTo(Running) + ) + ); + + await machine.RaiseEvent(claim, Create, new CreateInstance()); + + Assert.AreEqual("ExecuteAsync", claim.Value); + } + + class TestInstance + { + public State CurrentState { get; set; } + public string Value { get; set; } + } + + class SetValueAsyncActivity : + Activity + { + async Task Activity.Execute(BehaviorContext context, + Behavior next) + { + context.Instance.Value = "ExecuteAsync"; + } + + Task Activity.Faulted( + BehaviorExceptionContext context, + Behavior next) + { + return next.Faulted(context); + } + + void Visitable.Accept(StateMachineVisitor visitor) + { + visitor.Visit(this); + } + + public void Probe(ProbeContext context) + { + } + } + + class CreateInstance + { + public int X { get; set; } + public int Y { get; set; } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/AutomatonymousStateMachine_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/AutomatonymousStateMachine_Specs.cs new file mode 100644 index 00000000..2575e5b0 --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/AutomatonymousStateMachine_Specs.cs @@ -0,0 +1,100 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System.Linq; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class Using_a_simple_state_machine + { + [Test] + public void Should_initialize_inherited_final_state_property() + { + var stateMachine = CreateStateMachine(); + + Assert.That(stateMachine.Final, Is.Not.Null); + } + + [Test] + public void Should_initialize_inherited_initial_state_property() + { + var stateMachine = CreateStateMachine(); + + Assert.That(stateMachine.Initial, Is.Not.Null); + } + + [Test] + public void Should_register_all_events() + { + var stateMachine = CreateStateMachine(); + + var events = stateMachine.Events.ToList(); + + Assert.That(events, Has.Count.EqualTo(2)); + } + + [Test] + public void Should_register_all_states() + { + var stateMachine = CreateStateMachine(); + + var states = stateMachine.States.ToList(); + + Assert.That(states, Has.Count.EqualTo(3)); + } + + [Test] + public void Should_register_declared_state() + { + var stateMachine = CreateStateMachine(); + + Assert.That(stateMachine.States, Contains.Item(ThisIsAState)); + } + + [Test] + public void Should_register_generic_event() + { + var stateMachine = CreateStateMachine(); + + Assert.That(stateMachine.Events, Contains.Item(ThisIsAnEventConsumingData)); + } + + [Test] + public void Should_register_inherited_final_state_property() + { + var stateMachine = CreateStateMachine(); + + Assert.That(stateMachine.States, Contains.Item(stateMachine.Final)); + } + + [Test] + public void Should_register_inherited_initial_state_property() + { + var stateMachine = CreateStateMachine(); + + Assert.That(stateMachine.States, Contains.Item(stateMachine.Initial)); + } + + [Test] + public void Should_register_simple_event() + { + var stateMachine = CreateStateMachine(); + + Assert.That(stateMachine.Events, Contains.Item(ThisIsASimpleEvent)); + } + + State ThisIsAState; + Event ThisIsASimpleEvent; + Event ThisIsAnEventConsumingData; + + class Instance { } + class EventData { } + + private StateMachine CreateStateMachine() + => AutomatonymousStateMachine.Build(builder => builder + .State("ThisIsAState", out ThisIsAState) + .Event("ThisIsASimpleEvent", out ThisIsASimpleEvent) + .Event("ThisIsAnEventConsumingData", out ThisIsAnEventConsumingData) + ); + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/Combine_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Combine_Specs.cs new file mode 100644 index 00000000..106d9137 --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/Combine_Specs.cs @@ -0,0 +1,178 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System.Threading.Tasks; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class When_combining_events_into_a_single_event + { + [Test] + public async Task Should_have_called_combined_event() + { + _machine = CreateStateMachine(); + _instance = new Instance(); + await _machine.RaiseEvent(_instance, Start); + + await _machine.RaiseEvent(_instance, First); + await _machine.RaiseEvent(_instance, Second); + + Assert.IsTrue(_instance.Called); + } + + [Test] + public async Task Should_not_call_for_one_event() + { + _machine = CreateStateMachine(); + _instance = new Instance(); + await _machine.RaiseEvent(_instance, Start); + + await _machine.RaiseEvent(_instance, First); + + Assert.IsFalse(_instance.Called); + } + + [Test] + public async Task Should_not_call_for_one_other_event() + { + _machine = CreateStateMachine(); + _instance = new Instance(); + await _machine.RaiseEvent(_instance, Start); + + await _machine.RaiseEvent(_instance, Second); + + Assert.IsFalse(_instance.Called); + } + + State Waiting; + Event Start; + Event First; + Event Second; + Event Third; + + StateMachine _machine; + Instance _instance; + + + class Instance + { + public CompositeEventStatus CompositeStatus { get; set; } + public bool Called { get; set; } + public State CurrentState { get; set; } + } + + private StateMachine CreateStateMachine() + { + return AutomatonymousStateMachine + .Build(builder => builder + .State("Waiting", out Waiting) + .Event("Start", out Start) + .Event("First", out First) + .Event("Second", out Second) + .Event("Third", out Third) + .CompositeEvent(Third, b => b.CompositeStatus, First, Second) + .Initially() + .When(Start, b => b.TransitionTo(Waiting)) + .During(Waiting) + .When(Third, b => b + .Then(context => context.Instance.Called = true) + .Finalize() + ) + ); + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class When_combining_events_with_an_int_for_state + { + [Test] + public async Task Should_have_called_combined_event() + { + _machine = CreateStateMachine(); + _instance = new Instance(); + await _machine.RaiseEvent(_instance, Start); + + Assert.IsFalse(_instance.Called); + + await _machine.RaiseEvent(_instance, First); + await _machine.RaiseEvent(_instance, Second); + + Assert.IsTrue(_instance.Called); + + Assert.AreEqual(2, _instance.CurrentState); + } + + [Test] + public async Task Should_have_initial_state_with_zero() + { + _machine = CreateStateMachine(); + _instance = new Instance(); + await _machine.RaiseEvent(_instance, Start); + + Assert.AreEqual(3, _instance.CurrentState); + } + + [Test] + public async Task Should_not_call_for_one_event() + { + _machine = CreateStateMachine(); + _instance = new Instance(); + await _machine.RaiseEvent(_instance, Start); + + await _machine.RaiseEvent(_instance, First); + + Assert.IsFalse(_instance.Called); + } + + [Test] + public async Task Should_not_call_for_one_other_event() + { + _machine = CreateStateMachine(); + _instance = new Instance(); + await _machine.RaiseEvent(_instance, Start); + + await _machine.RaiseEvent(_instance, Second); + + Assert.IsFalse(_instance.Called); + } + + State Waiting; + Event Start; + Event First; + Event Second; + Event Third; + + StateMachine _machine; + Instance _instance; + + + class Instance + { + public int CompositeStatus { get; set; } + public bool Called { get; set; } + public int CurrentState { get; set; } + } + + private StateMachine CreateStateMachine() + { + return AutomatonymousStateMachine + .Build(builder => builder + .State("Waiting", out Waiting) + .Event("Start", out Start) + .Event("First", out First) + .Event("Second", out Second) + .Event("Third", out Third) + .InstanceState(b => b.CurrentState) + .CompositeEvent(Third, b => b.CompositeStatus, First, Second) + .Initially() + .When(Start, b => b.TransitionTo(Waiting)) + .During(Waiting) + .When(Third, b => b + .Then(context => context.Instance.Called = true) + .Finalize() + ) + ); + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/CompositeCondition_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/CompositeCondition_Specs.cs new file mode 100644 index 00000000..48880f3b --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/CompositeCondition_Specs.cs @@ -0,0 +1,95 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System.Threading.Tasks; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class When_specifying_a_condition_on_a_composite_event + { + [Test] + public async Task Should_call_when_met() + { + _machine = CreateStateMachine(); + _instance = new Instance(); + await _machine.RaiseEvent(_instance, Start); + + await _machine.RaiseEvent(_instance, Second); + await _machine.RaiseEvent(_instance, First); + + Assert.IsTrue(_instance.Called); + Assert.IsTrue(_instance.SecondFirst); + } + + [Test] + public async Task Should_skip_when_not_met() + { + _machine = CreateStateMachine(); + _instance = new Instance(); + await _machine.RaiseEvent(_instance, Start); + + await _machine.RaiseEvent(_instance, First); + await _machine.RaiseEvent(_instance, Second); + + Assert.IsFalse(_instance.Called); + Assert.IsFalse(_instance.SecondFirst); + } + + State Waiting; + Event Start; + Event First; + Event Second; + Event Third; + + StateMachine _machine; + Instance _instance; + + + class Instance + { + public CompositeEventStatus CompositeStatus { get; set; } + public bool Called { get; set; } + public bool CalledAfterAll { get; set; } + public State CurrentState { get; set; } + public bool SecondFirst { get; set; } + public bool First { get; set; } + public bool Second { get; set; } + } + + private StateMachine CreateStateMachine() + { + return AutomatonymousStateMachine + .Build(builder => builder + .State("Waiting", out Waiting) + .Event("Start", out Start) + .Event("First", out First) + .Event("Second", out Second) + .Event("Third", out Third) + .Initially() + .When(Start, b => b.TransitionTo(Waiting)) + .During(Waiting) + .When(First, b => b.Then(context => + { + context.Instance.First = true; + context.Instance.CalledAfterAll = false; + })) + .When(Second, b => b.Then(context => + { + context.Instance.SecondFirst = !context.Instance.First; + context.Instance.Second = true; + context.Instance.CalledAfterAll = false; + })) + .CompositeEvent(Third, b => b.CompositeStatus, First, Second) + .During(Waiting) + .When(Third, context => context.Instance.SecondFirst, b => b + .Then(context => + { + context.Instance.Called = true; + context.Instance.CalledAfterAll = true; + }) + .Finalize() + ) + ); + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/CompositeOrder_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/CompositeOrder_Specs.cs new file mode 100644 index 00000000..0c2d122e --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/CompositeOrder_Specs.cs @@ -0,0 +1,105 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System.Threading.Tasks; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class When_combining_events_into_a_single_event_happily + { + [Test] + public async Task Should_have_called_combined_event() + { + _machine = CreateStateMachine(); + _instance = new Instance(); + await _machine.RaiseEvent(_instance, Start); + + await _machine.RaiseEvent(_instance, First); + await _machine.RaiseEvent(_instance, Second); + + Assert.IsTrue(_instance.Called); + } + + [Test] + public async Task Should_have_called_combined_event_after_all_events() + { + _machine = CreateStateMachine(); + _instance = new Instance(); + await _machine.RaiseEvent(_instance, Start); + + await _machine.RaiseEvent(_instance, First); + await _machine.RaiseEvent(_instance, Second); + + Assert.IsTrue(_instance.CalledAfterAll); + } + + [Test] + public async Task Should_not_call_for_one_event() + { + _machine = CreateStateMachine(); + _instance = new Instance(); + await _machine.RaiseEvent(_instance, Start); + + await _machine.RaiseEvent(_instance, First); + + Assert.IsFalse(_instance.Called); + } + + [Test] + public async Task Should_not_call_for_one_other_event() + { + _machine = CreateStateMachine(); + _instance = new Instance(); + await _machine.RaiseEvent(_instance, Start); + + await _machine.RaiseEvent(_instance, Second); + + Assert.IsFalse(_instance.Called); + } + + class Instance + { + public CompositeEventStatus CompositeStatus { get; set; } + public bool Called { get; set; } + public bool CalledAfterAll { get; set; } + public State CurrentState { get; set; } + } + + State Waiting; + Event Start; + Event First; + Event Second; + Event Third; + + StateMachine _machine; + Instance _instance; + + private StateMachine CreateStateMachine() + { + return AutomatonymousStateMachine + .Build(builder => builder + .State("Waiting", out Waiting) + .Event("Start", out Start) + .Event("First", out First) + .Event("Second", out Second) + .Event("Third", out Third) + .Initially() + .When(Start, b => b.TransitionTo(Waiting)) + .During(Waiting) + .When(First, b => b.Then(context => { context.Instance.CalledAfterAll = false; })) + .When(Second, b => b.Then(context => { context.Instance.CalledAfterAll = false; })) + .CompositeEvent(Third, b => b.CompositeStatus, First, Second) + .During(Waiting) + .When(Third, b => b + .Then(context => + { + context.Instance.Called = true; + context.Instance.CalledAfterAll = true; + }) + .Finalize() + ) + + ); + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/Condition_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Condition_Specs.cs new file mode 100644 index 00000000..c1421548 --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/Condition_Specs.cs @@ -0,0 +1,199 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System; + using System.Threading.Tasks; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class Using_a_condition_in_a_state_machine + { + [Test] + public async Task Should_allow_if_condition_to_be_evaluated() + { + await _machine.RaiseEvent(_instance, ExplicitFilterStarted, new StartedExplicitFilter()); + + Assert.That(_instance.CurrentState, Is.Not.EqualTo(ShouldNotBeHere)); + } + + [Test] + public async Task Should_allow_the_condition_to_be_used() + { + await _machine.RaiseEvent(_instance, Started, new Start {InitializeOnly = true}); + + Assert.That(_instance.CurrentState, Is.EqualTo(Initialized)); + } + + [Test] + public async Task Should_evaluate_else_statement_when_if_condition__is_false() + { + await _machine.RaiseEvent(_instance, ExplicitFilterStarted, new StartedExplicitFilter()); + + Assert.That(_instance.ShouldBeCalled, Is.True); + } + + [Test] + public async Task Should_work() + { + await _machine.RaiseEvent(_instance, Started, new Start()); + + Assert.That(_instance.CurrentState, Is.EqualTo(Running)); + } + + + [SetUp] + public void Specifying_an_event_activity() + { + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .State("Initialized", out Initialized) + .State("ShouldNotBeHere", out ShouldNotBeHere) + .Event("Started", out Started) + .Event("ExplicitFilterStarted", out ExplicitFilterStarted) + .Event("Finish", out Finish) + .During(builder.Initial) + .When(Started, b => b + .Then(context => context.Instance.InitializeOnly = context.Data.InitializeOnly) + .If(context => context.Data.InitializeOnly, x => x.Then(context => Console.WriteLine("Initializing Only!"))) + .TransitionTo(Initialized) + ) + .During(builder.Initial) + .When(ExplicitFilterStarted, context => true, b => b + .IfElse(context => false, + binder => binder + .Then(context => Console.WriteLine("Should not be here!")) + .TransitionTo(ShouldNotBeHere), + binder => binder + .Then(context => context.Instance.ShouldBeCalled = true) + .Then(context => Console.WriteLine("Initializing Only!"))) + ) + .During(Running) + .When(Finish, b => b.Finalize()) + .WhenEnter(Initialized, b => b.If(context => !context.Instance.InitializeOnly, b => b.TransitionTo(Running))) + ); + } + + State Running; + State Initialized; + State ShouldNotBeHere; + Event Started; + Event ExplicitFilterStarted; + Event Finish; + + Instance _instance; + StateMachine _machine; + + class Instance + { + public bool InitializeOnly { get; set; } + public State CurrentState { get; set; } + + public bool ShouldBeCalled { get; set; } + } + + class Start + { + public bool InitializeOnly { get; set; } + } + + class StartedExplicitFilter { } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class Using_an_async_condition_in_a_state_machine + { + [Test] + public async Task Should_allow_if_condition_to_be_evaluated() + { + await _machine.RaiseEvent(_instance, ExplicitFilterStarted, new StartedExplicitFilter()); + + Assert.That(_instance.CurrentState, Is.Not.EqualTo(ShouldNotBeHere)); + } + + [Test] + public async Task Should_allow_the_condition_to_be_used() + { + await _machine.RaiseEvent(_instance, Started, new Start {InitializeOnly = true}); + + Assert.That(_instance.CurrentState, Is.EqualTo(Initialized)); + } + + [Test] + public async Task Should_evaluate_else_statement_when_if_condition__is_false() + { + await _machine.RaiseEvent(_instance, ExplicitFilterStarted, new StartedExplicitFilter()); + + Assert.That(_instance.ShouldBeCalled, Is.True); + } + + [Test] + public async Task Should_work() + { + await _machine.RaiseEvent(_instance, Started, new Start()); + + Assert.That(_instance.CurrentState, Is.EqualTo(Running)); + } + + State Running; + State Initialized; + State ShouldNotBeHere; + Event Started; + Event ExplicitFilterStarted; + Event Finish; + + Instance _instance; + StateMachine _machine; + + class Instance + { + public bool InitializeOnly { get; set; } + public State CurrentState { get; set; } + public bool ShouldBeCalled { get; set; } + } + + [SetUp] + public void Specifying_an_event_activity() + { + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .State("Initialized", out Initialized) + .State("ShouldNotBeHere", out ShouldNotBeHere) + .Event("Started", out Started) + .Event("ExplicitFilterStarted", out ExplicitFilterStarted) + .Event("Finish", out Finish) + .During(builder.Initial) + .When(Started, b => b + .Then(context => context.Instance.InitializeOnly = context.Data.InitializeOnly) + .IfAsync(context => Task.FromResult(context.Data.InitializeOnly), + x => x.Then(context => Console.WriteLine("Initializing Only!"))) + .TransitionTo(Initialized) + ) + .During(builder.Initial) + .When(ExplicitFilterStarted, context => true, b => b + .IfElseAsync(context => Task.FromResult(false), + binder => binder + .Then(context => Console.WriteLine("Should not be here!")) + .TransitionTo(ShouldNotBeHere), + binder => binder + .Then(context => context.Instance.ShouldBeCalled = true) + .Then(context => Console.WriteLine("Initializing Only!"))) + ) + .During(Running) + .When(Finish, b => b.Finalize()) + .WhenEnter(Initialized, b => b.IfAsync(context => Task.FromResult(!context.Instance.InitializeOnly), b => b.TransitionTo(Running))) + ); + } + + class Start + { + public bool InitializeOnly { get; set; } + } + + class StartedExplicitFilter { } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/DataActivity_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/DataActivity_Specs.cs new file mode 100644 index 00000000..e85c4d3c --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/DataActivity_Specs.cs @@ -0,0 +1,75 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class When_specifying_an_event_activity_with_data + { + [Test] + public void Should_capture_passed_value() + { + Assert.AreEqual(47, _instance.OtherValue); + } + + [Test] + public void Should_have_the_proper_value() + { + Assert.AreEqual("Hello", _instance.Value); + } + + [Test] + public void Should_transition_to_the_proper_state() + { + Assert.AreEqual(Running, _instance.CurrentState); + } + + State Running; + Event Initialized; + Event PassedValue; + + Instance _instance; + StateMachine _machine; + + [OneTimeSetUp] + public void Specifying_an_event_activity_with_data() + { + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .Event("Initialized", out Initialized) + .Event("PassedValue", out PassedValue) + .During(builder.Initial) + .When(Initialized, b => b + .Then(context => context.Instance.Value = context.Data.Value) + .TransitionTo(Running) + ) + .During(Running) + .When(PassedValue, b => b.Then(context => context.Instance.OtherValue = context.Data)) + ); + + _machine.RaiseEvent(_instance, Initialized, new Init + { + Value = "Hello" + }).Wait(); + + _machine.RaiseEvent(_instance, PassedValue, 47) + .Wait(); + } + + + class Instance + { + public string Value { get; set; } + public int OtherValue { get; set; } + public State CurrentState { get; set; } + } + + + class Init + { + public string Value { get; set; } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/Declarative_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Declarative_Specs.cs new file mode 100644 index 00000000..0ec0ab09 --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/Declarative_Specs.cs @@ -0,0 +1,69 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class When_an_instance_has_multiple_states + { + [Test] + public void Should_handle_both_states() + { + Assert.AreEqual(TopGreeted, _instance.Top); + Assert.AreEqual(BottomIgnored, _instance.Bottom); + } + + State TopGreeted; + Event TopInitialized; + State BottomIgnored; + Event BottomInitialized; + + MyState _instance; + StateMachine _top; + StateMachine _bottom; + + [OneTimeSetUp] + public void Specifying_an_event_activity_with_data() + { + _instance = new MyState(); + + _top = AutomatonymousStateMachine + .Build(builder => builder + .State("Greeted", out TopGreeted) + .Event("Initialized", out TopInitialized) + .InstanceState(b => b.Top) + .During(builder.Initial) + .When(TopInitialized, b => b.TransitionTo(TopGreeted)) + ); + _bottom = AutomatonymousStateMachine + .Build(builder => builder + .State("Ignored", out BottomIgnored) + .Event("Initialized", out BottomInitialized) + .InstanceState(b => b.Bottom) + .During(builder.Initial) + .When(BottomInitialized, b => b.TransitionTo(BottomIgnored)) + ); + + _top.RaiseEvent(_instance, TopInitialized, new Init + { + Value = "Hello" + }).Wait(); + + _bottom.RaiseEvent(_instance, BottomInitialized, new Init + { + Value = "Goodbye" + }).Wait(); + } + + class MyState + { + public State Top { get; set; } + public State Bottom { get; set; } + } + + class Init + { + public string Value { get; set; } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/Dependency_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Dependency_Specs.cs new file mode 100644 index 00000000..91f1d69e --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/Dependency_Specs.cs @@ -0,0 +1,123 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System; + using System.Threading.Tasks; + using Activities; + using GreenPipes; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class Having_a_dependency_available + { + [Test] + public void Should_capture_the_value() + { + Assert.AreEqual("79", _claim.Value); + } + + State Running; + Event Create; + ClaimAdjustmentInstance _claim; + StateMachine _machine; + + [OneTimeSetUp] + public void Specifying_an_event_activity() + { + _claim = new ClaimAdjustmentInstance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .Event("Create", out Create) + .InstanceState(x => x.CurrentState) + .During(builder.Initial) + .When(Create, b => b + .Execute(context => new CalculateValueActivity(new LocalCalculator())) + .Execute(context => new ActionActivity(x => { })) + .TransitionTo(Running) + ) + ); + + var data = new CreateClaim + { + X = 56, + Y = 23, + }; + + _machine.RaiseEvent(_claim, Create, data) + .Wait(); + } + + class ClaimAdjustmentInstance : + ClaimAdjustment + { + public State CurrentState { get; set; } + public string Value { get; set; } + } + + class CalculateValueActivity : + Activity + { + readonly CalculatorService _calculator; + + public CalculateValueActivity(CalculatorService calculator) + { + _calculator = calculator; + } + + public async Task Execute(BehaviorContext context, + Behavior next) + { + context.Instance.Value = _calculator.Add(context.Data.X, context.Data.Y); + } + + public Task Faulted(BehaviorExceptionContext context, + Behavior next) + where TException : Exception + { + return next.Faulted(context); + } + + public void Accept(StateMachineVisitor visitor) + { + visitor.Visit(this); + } + + public void Probe(ProbeContext context) + { + } + } + + interface ClaimAdjustment : + ClaimModel + { + State CurrentState { get; set; } + } + + interface ClaimModel + { + string Value { get; set; } + } + + class CreateClaim + { + public int X { get; set; } + public int Y { get; set; } + } + + interface CalculatorService + { + string Add(int x, int y); + } + + + class LocalCalculator : + CalculatorService + { + public string Add(int x, int y) + { + return (x + y).ToString(); + } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/EventLift_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/EventLift_Specs.cs new file mode 100644 index 00000000..1c7aa4e7 --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/EventLift_Specs.cs @@ -0,0 +1,83 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class When_using_an_event_raiser + { + [Test] + public void Should_raise_the_event() + { + Assert.AreEqual(Running, _instance.CurrentState); + } + + State Running; + Event Initialized; + Instance _instance; + StateMachine _machine; + + [OneTimeSetUp] + public void Specifying_an_event_activity() + { + _instance = new Instance(); + _machine = AutomatonymousStateMachine.Build(builder => builder + .State("Running", out Running) + .Event("Initialized", out Initialized) + .During(builder.Initial) + .When(Initialized, b => b.TransitionTo(Running)) + ); + + EventLift eventLift = _machine.CreateEventLift(Initialized); + + eventLift.Raise(_instance).Wait(); + } + + + class Instance + { + public State CurrentState { get; set; } + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class When_using_an_event_raiser_with_data + { + [Test] + public void Should_raise_the_event() + { + Assert.AreEqual(Running, _instance.CurrentState); + } + + State Running; + Event Initialized; + Instance _instance; + StateMachine _machine; + + [OneTimeSetUp] + public void Specifying_an_event_activity() + { + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .Event("Initialized", out Initialized) + .InstanceState(b => b.CurrentState) + .During(builder.Initial) + .When(Initialized, b => b.TransitionTo(Running)) + ); + + EventLift eventLift = _machine.CreateEventLift(Initialized); + + eventLift.Raise(_instance, new Init()).Wait(); + } + + class Init { } + + class Instance + { + public State CurrentState { get; set; } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/EventObservable_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/EventObservable_Specs.cs new file mode 100644 index 00000000..4bf63b1b --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/EventObservable_Specs.cs @@ -0,0 +1,50 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class When_an_event_is_raised_on_an_instance + { + [Test] + public void Should_have_raised_the_initialized_event() + { + Assert.AreEqual(Initialized, _observer.Events[0].Event); + } + + [Test] + public void Should_raise_the_event() + { + Assert.AreEqual(1, _observer.Events.Count); + } + + State Running; + Event Initialized; + Instance _instance; + StateMachine _machine; + EventRaisedObserver _observer; + + [OneTimeSetUp] + public void Specifying_an_event_activity() + { + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .Event("Initialized", out Initialized) + .State("Running", out Running) + .During(builder.Initial) + .When(Initialized, b => b.TransitionTo(Running)) + ); + _observer = new EventRaisedObserver(); + + using (IDisposable subscription = _machine.ConnectEventObserver(Initialized, _observer)) + _machine.RaiseEvent(_instance, Initialized).Wait(); + } + + class Instance + { + public State CurrentState { get; set; } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/Event_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Event_Specs.cs new file mode 100644 index 00000000..6b32cecb --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/Event_Specs.cs @@ -0,0 +1,83 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System; + using Events; + using GreenPipes; + using GreenPipes.Introspection; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class When_an_event_is_declared + { + [Test] + public void It_should_capture_a_simple_event_name() + { + Assert.AreEqual("Hello", Hello.Name); + } + + [Test] + public void It_should_capture_the_data_event_name() + { + Assert.AreEqual("EventA", EventA.Name); + } + + [Test] + public void It_should_create_the_event_for_the_value_type() + { + Assert.IsInstanceOf>(EventInt); + } + + [Test] + public void It_should_create_the_proper_event_type_for_data_events() + { + Assert.IsInstanceOf>(EventA); + } + + [Test] + public void It_should_create_the_proper_event_type_for_simple_events() + { + Assert.IsInstanceOf(Hello); + } + + [Test] + public void Should_return_a_wonderful_breakdown_of_the_guts_inside_it() + { + ProbeResult result = _machine.GetProbeResult(); + + Console.WriteLine(result.ToJsonString()); + } + + Event Hello; + Event EventA; + Event EventInt; + StateMachine _machine; + + class Instance + { + public State CurrentState { get; set; } + } + + + [OneTimeSetUp] + public void A_state_is_declared() + { + _machine = AutomatonymousStateMachine + .Build(builder => builder + .Event("Hello", out Hello) + .Event("EventA", out EventA) + .Event("EventInt", out EventInt) + ); + } + + class A { } + + class TestStateMachine : + AutomatonymousStateMachine + { + public Event Hello { get; private set; } + public Event EventA { get; private set; } + public Event EventInt { get; private set; } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/Exception_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Exception_Specs.cs new file mode 100644 index 00000000..3fe69985 --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/Exception_Specs.cs @@ -0,0 +1,454 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System; + using System.Threading.Tasks; + using GreenPipes; + using GreenPipes.Introspection; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class When_an_action_throws_an_exception + { + [Test] + public void Should_capture_the_exception_message() + { + Assert.AreEqual("Boom!", _instance.ExceptionMessage); + } + + [Test] + public void Should_capture_the_exception_type() + { + Assert.AreEqual(typeof(ApplicationException), _instance.ExceptionType); + } + + [Test] + public void Should_have_called_the_async_if_block() + { + Assert.IsTrue(_instance.CalledThenClauseAsync); + } + + [Test] + public void Should_have_called_the_async_then_block() + { + Assert.IsTrue(_instance.ThenAsyncShouldBeCalled); + } + + [Test] + public void Should_have_called_the_exception_handler() + { + Assert.AreEqual(Failed, _instance.CurrentState); + } + + [Test] + public void Should_have_called_the_false_async_condition_else_block() + { + Assert.IsTrue(_instance.ElseAsyncShouldBeCalled); + } + + [Test] + public void Should_have_called_the_false_condition_else_block() + { + Assert.IsTrue(_instance.ElseShouldBeCalled); + } + + [Test] + public void Should_have_called_the_first_action() + { + Assert.IsTrue(_instance.Called); + } + + [Test] + public void Should_have_called_the_first_if_block() + { + Assert.IsTrue(_instance.CalledThenClause); + } + + [Test] + public void Should_not_have_called_the_false_async_condition_then_block() + { + Assert.IsFalse(_instance.ThenAsyncShouldNotBeCalled); + } + + [Test] + public void Should_not_have_called_the_false_condition_then_block() + { + Assert.IsFalse(_instance.ThenShouldNotBeCalled); + } + + [Test] + public void Should_not_have_called_the_regular_exception() + { + Assert.IsFalse(_instance.ShouldNotBeCalled); + } + + [Test] + public void Should_not_have_called_the_second_action() + { + Assert.IsTrue(_instance.NotCalled); + } + + + [Test] + public void Should_return_a_wonderful_breakdown_of_the_guts_inside_it() + { + ProbeResult result = _machine.GetProbeResult(); + + Console.WriteLine(result.ToJsonString()); + } + + State Failed; + Event Initialized; + Instance _instance; + StateMachine _machine; + + [OneTimeSetUp] + public void Specifying_an_event_activity() + { + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Failed", out Failed) + .Event("Initialized", out Initialized) + .During(builder.Initial) + .When(Initialized, b => b + .Then(context => context.Instance.Called = true) + .Then(_ => { throw new ApplicationException("Boom!"); }) + .Then(context => context.Instance.NotCalled = false) + .Catch(ex => ex + .If(context => true, c => c + .Then(context => context.Instance.CalledThenClause = true) + ) + .IfAsync(context => Task.FromResult(true), c => c + .Then(context => context.Instance.CalledThenClauseAsync = true) + ) + .IfElse(context => false, + c => c.Then(context => context.Instance.ThenShouldNotBeCalled = true), + c => c.Then(context => context.Instance.ElseShouldBeCalled = true) + ) + .IfElseAsync(context => Task.FromResult(false), + c => c.Then(context => context.Instance.ThenAsyncShouldNotBeCalled = true), + c => c.Then(context => context.Instance.ElseAsyncShouldBeCalled = true) + ) + .Then(context => + { + context.Instance.ExceptionMessage = context.Exception.Message; + context.Instance.ExceptionType = context.Exception.GetType(); + }) + .ThenAsync(context => + { + context.Instance.ThenAsyncShouldBeCalled = true; + return Task.CompletedTask; + }) + .TransitionTo(Failed) + ) + .Catch(ex => ex + .Then(context => context.Instance.ShouldNotBeCalled = true) + ) + ) + ); + + _machine.RaiseEvent(_instance, Initialized).Wait(); + } + + + class Instance + { + public Instance() + { + NotCalled = true; + } + + public bool Called { get; set; } + public bool NotCalled { get; set; } + public Type ExceptionType { get; set; } + public string ExceptionMessage { get; set; } + public State CurrentState { get; set; } + + public bool ShouldNotBeCalled { get; set; } + + public bool CalledThenClause { get; set; } + public bool CalledThenClauseAsync { get; set; } + + public bool ThenShouldNotBeCalled { get; set; } + public bool ElseShouldBeCalled { get; set; } + public bool ThenAsyncShouldNotBeCalled { get; set; } + public bool ElseAsyncShouldBeCalled { get; set; } + + public bool ThenAsyncShouldBeCalled { get; set; } + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class When_the_exception_does_not_match_the_type + { + [Test] + public void Should_capture_the_exception_message() + { + Assert.AreEqual("Boom!", _instance.ExceptionMessage); + } + + [Test] + public void Should_capture_the_exception_type() + { + Assert.AreEqual(typeof(ApplicationException), _instance.ExceptionType); + } + + [Test] + public void Should_have_called_the_exception_handler() + { + Assert.AreEqual(Failed, _instance.CurrentState); + } + + [Test] + public void Should_have_called_the_first_action() + { + Assert.IsTrue(_instance.Called); + } + + + [Test] + public void Should_not_have_called_the_second_action() + { + Assert.IsTrue(_instance.NotCalled); + } + + State Failed; + Event Initialized; + Instance _instance; + StateMachine _machine; + + [OneTimeSetUp] + public void Specifying_an_event_activity() + { + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Failed", out Failed) + .Event("Initialized", out Initialized) + .InstanceState(b => b.CurrentState) + .During(builder.Initial) + .When(Initialized, b => b + .Then(context => context.Instance.Called = true) + .Then(_ => { throw new ApplicationException("Boom!"); }) + .Then(context => context.Instance.NotCalled = false) + .Catch(ex => ex + .Then(context => + { + context.Instance.ExceptionMessage = context.Exception.Message; + context.Instance.ExceptionType = context.Exception.GetType(); + }) + .TransitionTo(Failed) + ) + ) + ); + + _machine.RaiseEvent(_instance, Initialized).Wait(); + } + + + class Instance + { + public Instance() + { + NotCalled = true; + } + + public bool Called { get; set; } + public bool NotCalled { get; set; } + public Type ExceptionType { get; set; } + public string ExceptionMessage { get; set; } + public State CurrentState { get; set; } + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class When_the_exception_is_caught + { + [Test] + public void Should_have_called_the_subsequent_action() + { + Assert.IsTrue(_instance.Called); + } + + Instance _instance; + StateMachine _machine; + + [OneTimeSetUp] + public void Specifying_an_event_activity() + { + Event Initialized = null; + + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .Event("Initialized", out Initialized) + .State("Failed", out State Failed) + .InstanceState(b => b.CurrentState) + .During(builder.Initial) + .When(Initialized, b => b + .Then(_ => { throw new ApplicationException("Boom!"); }) + .Catch(ex => ex) + .Then(context => context.Instance.Called = true) + ) + ); + + _machine.RaiseEvent(_instance, Initialized).Wait(); + } + + + class Instance + { + public bool Called { get; set; } + public State CurrentState { get; set; } + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class When_an_action_throws_an_exception_on_data_events + { + [Test] + public void Should_capture_the_exception_message() + { + Assert.AreEqual("Boom!", _instance.ExceptionMessage); + } + + [Test] + public void Should_capture_the_exception_type() + { + Assert.AreEqual(typeof(ApplicationException), _instance.ExceptionType); + } + + [Test] + public void Should_have_called_the_async_if_block() + { + Assert.IsTrue(_instance.CalledSecondThenClause); + } + + [Test] + public void Should_have_called_the_exception_handler() + { + Assert.AreEqual(Failed, _instance.CurrentState); + } + + [Test] + public void Should_have_called_the_false_async_condition_else_block() + { + Assert.IsTrue(_instance.ElseAsyncShouldBeCalled); + } + + [Test] + public void Should_have_called_the_false_condition_else_block() + { + Assert.IsTrue(_instance.ElseShouldBeCalled); + } + + [Test] + public void Should_have_called_the_first_action() + { + Assert.IsTrue(_instance.Called); + } + + [Test] + public void Should_have_called_the_first_if_block() + { + Assert.IsTrue(_instance.CalledThenClause); + } + + [Test] + public void Should_not_have_called_the_false_async_condition_then_block() + { + Assert.IsFalse(_instance.ThenAsyncShouldNotBeCalled); + } + + [Test] + public void Should_not_have_called_the_false_condition_then_block() + { + Assert.IsFalse(_instance.ThenShouldNotBeCalled); + } + + [Test] + public void Should_not_have_called_the_second_action() + { + Assert.IsTrue(_instance.NotCalled); + } + + State Failed; + Event Initialized; + Instance _instance; + StateMachine _machine; + + [OneTimeSetUp] + public void Specifying_an_event_activity() + { + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Failed", out Failed) + .Event("Initialized", out Initialized) + .InstanceState(b => b.CurrentState) + .During(builder.Initial) + .When(Initialized, b => b + .Then(context => context.Instance.Called = true) + .Then(_ => { throw new ApplicationException("Boom!"); }) + .Then(context => context.Instance.NotCalled = false) + .Catch(ex => ex + .If(context => true, b => b + .Then(context => context.Instance.CalledThenClause = true) + ) + .IfAsync(context => Task.FromResult(true), b => b + .Then(context => context.Instance.CalledSecondThenClause = true) + ) + .IfElse(context => false, + b => b.Then(context => context.Instance.ThenShouldNotBeCalled = true), + b => b.Then(context => context.Instance.ElseShouldBeCalled = true) + ) + .IfElseAsync(context => Task.FromResult(false), + b => b.Then(context => context.Instance.ThenAsyncShouldNotBeCalled = true), + b => b.Then(context => context.Instance.ElseAsyncShouldBeCalled = true) + ) + .Then(context => + { + context.Instance.ExceptionMessage = context.Exception.Message; + context.Instance.ExceptionType = context.Exception.GetType(); + }) + .TransitionTo(Failed) + ) + ) + ); + + _machine.RaiseEvent(_instance, Initialized, new Init()).Wait(); + } + + + class Instance + { + public Instance() + { + NotCalled = true; + } + + public bool Called { get; set; } + public bool NotCalled { get; set; } + public Type ExceptionType { get; set; } + public string ExceptionMessage { get; set; } + public State CurrentState { get; set; } + + public bool CalledThenClause { get; set; } + public bool CalledSecondThenClause { get; set; } + + public bool ThenShouldNotBeCalled { get; set; } + public bool ElseShouldBeCalled { get; set; } + public bool ThenAsyncShouldNotBeCalled { get; set; } + public bool ElseAsyncShouldBeCalled { get; set; } + } + + + class Init + { + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/Faulted_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Faulted_Specs.cs new file mode 100644 index 00000000..0565c0a7 --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/Faulted_Specs.cs @@ -0,0 +1,140 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System; + using System.Threading.Tasks; + using Activities; + using GreenPipes; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class Having_an_activity_with_faulted_handler + { + [Test] + public void Should_capture_the_value() + { + var data = new CreateClaim + { + X = 56, + Y = 23, + }; + + Assert.That(async () => await _machine.RaiseEvent(_claim, Create, data), Throws.TypeOf()); + + Assert.AreEqual(default, _claim.Value); + } + + Event Create; + ClaimAdjustmentInstance _claim; + StateMachine _machine; + + [OneTimeSetUp] + public void Specifying_an_event_activity() + { + State Running = null; + + _claim = new ClaimAdjustmentInstance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .Event("Create", out Create) + .InstanceState(b => b.CurrentState) + .During(builder.Initial) + .When(Create, b => b + .Execute(context => new CalculateValueActivity(new LocalCalculator())) + .Execute(context => new ActionActivity(x => throw new Exception())) + .TransitionTo(Running)) + ); + } + + + class ClaimAdjustmentInstance : + ClaimAdjustment + { + public State CurrentState { get; set; } + public string Value { get; set; } + } + + + class CalculateValueActivity : + Activity + { + readonly CalculatorService _calculator; + + public CalculateValueActivity(CalculatorService calculator) + { + _calculator = calculator; + } + + public async Task Execute(BehaviorContext context, + Behavior next) + { + var originalValue = context.Instance.Value; + try + { + context.Instance.Value = _calculator.Add(context.Data.X, context.Data.Y); + + await next.Execute(context); + } + catch (Exception) + { + context.Instance.Value = originalValue; + + throw; + } + } + + public Task Faulted(BehaviorExceptionContext context, + Behavior next) + where TException : Exception + { + return next.Faulted(context); + } + + public void Accept(StateMachineVisitor visitor) + { + visitor.Visit(this); + } + + public void Probe(ProbeContext context) + { + } + } + + + interface ClaimAdjustment : + ClaimModel + { + State CurrentState { get; set; } + } + + + interface ClaimModel + { + string Value { get; set; } + } + + + class CreateClaim + { + public int X { get; set; } + public int Y { get; set; } + } + + + interface CalculatorService + { + string Add(int x, int y); + } + + + class LocalCalculator : + CalculatorService + { + public string Add(int x, int y) + { + return (x + y).ToString(); + } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/FilterExpression_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/FilterExpression_Specs.cs new file mode 100644 index 00000000..ce7010ee --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/FilterExpression_Specs.cs @@ -0,0 +1,72 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System.Threading.Tasks; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class When_specifying_a_conditional_event_activity + { + [Test] + public async Task Should_transition_to_the_proper_state() + { + State True = null; + State False = null; + Event Thing = null; + + var instance = new Instance(); + var machine = AutomatonymousStateMachine + .Build(builder => builder + .State("True", out True) + .State("False", out False) + .Event("Thing", out Thing) + .InstanceState(b => b.CurrentState) + .During(builder.Initial) + .When(Thing, context => context.Data.Condition, b => b.TransitionTo(True)) + .When(Thing, context => !context.Data.Condition, b => b.TransitionTo(False)) + ); + + await machine.RaiseEvent(instance, Thing, new Data {Condition = true}); + Assert.AreEqual(True, instance.CurrentState); + + // reset + instance.CurrentState = machine.Initial; + + await machine.RaiseEvent(instance, Thing, new Data {Condition = false}); + Assert.AreEqual(False, instance.CurrentState); + } + + + class Instance + { + public State CurrentState { get; set; } + } + + + class InstanceStateMachine : + AutomatonymousStateMachine + { + public InstanceStateMachine() + { + InstanceState(x => x.CurrentState); + + During(Initial, + When(Thing, context => context.Data.Condition) + .TransitionTo(True), + When(Thing, context => !context.Data.Condition) + .TransitionTo(False)); + } + + public State True { get; private set; } + public State False { get; private set; } + + public Event Thing { get; private set; } + } + + + class Data + { + public bool Condition { get; set; } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/Group_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Group_Specs.cs new file mode 100644 index 00000000..a0420c6a --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/Group_Specs.cs @@ -0,0 +1,181 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System; + using NUnit.Framework; + + // NOTE: This test was pulled from the non-dynamic set; Seems incomplete with the commented out code below. + [TestFixture(Category = "Dynamic Modify")] + public class Declaring_groups_in_a_state_machine + { + [Test] + public void Should_allow_parallel_execution_of_events() + { + } + + [Test] + public void Should_have_captured_initial_data() + { + Assert.AreEqual("Audi", _instance.VehicleMake); + Assert.AreEqual("A6", _instance.VehicleModel); + } + + State BeingServiced; + Event VehicleArrived; + + StateMachine _machine; + PitStopInstance _instance; + + [OneTimeSetUp] + public void Setup() + { + _instance = new PitStopInstance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("BeingServiced", out BeingServiced) + .Event("VehicleArrived", out VehicleArrived) + .InstanceState(b => b.OverallState) + .During(builder.Initial) + .When(VehicleArrived, b => b + .Then(context => + { + context.Instance.VehicleMake = context.Data.Make; + context.Instance.VehicleModel = context.Data.Model; + }) + .TransitionTo(BeingServiced)) + ); + + var vehicle = new Vehicle + { + Make = "Audi", + Model = "A6", + }; + + _machine.RaiseEvent(_instance, VehicleArrived, vehicle).Wait(); + } + + + class PitStopInstance + { + public State OverallState { get; private set; } + public State FuelState { get; private set; } + public State OilState { get; private set; } + + public string VehicleMake { get; set; } + public string VehicleModel { get; set; } + + public decimal FuelGallons { get; set; } + public decimal FuelPricePerGallon { get; set; } + public decimal FuelCost { get; set; } + + public decimal OilQuarts { get; set; } + public decimal OilPricePerQuart { get; set; } + public decimal OilCost { get; set; } + } + + // NOTE: Left in place due to the incompleteness of this test. +// class PitStop : +// AutomatonymousStateMachine +// { +// public PitStop() +// { +// InstanceState(x => x.OverallState); + +// During(Initial, +// When(VehicleArrived) +// .Then(context => +// { +// context.Instance.VehicleMake = context.Data.Make; +// context.Instance.VehicleModel = context.Data.Model; +// }) +// .TransitionTo(BeingServiced) +//// .RunParallel(p => +//// { +//// p.Start(x => x.BeginFilling); +//// p.Start(x => x.BeginChecking); +//// })) +// ); +// } + +// public State BeingServiced { get; private set; } + +// public Event VehicleArrived { get; private set; } +// } + + + class FillTank : + AutomatonymousStateMachine + { + public FillTank() + { + InstanceState(x => x.FuelState); + + Initially( + When(Initial.Enter) + .TransitionTo(Filling)); + + During(Filling, + When(Full) + .Then(context => + { + context.Instance.FuelGallons = context.Data.Gallons; + context.Instance.FuelPricePerGallon = context.Data.PricePerGallon; + context.Instance.FuelCost = context.Data.Gallons * context.Data.PricePerGallon; + }) + .Finalize()); + } + + public State Filling { get; private set; } + + public Event Full { get; private set; } + } + + + class CheckOil : + AutomatonymousStateMachine + { + public CheckOil() + { + InstanceState(x => x.OilState); + + Initially( + When(Initial.Enter) + .TransitionTo(AddingOil)); + + During(AddingOil, + When(Done) + .Then(context => + { + context.Instance.OilQuarts = context.Data.Quarts; + context.Instance.OilPricePerQuart = context.Data.PricePerQuart; + context.Instance.OilCost = Math.Ceiling(context.Data.Quarts) * context.Data.PricePerQuart; + }) + .Finalize()); + } + + public State AddingOil { get; private set; } + + public Event Done { get; private set; } + } + + + class FuelDispensed + { + public decimal Gallons { get; set; } + public decimal PricePerGallon { get; set; } + } + + + class OilAdded + { + public decimal Quarts { get; set; } + public decimal PricePerQuart { get; set; } + } + + + class Vehicle + { + public string Make { get; set; } + public string Model { get; set; } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/InstanceLift_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/InstanceLift_Specs.cs new file mode 100644 index 00000000..6cbf2b91 --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/InstanceLift_Specs.cs @@ -0,0 +1,121 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class When_using_an_instance_lift + { + [Test] + public void Should_raise_the_event() + { + Assert.AreEqual(Running, _instance.CurrentState); + } + + State Running; + Event Initialized; + Instance _instance; + StateMachine _machine; + + [OneTimeSetUp] + public void Specifying_an_event_activity() + { + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .Event("Initialized", out Initialized) + .During(builder.Initial) + .When(Initialized, b => b.TransitionTo(Running)) + ); + + InstanceLift> instanceLift = _machine.CreateInstanceLift(_instance); + + instanceLift.Raise(Initialized) + .Wait(); + } + + + class Instance + { + public State CurrentState { get; set; } + } + + + class InstanceStateMachine : + AutomatonymousStateMachine + { + public InstanceStateMachine() + { + During(Initial, + When(Initialized) + .TransitionTo(Running)); + } + + public State Running { get; private set; } + + public Event Initialized { get; private set; } + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class When_using_an_instance_lift_with_data + { + [Test] + public void Should_raise_the_event() + { + Assert.AreEqual(Running, _instance.CurrentState); + } + + State Running; + Event Initialized; + Instance _instance; + StateMachine _machine; + + [OneTimeSetUp] + public void Specifying_an_event_activity() + { + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .Event("Initialized", out Initialized) + .During(builder.Initial) + .When(Initialized, b => b.TransitionTo(Running)) + ); + + InstanceLift> instanceLift = _machine.CreateInstanceLift(_instance); + + instanceLift.Raise(Initialized, new Init()) + .Wait(); + } + + + class Init + { + } + + + class Instance + { + public State CurrentState { get; set; } + } + + + class InstanceStateMachine : + AutomatonymousStateMachine + { + public InstanceStateMachine() + { + During(Initial, + When(Initialized) + .TransitionTo(Running)); + } + + public State Running { get; private set; } + + public Event Initialized { get; private set; } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/Introspection_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Introspection_Specs.cs new file mode 100644 index 00000000..e5fbf9f0 --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/Introspection_Specs.cs @@ -0,0 +1,134 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using GreenPipes; + using GreenPipes.Introspection; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class Introspection_Specs + { + [Test] + public void Should_return_a_wonderful_breakdown_of_the_guts_inside_it() + { + ProbeResult result = _machine.GetProbeResult(); + + Console.WriteLine(result.ToJsonString()); + } + + [Test] + public void The_machine_shoud_report_its_instance_type() + { + Assert.AreEqual(typeof(Instance), ((StateMachine)_machine).InstanceType); + } + + [Test] + public void The_machine_should_expose_all_events() + { + var events = _machine.Events.ToList(); + + Assert.AreEqual(4, events.Count); + Assert.Contains(Ignored, events); + Assert.Contains(Handshake, events); + Assert.Contains(Hello, events); + Assert.Contains(YelledAt, events); + } + + [Test] + public void The_machine_should_expose_all_states() + { + Assert.AreEqual(5, ((StateMachine)_machine).States.Count()); + Assert.Contains(_machine.Initial, _machine.States.ToList()); + Assert.Contains(_machine.Final, _machine.States.ToList()); + Assert.Contains(Greeted, _machine.States.ToList()); + Assert.Contains(Loved, _machine.States.ToList()); + Assert.Contains(Pissed, _machine.States.ToList()); + } + + [Test] + public async Task The_next_events_should_be_known() + { + List events = (await _machine.NextEvents(_instance)).ToList(); + Assert.AreEqual(3, events.Count); + } + + Event Ignored; + Event Handshake; + Event Hello; + Event YelledAt; + State Greeted; + State Pissed; + State Loved; + Instance _instance; + StateMachine _machine; + + [OneTimeSetUp] + public void A_state_is_declared() + { + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .Event("Ignored", out Ignored) + .Event("Handshake", out Handshake) + .Event("Hello", out Hello) + .Event("YelledAt", out YelledAt) + .State("Greeted", out Greeted) + .State("Pissed", out Pissed) + .State("Loved", out Loved) + .Initially() + .When(Hello, b => b.TransitionTo(Greeted)) + .During(Greeted) + .When(Handshake, b => b.TransitionTo(Loved)) + .When(Ignored, b => b.TransitionTo(Pissed)) + .WhenEnter(Greeted, b=> b.Then(context => { })) + .DuringAny() + .When(YelledAt, b => b.TransitionTo(builder.Final)) + ); + + _machine.RaiseEvent(_instance, Hello); + } + + class A { } + class B { } + + class Instance + { + public State CurrentState { get; set; } + } + + + class TestStateMachine : + AutomatonymousStateMachine + { + public TestStateMachine() + { + Initially( + When(Hello) + .TransitionTo(Greeted)); + + During(Greeted, + When(Handshake) + .TransitionTo(Loved), + When(Ignored) + .TransitionTo(Pissed)); + + WhenEnter(Greeted, x => x.Then(context => { })); + + DuringAny(When(YelledAt).TransitionTo(Final)); + } + + public State Greeted { get; set; } + public State Pissed { get; set; } + public State Loved { get; set; } + + public Event Hello { get; private set; } + public Event YelledAt { get; private set; } + public Event Handshake { get; private set; } + public Event Ignored { get; private set; } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/Models/DynamicModifyTestStateMachine.cs b/src/Automatonymous.Tests/Dynamic Modify/Models/DynamicModifyTestStateMachine.cs new file mode 100644 index 00000000..406aa512 --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/Models/DynamicModifyTestStateMachine.cs @@ -0,0 +1,8 @@ +namespace Automatonymous.Tests.DynamicModify +{ + internal class DynamicModifyTestStateMachine : + AutomatonymousStateMachine + where TInstance : class + { + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/Observable_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Observable_Specs.cs new file mode 100644 index 00000000..f8379e8c --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/Observable_Specs.cs @@ -0,0 +1,375 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System; + using GreenPipes; + using GreenPipes.Introspection; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class Observing_state_machine_instance_state_changes + { + [Test] + public void Should_have_first_moved_to_initial() + { + Assert.AreEqual(null, _observer.Events[0].Previous); + Assert.AreEqual(_machine.Initial, _observer.Events[0].Current); + } + + [Test] + public void Should_have_second_switched_to_running() + { + Assert.AreEqual(_machine.Initial, _observer.Events[1].Previous); + Assert.AreEqual(Running, _observer.Events[1].Current); + } + + [Test] + public void Should_raise_the_event() + { + Assert.AreEqual(3, _observer.Events.Count); + } + + State Running; + Instance _instance; + StateMachine _machine; + StateChangeObserver _observer; + + [OneTimeSetUp] + public void Specifying_an_event_activity() + { + Event Initialized = null; + Event Finish = null; + + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .Event("Initialized", out Initialized) + .Event("Finish", out Finish) + .During(builder.Initial) + .When(Initialized, b => b.TransitionTo(Running)) + .During(Running) + .When(Finish, b => b.Finalize()) + ); + _observer = new StateChangeObserver(); + + using (IDisposable subscription = _machine.ConnectStateObserver(_observer)) + { + _machine.RaiseEvent(_instance, Initialized).Wait(); + _machine.RaiseEvent(_instance, Finish).Wait(); + } + } + + + class Instance + { + public State CurrentState { get; set; } + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class Observing_events_with_substates + { + [Test] + public void Should_have_all_events() + { + Assert.AreEqual(2, _eventObserver.Events.Count); + } + + [Test] + public void Should_have_first_moved_to_initial() + { + Assert.AreEqual(null, _observer.Events[0].Previous); + Assert.AreEqual(_machine.Initial, _observer.Events[0].Current); + } + + [Test] + public void Should_have_fourth_switched_to_finished() + { + Assert.AreEqual(Resting, _observer.Events[3].Previous); + Assert.AreEqual(_machine.Final, _observer.Events[3].Current); + } + + [Test] + public void Should_have_second_switched_to_running() + { + Assert.AreEqual(_machine.Initial, _observer.Events[1].Previous); + Assert.AreEqual(Running, _observer.Events[1].Current); + } + + [Test] + public void Should_have_third_switched_to_resting() + { + Assert.AreEqual(Running, _observer.Events[2].Previous); + Assert.AreEqual(Resting, _observer.Events[2].Current); + } + + [Test] + public void Should_have_transition_1() + { + Assert.AreEqual("Initialized", _eventObserver.Events[0].Event.Name); + } + + [Test] + public void Should_have_transition_2() + { + Assert.AreEqual("LegCramped", _eventObserver.Events[1].Event.Name); + } + + [Test] + public void Should_raise_the_event() + { + Assert.AreEqual(4, _observer.Events.Count); + } + + + [Test] + public void Should_return_a_wonderful_breakdown_of_the_guts_inside_it() + { + ProbeResult result = _machine.GetProbeResult(); + + Console.WriteLine(result.ToJsonString()); + } + + State Resting; + State Running; + Instance _instance; + StateMachine _machine; + StateChangeObserver _observer; + EventRaisedObserver _eventObserver; + + [OneTimeSetUp] + public void Specifying_an_event_activity() + { + Event Initialized = null; + Event LegCramped = null; + Event Finish = null; + + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .State("Resting", out Resting) + .Event("Initialized", out Initialized) + .Event("LegCramped", out LegCramped) + .Event("Finish", out Finish) + .SubState(Resting, Running) + .During(builder.Initial) + .When(Initialized, b => b.TransitionTo(Running)) + .During(Running) + .When(LegCramped, b => b.TransitionTo(Resting)) + .When(Finish, b => b.Finalize()) + .WhenEnter(Running, b => b.Then(context => { })) + .WhenLeave(Running, b => b.Then(context => { })) + .BeforeEnter(Running, b => b.Then(context => { })) + .AfterLeave(Running, b => b.Then(context => { })) + ); + _observer = new StateChangeObserver(); + _eventObserver = new EventRaisedObserver(); + + using (IDisposable subscription = _machine.ConnectStateObserver(_observer)) + using (IDisposable beforeEnterSub = _machine.ConnectEventObserver(Initialized, _eventObserver)) + using (IDisposable afterLeaveSub = _machine.ConnectEventObserver(LegCramped, _eventObserver)) + { + _machine.RaiseEvent(_instance, Initialized).Wait(); + _machine.RaiseEvent(_instance, LegCramped).Wait(); + _machine.RaiseEvent(_instance, Finish).Wait(); + } + } + + + class Instance + { + public State CurrentState { get; set; } + } + + + class InstanceStateMachine : + AutomatonymousStateMachine + { + public InstanceStateMachine() + { + SubState(() => Resting, Running); + + During(Initial, + When(Initialized) + .TransitionTo(Running)); + + During(Running, + When(LegCramped) + .TransitionTo(Resting), + When(Finish) + .Finalize()); + + WhenEnter(Running, x => x.Then(context => { })); + WhenLeave(Running, x => x.Then(context => { })); + BeforeEnter(Running, x => x.Then(context => { })); + AfterLeave(Running, x => x.Then(context => { })); + } + + public State Running { get; private set; } + public State Resting { get; private set; } + public Event Initialized { get; private set; } + public Event LegCramped { get; private set; } + public Event Finish { get; private set; } + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class Observing_events_with_substates_part_deux + { + [Test] + public void Should_have_all_events() + { + Assert.AreEqual(2, _eventObserver.Events.Count); + } + + [Test] + public void Should_have_fifth_switched_to_finished() + { + Assert.AreEqual(Running, _observer.Events[4].Previous); + Assert.AreEqual(_machine.Final, _observer.Events[4].Current); + } + + [Test] + public void Should_have_first_moved_to_initial() + { + Assert.AreEqual(null, _observer.Events[0].Previous); + Assert.AreEqual(_machine.Initial, _observer.Events[0].Current); + } + + [Test] + public void Should_have_fourth_switched_to_finished() + { + Assert.AreEqual(Resting, _observer.Events[3].Previous); + Assert.AreEqual(Running, _observer.Events[3].Current); + } + + [Test] + public void Should_have_second_switched_to_running() + { + Assert.AreEqual(_machine.Initial, _observer.Events[1].Previous); + Assert.AreEqual(Running, _observer.Events[1].Current); + } + + [Test] + public void Should_have_third_switched_to_resting() + { + Assert.AreEqual(Running, _observer.Events[2].Previous); + Assert.AreEqual(Resting, _observer.Events[2].Current); + } + + [Test] + public void Should_have_transition_1() + { + Assert.AreEqual("Running.BeforeEnter", _eventObserver.Events[0].Event.Name); + } + + [Test] + public void Should_have_transition_2() + { + Assert.AreEqual("Running.AfterLeave", _eventObserver.Events[1].Event.Name); + } + + [Test] + public void Should_raise_the_event() + { + Assert.AreEqual(5, _observer.Events.Count); + } + + State Resting; + State Running; + Instance _instance; + StateMachine _machine; + StateChangeObserver _observer; + EventRaisedObserver _eventObserver; + + [OneTimeSetUp] + public void Specifying_an_event_activity() + { + Event Initialized = null; + Event LegCramped = null; + Event Finish = null; + Event Recovered = null; + + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .State("Resting", out Resting) + .Event("Initialized", out Initialized) + .Event("LegCramped", out LegCramped) + .Event("Finish", out Finish) + .Event("Recovered", out Recovered) + .SubState(Resting, Running) + .During(builder.Initial) + .When(Initialized, b => b.TransitionTo(Running)) + .During(Running) + .When(LegCramped, b => b.TransitionTo(Resting)) + .When(Finish, b => b.Finalize()) + .During(Resting) + .When(Recovered, b => b.TransitionTo(Running)) + .WhenEnter(Running, b => b.Then(context => { })) + .WhenLeave(Running, b => b.Then(context => { })) + .BeforeEnter(Running, b => b.Then(context => { })) + .AfterLeave(Running, b => b.Then(context => { })) + ); + _observer = new StateChangeObserver(); + _eventObserver = new EventRaisedObserver(); + + using (IDisposable subscription = _machine.ConnectStateObserver(_observer)) + using (IDisposable beforeEnterSub = _machine.ConnectEventObserver(Running.BeforeEnter, _eventObserver)) + using (IDisposable afterLeaveSub = _machine.ConnectEventObserver(Running.AfterLeave, _eventObserver)) + { + _machine.RaiseEvent(_instance, Initialized).Wait(); + _machine.RaiseEvent(_instance, LegCramped).Wait(); + _machine.RaiseEvent(_instance, Recovered).Wait(); + _machine.RaiseEvent(_instance, Finish).Wait(); + } + } + + + class Instance + { + public State CurrentState { get; set; } + } + + + class InstanceStateMachine : + AutomatonymousStateMachine + { + public InstanceStateMachine() + { + SubState(() => Resting, Running); + + During(Initial, + When(Initialized) + .TransitionTo(Running)); + + During(Running, + When(LegCramped) + .TransitionTo(Resting), + When(Finish) + .Finalize()); + + During(Resting, + When(Recovered) + .TransitionTo(Running)); + + WhenEnter(Running, x => x.Then(context => { })); + WhenLeave(Running, x => x.Then(context => { })); + BeforeEnter(Running, x => x.Then(context => { })); + AfterLeave(Running, x => x.Then(context => { })); + } + + public State Running { get; private set; } + public State Resting { get; private set; } + public Event Initialized { get; private set; } + public Event LegCramped { get; private set; } + public Event Recovered { get; private set; } + public Event Finish { get; private set; } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/RaiseEvent_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/RaiseEvent_Specs.cs new file mode 100644 index 00000000..842d998b --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/RaiseEvent_Specs.cs @@ -0,0 +1,81 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System; + using System.Threading.Tasks; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class Raising_an_event_within_an_event + { + [Test] + public async Task Should_include_payload() + { + Event Thing = null; + State True = null; + + var instance = new Instance(); + var machine = AutomatonymousStateMachine + .Build(builder => builder + .State("True", out True) + .State("False", out State False) + .Event("Thing", out Thing) + .Event("Initialize", out Event Initialize) + .During(builder.Initial) + .When(Thing, context => context.Data.Condition, b => b + .TransitionTo(True) + .Then(context => context.Raise(Initialize))) + .When(Thing, context => !context.Data.Condition, b => b + .TransitionTo(False)) + .DuringAny() + .When(Initialize, b => b + .Then(context => context.Instance.Initialized = DateTime.Now)) + ); + + await machine.RaiseEvent(instance, Thing, new Data + { + Condition = true + }); + Assert.AreEqual(True, instance.CurrentState); + Assert.IsTrue(instance.Initialized.HasValue); + } + + + class Instance + { + public State CurrentState { get; set; } + public DateTime? Initialized { get; set; } + } + + + class InstanceStateMachine : + AutomatonymousStateMachine + { + public InstanceStateMachine() + { + During(Initial, + When(Thing, context => context.Data.Condition) + .TransitionTo(True) + .Then(context => context.Raise(Initialize)), + When(Thing, context => !context.Data.Condition) + .TransitionTo(False)); + + DuringAny( + When(Initialize) + .Then(context => context.Instance.Initialized = DateTime.Now)); + } + + public State True { get; private set; } + public State False { get; private set; } + + public Event Thing { get; private set; } + public Event Initialize { get; private set; } + } + + + class Data + { + public bool Condition { get; set; } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/Request_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Request_Specs.cs new file mode 100644 index 00000000..956d521b --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/Request_Specs.cs @@ -0,0 +1,399 @@ +namespace Automatonymous.Tests.DynamicModify +{ + namespace Request_Specs + { + using System; + using System.Linq.Expressions; + using System.Reflection; + using System.Threading.Tasks; + using Binders; + using GreenPipes; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class Using_a_request_in_a_state_machine + { + [Test] + public async Task Should_property_initialize() + { + Request QuoteRequest = null; + Event QuoteRequested = null; + + var machine = AutomatonymousStateMachine + .Build(builder => builder + .InstanceState(x => x.CurrentState) + .Event("QuoteRequested", out QuoteRequested) + .Request(x => x.ServiceAddress = new Uri("loopback://localhost/my_queue"), "QuoteRequest", out QuoteRequest) + .Initially() + .When(QuoteRequested, b => b + .Then(context => Console.WriteLine("Quote requested: {0}", context.Data.Symbol)) + .Request(QuoteRequest, context => new GetQuote { Symbol = context.Message.Symbol }) + .TransitionTo(QuoteRequest.Pending) + ) + .During(QuoteRequest.Pending) + .When(QuoteRequest.Completed, b => b.Then((context) => Console.WriteLine("Request Completed!"))) + .When(QuoteRequest.Faulted, b => b.Then((context) => Console.WriteLine("Request Faulted"))) + .When(QuoteRequest.TimeoutExpired, b => b.Then((context) => Console.WriteLine("Request timed out"))) + ); + var instance = new TestState(); + + var requestQuote = new RequestQuote + { + Symbol = "MSFT", + TicketNumber = "8675309", + }; + + ConsumeContext consumeContext = new InternalConsumeContext(requestQuote); + + await machine.RaiseEvent(instance, QuoteRequested, requestQuote, consumeContext); + + await machine.RaiseEvent(instance, QuoteRequest.Completed, new Quote {Symbol = requestQuote.Symbol}); + } + } + + + interface Fault + where T : class + { + } + + + interface Request + where TRequest : class + where TResponse : class + { + /// + /// The name of the request + /// + string Name { get; } + + /// + /// The event that is raised when the request completes and the response is received + /// + Event Completed { get; } + + /// + /// The event raised when the request faults + /// + Event> Faulted { get; } + + /// + /// The event raised when the request times out with no response received + /// + Event TimeoutExpired { get; } + + /// + /// The state that is transitioned to once the request is pending + /// + State Pending { get; } + } + + + interface ConsumeContext + where T : class + { + T Message { get; } + } + + + interface RequestConfigurator + where T : class + where TRequest : class + where TResponse : class + { + Uri ServiceAddress { set; } + TimeSpan Timeout { set; } + } + + + class StateMachineRequestConfigurator : + RequestConfigurator, + RequestSettings + where T : class + where TRequest : class + where TResponse : class + { + Uri _serviceAddress; + TimeSpan _timeout; + + public StateMachineRequestConfigurator() + { + _timeout = TimeSpan.FromSeconds(30); + } + + public RequestSettings Settings + { + get + { + if (_serviceAddress == null) + throw new AutomatonymousException("The ServiceAddress was not specified."); + + return this; + } + } + + public Uri ServiceAddress + { + get { return _serviceAddress; } + set { _serviceAddress = value; } + } + + public TimeSpan Timeout + { + get { return _timeout; } + set { _timeout = value; } + } + } + + static class StateMachineExtensions + { + public static StateMachineModifier Request(this StateMachineModifier modifier, + Action> configureRequest, string requestName, out Request request) + where TInstance : class + where TRequest : class + where TResponse : class + { + var configurator = new StateMachineRequestConfigurator(); + + configureRequest(configurator); + + modifier.Request(requestName, configurator.Settings, out request); + return modifier; + } + + private static void Request( + this StateMachineModifier modifier, string requestName, + RequestSettings settings, out Request request) + where TInstance : class + where TRequest : class + where TResponse : class + { + var smRequest = new StateMachineRequest(requestName, settings); + + modifier.Event("Completed", out Event Completed); + smRequest.Completed = Completed; + + modifier.Event("Faulted", out Event> Faulted); + smRequest.Faulted = Faulted; + + modifier.Event("TimeoutExpired", out Event TimeoutExpired); + smRequest.TimeoutExpired = TimeoutExpired; + + modifier.State("Pending", out State Pending); + smRequest.Pending = Pending; + + request = smRequest; + } + } + + + interface RequestSettings + { + /// + /// The endpoint address of the service that handles the request + /// + Uri ServiceAddress { get; } + + /// + /// The timeout period before the request times out + /// + TimeSpan Timeout { get; } + } + + + class StateMachineRequest : + Request + where TRequest : class + where TResponse : class + { + readonly string _name; + readonly RequestSettings _settings; + + public StateMachineRequest(string requestName, RequestSettings settings) + { + _name = requestName; + _settings = settings; + } + + public string Name + { + get { return _name; } + } + + public Event Completed { get; set; } + public Event> Faulted { get; set; } + public Event TimeoutExpired { get; set; } + + public State Pending { get; set; } + + + public async Task SendRequest(ConsumeContext context, TRequest requestMessage) + where T : class + { + // capture requestId + // send request to endpoint + // schedule timeout for requestId + } + } + + + class InternalConsumeContext : + ConsumeContext + where T : class + { + readonly T _message; + + public InternalConsumeContext(T message) + { + _message = message; + } + + public T Message + { + get { return _message; } + } + } + + + class GetQuote + { + public string Symbol { get; set; } + } + + + class Quote + { + public string Symbol { get; set; } + public decimal Last { get; set; } + public decimal Bid { get; set; } + public decimal Ask { get; set; } + } + + + class TestState + { + public string TicketNumber { get; set; } + public int CurrentState { get; set; } + + public Guid QuoteRequestId { get; set; } + } + + + class RequestQuote + { + public string TicketNumber { get; set; } + public string Symbol { get; set; } + } + + + static class RequestExtensions + { + public static EventActivityBinder Request( + this EventActivityBinder binder, Request request, + Func, TRequest> requestMessageFactory) + // Action> action) + where TInstance : class + where TRequest : class + where TResponse : class where TData : class + { + var activity = new RequestActivity(request, requestMessageFactory); + + return binder.Add(activity); + } + } + + + class RequestActivity : + Activity + where TRequest : class + where TResponse : class + where TData : class + { + readonly Request _request; + readonly Func, TRequest> _requestMessageFactory; + + public RequestActivity(Request request, Func, TRequest> requestMessageFactory) + { + _request = request; + _requestMessageFactory = requestMessageFactory; + } + + public void Accept(StateMachineVisitor visitor) + { + visitor.Visit(this); + } + + public async Task Execute(BehaviorContext context, Behavior next) + { + ConsumeContext consumeContext; + if (!context.TryGetPayload(out consumeContext)) + throw new ArgumentException("The ConsumeContext was not available"); + + + TRequest requestMessage = _requestMessageFactory(consumeContext); + + await next.Execute(context); + } + + public Task Faulted(BehaviorExceptionContext context, Behavior next) + where TException : Exception + { + return next.Faulted(context); + } + + public void Probe(ProbeContext context) + { + } + } + + + static class ExpressionExtensions + { + public static PropertyInfo GetPropertyInfo(this Expression> expression) + { + return expression.GetMemberExpression().Member as PropertyInfo; + } + + public static PropertyInfo GetPropertyInfo(this Expression> expression) + { + return expression.GetMemberExpression().Member as PropertyInfo; + } + + public static MemberExpression GetMemberExpression(this Expression> expression) + { + if (expression == null) + throw new ArgumentNullException("expression"); + + return GetMemberExpression(expression.Body); + } + + public static MemberExpression GetMemberExpression(this Expression> expression) + { + if (expression == null) + throw new ArgumentNullException("expression"); + return GetMemberExpression(expression.Body); + } + + static MemberExpression GetMemberExpression(Expression body) + { + if (body == null) + throw new ArgumentNullException("body"); + + MemberExpression memberExpression = null; + if (body.NodeType == ExpressionType.Convert) + { + var unaryExpression = (UnaryExpression)body; + memberExpression = unaryExpression.Operand as MemberExpression; + } + else if (body.NodeType == ExpressionType.MemberAccess) + memberExpression = body as MemberExpression; + + if (memberExpression == null) + throw new ArgumentException("Expression is not a member access"); + + return memberExpression; + } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/SerializeState_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/SerializeState_Specs.cs new file mode 100644 index 00000000..969a7081 --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/SerializeState_Specs.cs @@ -0,0 +1,76 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System; + using System.Threading.Tasks; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class Serializing_a_state_instance + { + [Test] + public async Task Should_properly_handle_the_state_property() + { + State True = null; + State False = null; + Event Thing = null; + + var instance = new Instance(); + var machine = AutomatonymousStateMachine + .Build(builder => builder + .State("True", out True) + .State("False", out False) + .Event("Thing", out Thing) + .During(builder.Initial) + .When(Thing, context => context.Data.Condition, b => b.TransitionTo(True)) + .When(Thing, context => !context.Data.Condition, b => b.TransitionTo(False)) + ); + + await machine.RaiseEvent(instance, Thing, new Data + { + Condition = true + }); + Assert.AreEqual(True, instance.CurrentState); + + var serializer = new JsonStateSerializer, Instance>(machine); + + string body = serializer.Serialize(instance); + + Console.WriteLine("Body: {0}", body); + var reInstance = serializer.Deserialize(body); + + Assert.AreEqual(True, reInstance.CurrentState); + } + + + class Instance + { + public State CurrentState { get; set; } + } + + + class InstanceStateMachine : + AutomatonymousStateMachine + { + public InstanceStateMachine() + { + During(Initial, + When(Thing, context => context.Data.Condition) + .TransitionTo(True), + When(Thing, context => !context.Data.Condition) + .TransitionTo(False)); + } + + public State True { get; private set; } + public State False { get; private set; } + + public Event Thing { get; private set; } + } + + + class Data + { + public bool Condition { get; set; } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/StateExpression_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/StateExpression_Specs.cs new file mode 100644 index 00000000..095bdd4b --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/StateExpression_Specs.cs @@ -0,0 +1,256 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System; + using System.Linq.Expressions; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class When_creating_a_state_expression_for_int + { + [Test] + public void It_should_match_the_state_requested() + { + var expression = ((StateMachine)_machine).Accessor.GetStateExpression(Running); + + Func filter = expression.Compile(); + + bool result = filter(_instance); + + Assert.That(result, Is.True); + } + + [Test] + public void It_should_not_match_the_state_requested() + { + var expression = ((StateMachine)_machine).Accessor.GetStateExpression(_machine.Initial); + + Func filter = expression.Compile(); + + bool result = filter(_instance); + + Assert.That(result, Is.False); + } + + [Test] + public void It_should_match_the_state_not_requested() + { + var expression = ((StateMachine)_machine).Accessor.GetStateExpression(_machine.Initial); + + var filter = Expression.Lambda>(Expression.Not(expression.Body), expression.Parameters).Compile(); + + bool result = filter(_instance); + + Assert.That(result, Is.True); + } + + State Running; + Event Started; + StateMachine _machine; + Instance _instance; + + [OneTimeSetUp] + public void A_state_is_declared() + { + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .Event("Started", out Started) + .InstanceState(x => x.CurrentState, Running) + .Initially() + .When(Started, b => b.TransitionTo(Running)) + ); + _instance = new Instance(); + + _machine.RaiseEvent(_instance, Started).Wait(); + } + + + /// + /// For this instance, the state is actually stored as a string. Therefore, + /// it is important that the StateMachine property is initialized when the + /// instance is hydrated, as it is used to get the State for the name of + /// the current state. This makes it easier to save the instance using + /// an ORM that doesn't support user types (cough, EF, cough). + /// + class Instance + { + /// + /// The CurrentState is exposed as a string for the ORM + /// + public int CurrentState { get; private set; } + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class When_creating_a_state_expression_for_string + { + [Test] + public void It_should_match_the_state_requested() + { + var expression = ((StateMachine)_machine).Accessor.GetStateExpression(Running); + + Func filter = expression.Compile(); + + bool result = filter(_instance); + + Assert.That(result, Is.True); + } + + [Test] + public void It_should_not_match_the_state_requested() + { + var expression = ((StateMachine)_machine).Accessor.GetStateExpression(_machine.Initial); + + Func filter = expression.Compile(); + + bool result = filter(_instance); + + Assert.That(result, Is.False); + } + + [Test] + public void It_should_match_the_state_not_requested() + { + var expression = ((StateMachine)_machine).Accessor.GetStateExpression(_machine.Initial); + + var filter = Expression.Lambda>(Expression.Not(expression.Body), expression.Parameters).Compile(); + + bool result = filter(_instance); + + Assert.That(result, Is.True); + } + + State Running; + Event Started; + StateMachine _machine; + Instance _instance; + + [OneTimeSetUp] + public void A_state_is_declared() + { + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .Event("Started", out Started) + .InstanceState(x => x.CurrentState) + .Initially() + .When(Started, b => b.TransitionTo(Running)) + ); + _instance = new Instance(); + + _machine.RaiseEvent(_instance, Started).Wait(); + } + + + /// + /// For this instance, the state is actually stored as a string. Therefore, + /// it is important that the StateMachine property is initialized when the + /// instance is hydrated, as it is used to get the State for the name of + /// the current state. This makes it easier to save the instance using + /// an ORM that doesn't support user types (cough, EF, cough). + /// + class Instance + { + /// + /// The CurrentState is exposed as a string for the ORM + /// + public string CurrentState { get; private set; } + } + } + + [TestFixture(Category = "Dynamic Modify")] + public class When_creating_a_state_expression_for_raw + { + [Test] + public void It_should_match_the_state_requested() + { + var expression = ((StateMachine)_machine).Accessor.GetStateExpression(Running); + + Func filter = expression.Compile(); + + bool result = filter(_instance); + + Assert.That(result, Is.True); + } + + [Test] + public void It_should_not_match_the_state_requested() + { + var expression = ((StateMachine)_machine).Accessor.GetStateExpression(_machine.Initial); + + Func filter = expression.Compile(); + + bool result = filter(_instance); + + Assert.That(result, Is.False); + } + + [Test] + public void It_should_match_the_state_not_requested() + { + var expression = ((StateMachine)_machine).Accessor.GetStateExpression(_machine.Initial); + + var filter = Expression.Lambda>(Expression.Not(expression.Body), expression.Parameters).Compile(); + + bool result = filter(_instance); + + Assert.That(result, Is.True); + } + + State Running; + Event Started; + StateMachine _machine; + Instance _instance; + + [OneTimeSetUp] + public void A_state_is_declared() + { + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .Event("Started", out Started) + .InstanceState(x => x.CurrentState) + .Initially() + .When(Started, b => b.TransitionTo(Running)) + ); + _instance = new Instance(); + + _machine.RaiseEvent(_instance, Started).Wait(); + } + + + /// + /// For this instance, the state is actually stored as a string. Therefore, + /// it is important that the StateMachine property is initialized when the + /// instance is hydrated, as it is used to get the State for the name of + /// the current state. This makes it easier to save the instance using + /// an ORM that doesn't support user types (cough, EF, cough). + /// + class Instance + { + /// + /// The CurrentState is exposed as a string for the ORM + /// + public State CurrentState { get; private set; } + } + + + class TestStateMachine : + AutomatonymousStateMachine + { + public TestStateMachine() + { + InstanceState(x => x.CurrentState); + + Initially( + When(Started) + .TransitionTo(Running)); + } + + public Event Started { get; private set; } + public State Running { get; private set; } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/State_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/State_Specs.cs new file mode 100644 index 00000000..44ebb284 --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/State_Specs.cs @@ -0,0 +1,148 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using NUnit.Framework; + using States; + + + [TestFixture(Category = "Dynamic Modify")] + public class When_a_state_is_declared + { + [Test] + public void It_should_capture_the_name_of_final() + { + Assert.AreEqual("Final", _machine.Final.Name); + } + + [Test] + public void It_should_capture_the_name_of_initial() + { + Assert.AreEqual("Initial", _machine.Initial.Name); + } + + [Test] + public void It_should_capture_the_name_of_running() + { + Assert.AreEqual("Running", Running.Name); + } + + [Test] + public void Should_be_an_instance_of_the_proper_type() + { + Assert.IsInstanceOf>(_machine.Initial); + } + + + class Instance + { + public State CurrentState { get; set; } + } + + State Running; + StateMachine _machine; + + [OneTimeSetUp] + public void A_state_is_declared() + { + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .InstanceState(x => x.CurrentState) + ); + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class When_a_state_is_stored_another_way + { + [Test] + public void It_should_get_the_name_right() + { + Assert.AreEqual("Running", _instance.CurrentState); + } + + Event Started; + StateMachine _machine; + Instance _instance; + + [OneTimeSetUp] + public void A_state_is_declared() + { + _machine = AutomatonymousStateMachine + .Build(builder => builder + .Event("Started", out Started) + .State("Running", out State Running) + .InstanceState(x => x.CurrentState) + .Initially() + .When(Started, b => b.TransitionTo(Running)) + ); + _instance = new Instance(); + + _machine.RaiseEvent(_instance, Started).Wait(); + } + + + /// + /// For this instance, the state is actually stored as a string. Therefore, + /// it is important that the StateMachine property is initialized when the + /// instance is hydrated, as it is used to get the State for the name of + /// the current state. This makes it easier to save the instance using + /// an ORM that doesn't support user types (cough, EF, cough). + /// + class Instance + { + /// + /// The CurrentState is exposed as a string for the ORM + /// + public string CurrentState { get; private set; } + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class When_storing_state_as_an_int + { + [Test] + public void It_should_get_the_name_right() + { + Assert.AreEqual(Running, _machine.GetState(_instance).Result); + } + + State Running; + Event Started; + StateMachine _machine; + Instance _instance; + + [OneTimeSetUp] + public void A_state_is_declared() + { + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .Event("Started", out Started) + .InstanceState(x => x.CurrentState, Running) + .Initially() + .When(Started, b => b.TransitionTo(Running)) + ); + _instance = new Instance(); + + _machine.RaiseEvent(_instance, Started).Wait(); + } + + + /// + /// For this instance, the state is actually stored as a string. Therefore, + /// it is important that the StateMachine property is initialized when the + /// instance is hydrated, as it is used to get the State for the name of + /// the current state. This makes it easier to save the instance using + /// an ORM that doesn't support user types (cough, EF, cough). + /// + class Instance + { + /// + /// The CurrentState is exposed as a string for the ORM + /// + public int CurrentState { get; private set; } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/Telephone_Sample.cs b/src/Automatonymous.Tests/Dynamic Modify/Telephone_Sample.cs new file mode 100644 index 00000000..a58062e3 --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/Telephone_Sample.cs @@ -0,0 +1,247 @@ +namespace Automatonymous.Tests.DynamicModify +{ + namespace Telephone_Sample + { + using System; + using System.Diagnostics; + using System.Threading.Tasks; + using Graphing; + using GreenPipes; + using GreenPipes.Introspection; + using NUnit.Framework; + using Visualizer; + + + [TestFixture(Category = "Dynamic Modify")] + public class A_simple_phone_call + { + [Test] + public async Task Should_be_short_and_sweet() + { + var phone = new PrincessModelTelephone(); + await _machine.RaiseEvent(phone, _model.ServiceEstablished, new PhoneServiceEstablished {Digits = "555-1212"}); + + await _machine.RaiseEvent(phone, _model.CallDialed); + await _machine.RaiseEvent(phone, _model.CallConnected); + + await Task.Delay(50); + + await _machine.RaiseEvent(phone, _model.HungUp); + + Assert.AreEqual(_model.OffHook.Name, phone.CurrentState); + Assert.GreaterOrEqual(phone.CallTimer.ElapsedMilliseconds, 45); + } + + PhoneServiceStateModel _model; + StateMachine _machine; + + [OneTimeSetUp] + public void Setup() + { + _model = new PhoneServiceStateModel(); + _machine = _model.Machine; + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class Visualize + { + [Test] + public void Draw() + { + var machine = new PhoneServiceStateModel().Machine; + var generator = new StateMachineGraphvizGenerator(machine.GetGraph()); + + var dotFile = generator.CreateDotFile(); + + Console.WriteLine(dotFile); + } + + [Test] + public void Should_return_a_wonderful_breakdown_of_the_guts_inside_it() + { + ProbeResult result = new PhoneServiceStateModel().Machine.GetProbeResult(); + + Console.WriteLine(result.ToJsonString()); + } + + PhoneServiceStateModel _model; + StateMachine _machine; + + [OneTimeSetUp] + public void Setup() + { + _model = new PhoneServiceStateModel(); + _machine = _model.Machine; + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class A_short_time_on_hold + { + [Test] + public async Task Should_be_short_and_sweet() + { + var phone = new PrincessModelTelephone(); + await _machine.RaiseEvent(phone, _model.ServiceEstablished, new PhoneServiceEstablished {Digits = "555-1212"}); + + await _machine.RaiseEvent(phone, _model.CallDialed); + await _machine.RaiseEvent(phone, _model.CallConnected); + + await Task.Delay(50); + + await _machine.RaiseEvent(phone, _model.PlacedOnHold); + await _machine.RaiseEvent(phone, _model.TakenOffHold); + await _machine.RaiseEvent(phone, _model.HungUp); + + Assert.AreEqual(_model.OffHook.Name, phone.CurrentState); + Assert.GreaterOrEqual(phone.CallTimer.ElapsedMilliseconds, 45); + } + + PhoneServiceStateModel _model; + StateMachine _machine; + + [OneTimeSetUp] + public void Setup() + { + _model = new PhoneServiceStateModel(); + _machine = _model.Machine; + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class An_extended_time_on_hold + { + [Test] + public async Task Should_end__badly() + { + var phone = new PrincessModelTelephone(); + await _machine.RaiseEvent(phone, _model.ServiceEstablished, new PhoneServiceEstablished {Digits = "555-1212"}); + + await _machine.RaiseEvent(phone, _model.CallDialed); + await _machine.RaiseEvent(phone, _model.CallConnected); + await _machine.RaiseEvent(phone, _model.PlacedOnHold); + + await Task.Delay(50); + + await _machine.RaiseEvent(phone, _model.HungUp); + + Assert.AreEqual(_model.OffHook.Name, phone.CurrentState); + Assert.GreaterOrEqual(phone.CallTimer.ElapsedMilliseconds, 45); + } + + PhoneServiceStateModel _model; + StateMachine _machine; + + [OneTimeSetUp] + public void Setup() + { + _model = new PhoneServiceStateModel(); + _machine = _model.Machine; + } + } + + + class PrincessModelTelephone + { + public PrincessModelTelephone() + { + CallTimer = new Stopwatch(); + } + + public string CurrentState { get; set; } + + public Stopwatch CallTimer { get; private set; } + + public string Number { get; set; } + } + + class PhoneServiceEstablished + { + public string Digits { get; set; } + } + + class PhoneServiceStateModel + { + public StateMachine Machine; + + public PhoneServiceStateModel() + { + Machine = CreateDynamically(); + } + + public State OffHook; + public State Ringing; + public State Connected; + public State OnHold; + public State PhoneDestroyed; + + public Event ServiceEstablished; + public Event CallDialed; + public Event HungUp; + public Event CallConnected; + public Event LeftMessage; + public Event PlacedOnHold; + public Event TakenOffHold; + public Event PhoneHurledAgainstWall; + + void StopCallTimer(PrincessModelTelephone instance) + { + instance.CallTimer.Stop(); + + Console.WriteLine("Stopped call timer at {0}ms", instance.CallTimer.ElapsedMilliseconds); + } + + void StartCallTimer(PrincessModelTelephone instance) + { + Console.WriteLine("Started call timer"); + + instance.CallTimer.Start(); + } + + public StateMachine CreateDynamically() + { + return AutomatonymousStateMachine + .Build(builder => builder + .State("OffHook", out OffHook) + .State("Ringing", out Ringing) + .State("Connected", out Connected) + .State("OnHold", out OnHold) + .State("PhoneDestroyed", out PhoneDestroyed) + .Event("ServiceEstablished", out ServiceEstablished) + .Event("CallDialed", out CallDialed) + .Event("HungUp", out HungUp) + .Event("CallConnected", out CallConnected) + .Event("LeftMessage", out LeftMessage) + .Event("PlacedOnHold", out PlacedOnHold) + .Event("TakenOffHold", out TakenOffHold) + .Event("PhoneHurledAgainstWall", out PhoneHurledAgainstWall) + .InstanceState(x => x.CurrentState) + .SubState(OnHold, Connected) + .Initially() + .When(ServiceEstablished, b => b + .Then(context => context.Instance.Number = context.Data.Digits) + .TransitionTo(OffHook)) + .During(OffHook) + .When(CallDialed, b => b.TransitionTo(Ringing)) + .During(Ringing) + .When(HungUp, b => b.TransitionTo(OffHook)) + .When(CallConnected, b => b.TransitionTo(Connected)) + .During(Connected) + .When(LeftMessage, b => b.TransitionTo(OffHook)) + .When(HungUp, b => b.TransitionTo(OffHook)) + .When(PlacedOnHold, b => b.TransitionTo(OnHold)) + .During(OnHold) + .When(TakenOffHold, b => b.TransitionTo(Connected)) + .When(PhoneHurledAgainstWall, b => b.TransitionTo(PhoneDestroyed)) + .DuringAny() + .When(Connected.Enter, b => b.Then(context => StartCallTimer(context.Instance))) + .When(Connected.Leave, b => b.Then(context => StopCallTimer(context.Instance))) + ); + } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/Transition_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Transition_Specs.cs new file mode 100644 index 00000000..c8c9eb0b --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/Transition_Specs.cs @@ -0,0 +1,158 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class Explicitly_transitioning_to_a_state + { + [Test] + public void Should_call_the_enter_event() + { + Assert.IsTrue(_instance.EnterCalled); + } + + [Test] + public void Should_have_first_moved_to_initial() + { + Assert.AreEqual(null, _observer.Events[0].Previous); + Assert.AreEqual(_machine.Initial, _observer.Events[0].Current); + } + + [Test] + public void Should_have_second_moved_to_running() + { + Assert.AreEqual(_machine.Initial, _observer.Events[1].Previous); + Assert.AreEqual(Running, _observer.Events[1].Current); + } + + [Test] + public void Should_raise_the_event() + { + Assert.AreEqual(2, _observer.Events.Count); + } + + State Running; + Instance _instance; + StateMachine _machine; + StateChangeObserver _observer; + + [OneTimeSetUp] + public void Specifying_an_event_activity() + { + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .Event("Initialized", out Event Initialized) + .Event("Finish", out Event Finish) + .During(builder.Initial) + .When(Initialized, b => b.TransitionTo(Running)) + .During(Running) + .When(Finish, b => b.Finalize()) + .WhenEnter(Running, x => x.Then(context => context.Instance.EnterCalled = true)) + ); + _observer = new StateChangeObserver(); + + using (IDisposable subscription = _machine.ConnectStateObserver(_observer)) + { + _machine.TransitionToState(_instance, Running) + .Wait(); + } + } + + + class Instance + { + public State CurrentState { get; set; } + public bool EnterCalled { get; set; } + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class Transitioning_to_a_state_from_a_state + { + [Test] + public void Should_call_the_enter_event() + { + Assert.IsTrue(_instance.EnterCalled); + } + + [Test] + public void Should_have_first_moved_to_initial() + { + Assert.AreEqual(null, _observer.Events[0].Previous); + Assert.AreEqual(_machine.Initial, _observer.Events[0].Current); + } + + [Test] + public void Should_have_invoked_final_entered() + { + Assert.IsTrue(_instance.FinalEntered); + } + + [Test] + public void Should_have_second_moved_to_running() + { + Assert.AreEqual(_machine.Initial, _observer.Events[1].Previous); + Assert.AreEqual(Running, _observer.Events[1].Current); + } + + [Test] + public void Should_have_third_moved_to_final() + { + Assert.AreEqual(Running, _observer.Events[2].Previous); + Assert.AreEqual(_machine.Final, _observer.Events[2].Current); + } + + [Test] + public void Should_raise_the_event() + { + Assert.AreEqual(3, _observer.Events.Count); + } + + State Running; + Event Initialized; + Event Finish; + Instance _instance; + StateMachine _machine; + StateChangeObserver _observer; + + [OneTimeSetUp] + public void Specifying_an_event_activity() + { + _instance = new Instance(); + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .Event("Initialized", out Initialized) + .Event("Finish", out Finish) + .During(builder.Initial) + .When(Initialized, b => b.TransitionTo(Running)) + .During(Running) + .When(Finish, b => b.Finalize()) + .BeforeEnter(builder.Final, x => x.Then(context => context.Instance.FinalEntered = true)) + .WhenEnter(Running, x => x.Then(context => context.Instance.EnterCalled = true)) + ); + _observer = new StateChangeObserver(); + + using (IDisposable subscription = _machine.ConnectStateObserver(_observer)) + { + _machine.RaiseEvent(_instance, Initialized) + .Wait(); + _machine.RaiseEvent(_instance, Finish); + } + } + + + class Instance + { + public State CurrentState { get; set; } + public bool EnterCalled { get; set; } + + public bool FinalEntered { get; set; } + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/UnobservedEvent_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/UnobservedEvent_Specs.cs new file mode 100644 index 00000000..5a1c93bf --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/UnobservedEvent_Specs.cs @@ -0,0 +1,216 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using GreenPipes; + using GreenPipes.Introspection; + using NUnit.Framework; + + + [TestFixture(Category = "Dynamic Modify")] + public class Raising_an_unhandled_event_in_a_state + { + [Test] + public async Task Should_throw_an_exception_when_event_is_not_allowed_in_current_state() + { + var instance = new Instance(); + + await _machine.RaiseEvent(instance, x => Start); + + Assert.That(async () => await _machine.RaiseEvent(instance, x => Start), Throws.TypeOf()); + } + + StateMachine _machine; + Event Start; + + class Instance + { + public State CurrentState { get; set; } + } + + [OneTimeSetUp] + public void A_state_is_declared() + { + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out State Running) + .Event("Start", out Start) + .Initially() + .When(Start, b => b.TransitionTo(Running)) + ); + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class Raising_an_ignored_event_that_is_not_filtered + { + [Test] + public async Task Should_throw_an_exception_when_event_is_not_allowed_in_current_state() + { + var instance = new Instance(); + + await _machine.RaiseEvent(instance, Start); + + Assert.That(async () => await _machine.RaiseEvent(instance, Charge, new A {Volts = 12}), + Throws.TypeOf()); + } + + Event Start; + Event Charge; + StateMachine _machine; + + + class Instance + { + public State CurrentState { get; set; } + public int Volts { get; set; } + } + + + [OneTimeSetUp] + public void A_state_is_declared() + { + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out State Running) + .Event("Start", out Start) + .Event("Charge", out Charge) + .Initially() + .When(Start, b => b.TransitionTo(Running)) + .During(Running) + .Ignore(Start) + .Ignore(Charge, x => x.Data.Volts == 9) + ); + } + + + class A + { + public int Volts { get; set; } + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class Raising_an_ignored_event + { + [Test] + public async Task Should_also_ignore_yet_process_invalid_events() + { + var instance = new Instance(); + + await _machine.RaiseEvent(instance, Start); + + await _machine.RaiseEvent(instance, Charge, new A {Volts = 12}); + + Assert.AreEqual(0, instance.Volts); + } + + [Test] + public async Task Should_have_the_next_event_even_though_ignored() + { + var instance = new Instance(); + + await _machine.RaiseEvent(instance, Start); + + Assert.AreEqual(Running, await _machine.GetState(instance)); + + var nextEvents = await _machine.NextEvents(instance); + + Assert.IsTrue(nextEvents.Any(x => x.Name.Equals("Charge"))); + } + + [Test] + public void Should_return_a_wonderful_breakdown_of_the_guts_inside_it() + { + ProbeResult result = _machine.GetProbeResult(); + + Console.WriteLine(result.ToJsonString()); + } + + [Test] + public async Task Should_silenty_ignore_the_invalid_event() + { + var instance = new Instance(); + + await _machine.RaiseEvent(instance, Start); + + await _machine.RaiseEvent(instance, Start); + } + + State Running; + Event Start; + Event Charge; + StateMachine _machine; + + + class Instance + { + public State CurrentState { get; set; } + public int Volts { get; set; } + } + + + [OneTimeSetUp] + public void A_state_is_declared() + { + _machine = AutomatonymousStateMachine + .Build(builder => builder + .State("Running", out Running) + .Event("Start", out Start) + .Event("Charge", out Charge) + .Initially() + .When(Start, b => b.TransitionTo(Running)) + .During(Running) + .Ignore(Start) + .Ignore(Charge) + ); + } + + + class A + { + public int Volts { get; set; } + } + } + + + [TestFixture(Category = "Dynamic Modify")] + public class Raising_an_unhandled_event_when_the_state_machine_ignores_all_unhandled_events + { + [Test] + public async Task Should_silenty_ignore_the_invalid_event() + { + var instance = new Instance(); + + await _machine.RaiseEvent(instance, Start); + + await _machine.RaiseEvent(instance, Start); + } + + Event Start; + StateMachine _machine; + + + class Instance + { + public State CurrentState { get; set; } + } + + + [OneTimeSetUp] + public void A_state_is_declared() + { + _machine = AutomatonymousStateMachine + .Build(builder => builder + .Event("Start", out Start) + .State("Running", out State Running) + .OnUnhandledEvent(x => x.Ignore()) + .Initially() + .When(Start, b => b.TransitionTo(Running)) + ); + } + } +} diff --git a/src/Automatonymous.Tests/Dynamic Modify/Visualizer_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Visualizer_Specs.cs new file mode 100644 index 00000000..75bb5062 --- /dev/null +++ b/src/Automatonymous.Tests/Dynamic Modify/Visualizer_Specs.cs @@ -0,0 +1,104 @@ +namespace Automatonymous.Tests.DynamicModify +{ + using System; + using Graphing; + using NUnit.Framework; + using Visualizer; + + + [TestFixture(Category = "Dynamic Modify")] + public class When_visualizing_a_state_machine + { + [Test] + public void Should_parse_the_graph() + { + Assert.IsNotNull(_graph); + } + + [Test] + public void Should_show_the_goods() + { + var generator = new StateMachineGraphvizGenerator(_graph); + + string dots = generator.CreateDotFile(); + + Console.WriteLine(dots); + + var expected = Expected.Replace("\r", "").Replace("\n", Environment.NewLine); + + Assert.AreEqual(expected, dots); + } + + StateMachine _machine; + StateMachineGraph _graph; + + [OneTimeSetUp] + public void Setup() + { + _machine = AutomatonymousStateMachine + .Build(b => b + .State("Running", out State Running) + .State("Suspended", out State Suspended) + .State("Failed", out State Failed) + .Event("Initialized", out Event Initialized) + .Event("Suspend", out Event Suspend) + .Event("Resume", out Event Resume) + .Event("Finished", out Event Finished) + .Event("Restart", out Event Restart) + .During(b.Initial) + .When(Initialized, (binder) => binder + .TransitionTo(Running) + .Catch(h => h.TransitionTo(Failed)) + ) + .During(Running) + .When(Finished, (binder) => binder.TransitionTo(b.Final)) + .When(Suspend, (binder) => binder.TransitionTo(Suspended)) + .Ignore(Resume) + .During(Suspended) + .When(Resume, b => b.TransitionTo(Running)) + .During(Failed) + .When(Restart, context => context.Data.Name != null, b => b.TransitionTo(Running)) + ); + + _graph = _machine.GetGraph(); + } + + const string Expected = @"digraph G { +0 [shape=ellipse, label=""Initial""]; +1 [shape=ellipse, label=""Running""]; +2 [shape=ellipse, label=""Failed""]; +3 [shape=ellipse, label=""Final""]; +4 [shape=ellipse, label=""Suspended""]; +5 [shape=rectangle, label=""Initialized""]; +6 [shape=rectangle, label=""Exception""]; +7 [shape=rectangle, label=""Finished""]; +8 [shape=rectangle, label=""Suspend""]; +9 [shape=rectangle, label=""Resume""]; +10 [shape=rectangle, label=""Restart""]; +0 -> 5; +1 -> 7; +1 -> 8; +2 -> 10; +4 -> 9; +5 -> 1; +5 -> 6; +6 -> 2; +7 -> 3; +8 -> 4; +9 -> 1; +10 -> 1; +}"; + + + class Instance + { + public State CurrentState { get; set; } + } + + + class RestartData + { + public string Name { get; set; } + } + } +} diff --git a/src/Automatonymous/AutomatonymousStateMachine.cs b/src/Automatonymous/AutomatonymousStateMachine.cs index 4a993e9c..b74289f3 100644 --- a/src/Automatonymous/AutomatonymousStateMachine.cs +++ b/src/Automatonymous/AutomatonymousStateMachine.cs @@ -109,12 +109,15 @@ async Task StateMachine.RaiseEvent(EventContext cont public State GetState(string name) { - if (_stateCache.TryGetValue(name, out var result)) + if (TryGetState(name, out var result)) return result; throw new UnknownStateException(_name, name); } + public bool TryGetState(string name, out State state) + => _stateCache.TryGetValue(name, out state); + public IEnumerable States => _stateCache.Values; Event StateMachine.GetEvent(string name) @@ -196,7 +199,7 @@ Task DefaultUnhandledEventCallback(UnhandledEventContext context) /// Please note, the state machine can only manage one property at a given time per instance, /// and the best practice is to manage one property per machine. /// - protected void InstanceState(Expression> instanceStateProperty) + protected internal void InstanceState(Expression> instanceStateProperty) { var stateAccessor = new RawStateAccessor(this, instanceStateProperty, _stateObservers); @@ -207,7 +210,7 @@ protected void InstanceState(Expression> instanceStatePro /// Declares the property to hold the instance's state as a string (the state name is stored in the property) /// /// - protected void InstanceState(Expression> instanceStateProperty) + protected internal void InstanceState(Expression> instanceStateProperty) { var stateAccessor = new StringStateAccessor(this, instanceStateProperty, _stateObservers); @@ -219,7 +222,7 @@ protected void InstanceState(Expression> instanceStatePr /// /// /// Specifies the states, in order, to which the int values should be assigned - protected void InstanceState(Expression> instanceStateProperty, params State[] states) + protected internal void InstanceState(Expression> instanceStateProperty, params State[] states) { var stateIndex = new StateAccessorIndex(this, _initial, _final, states); @@ -232,7 +235,7 @@ protected void InstanceState(Expression> instanceStatePrope /// Specifies the name of the state machine /// /// - protected void Name(string machineName) + protected internal void Name(string machineName) { if (string.IsNullOrWhiteSpace(machineName)) throw new ArgumentException("The machine name must not be empty", nameof(machineName)); @@ -244,13 +247,20 @@ protected void Name(string machineName) /// Declares an event, and initializes the event property /// /// - protected virtual void Event(Expression> propertyExpression) + protected internal virtual void Event(Expression> propertyExpression) { PropertyInfo property = propertyExpression.GetPropertyInfo(); DeclareTriggerEvent(property); } + protected internal virtual Event Event(string name) + { + var @event = new TriggerEvent(name); + _eventCache[name] = new StateMachineEvent(@event, false); + return @event; + } + void DeclareTriggerEvent(PropertyInfo property) { string name = property.Name; @@ -266,13 +276,20 @@ void DeclareTriggerEvent(PropertyInfo property) /// Declares a data event on the state machine, and initializes the property /// /// The event property - protected virtual void Event(Expression>> propertyExpression) + protected internal virtual void Event(Expression>> propertyExpression) { PropertyInfo property = propertyExpression.GetPropertyInfo(); DeclareDataEvent(property); } + protected internal virtual Event Event(string name) + { + var @event = new DataEvent(name); + _eventCache[name] = new StateMachineEvent(@event, false); + return @event; + } + void DeclareDataEvent(PropertyInfo property) { string name = property.Name; @@ -289,7 +306,7 @@ void DeclareDataEvent(PropertyInfo property) /// /// The property /// The event property on the property - protected virtual void Event(Expression> propertyExpression, + protected internal virtual void Event(Expression> propertyExpression, Expression>> eventPropertyExpression) where TProperty : class { @@ -317,7 +334,7 @@ protected virtual void Event(Expression> propertyE /// The composite event /// The property in the instance used to track the state of the composite event /// The events that must be raised before the composite event is raised - protected virtual void CompositeEvent(Expression> propertyExpression, + protected internal virtual void CompositeEvent(Expression> propertyExpression, Expression> trackingPropertyExpression, params Event[] events) { @@ -337,7 +354,7 @@ protected virtual void CompositeEvent(Expression> propertyExpression /// The property in the instance used to track the state of the composite event /// Options on the composite event /// The events that must be raised before the composite event is raised - protected virtual void CompositeEvent(Expression> propertyExpression, + protected internal virtual void CompositeEvent(Expression> propertyExpression, Expression> trackingPropertyExpression, CompositeEventOptions options, params Event[] events) @@ -357,7 +374,7 @@ protected virtual void CompositeEvent(Expression> propertyExpression /// The composite event /// The property in the instance used to track the state of the composite event /// The events that must be raised before the composite event is raised - protected virtual void CompositeEvent(Expression> propertyExpression, + protected internal virtual void CompositeEvent(Expression> propertyExpression, Expression> trackingPropertyExpression, params Event[] events) { @@ -377,7 +394,7 @@ protected virtual void CompositeEvent(Expression> propertyExpression /// The property in the instance used to track the state of the composite event /// Options on the composite event /// The events that must be raised before the composite event is raised - protected virtual void CompositeEvent(Expression> propertyExpression, + protected internal virtual void CompositeEvent(Expression> propertyExpression, Expression> trackingPropertyExpression, CompositeEventOptions options, params Event[] events) @@ -431,17 +448,144 @@ void CompositeEvent(Expression> propertyExpression, CompositeEventSt } } + protected internal virtual void CompositeEvent(string name, + Expression> trackingPropertyExpression, + params Event[] events) + => CompositeEvent(name, new StructCompositeEventStatusAccessor(trackingPropertyExpression.GetPropertyInfo()), CompositeEventOptions.None, events); + + protected internal virtual Event CompositeEvent(string name, + Expression> trackingPropertyExpression, + CompositeEventOptions options, + params Event[] events) + => CompositeEvent(name, new StructCompositeEventStatusAccessor(trackingPropertyExpression.GetPropertyInfo()), options, events); + + protected internal virtual Event CompositeEvent(string name, + Expression> trackingPropertyExpression, + params Event[] events) + => CompositeEvent(name, new IntCompositeEventStatusAccessor(trackingPropertyExpression.GetPropertyInfo()), CompositeEventOptions.None, events); + + protected internal virtual Event CompositeEvent(string name, + Expression> trackingPropertyExpression, + CompositeEventOptions options, + params Event[] events) + => CompositeEvent(name, new IntCompositeEventStatusAccessor(trackingPropertyExpression.GetPropertyInfo()), options, events); + + Event CompositeEvent(string name, CompositeEventStatusAccessor accessor, + CompositeEventOptions options, Event[] events) + { + if (events == null) + throw new ArgumentNullException(nameof(events)); + if (events.Length > 31) + throw new ArgumentException("No more than 31 events can be combined into a single event"); + if (events.Length == 0) + throw new ArgumentException("At least one event must be specified for a composite event"); + if (events.Any(x => x == null)) + throw new ArgumentException("One or more events specified has not yet been initialized"); + + var @event = new TriggerEvent(name); + + _eventCache[name] = new StateMachineEvent(@event, false); + + var complete = new CompositeEventStatus(Enumerable.Range(0, events.Length) + .Aggregate(0, (current, x) => current | (1 << x))); + + for (int i = 0; i < events.Length; i++) + { + int flag = 1 << i; + + var activity = new CompositeEventActivity(accessor, flag, complete, @event); + + bool Filter(State x) => options.HasFlag(CompositeEventOptions.IncludeInitial) || !Equals(x, Initial); + + foreach (var state in _stateCache.Values.Where(Filter)) + { + During(state, + When(events[i]) + .Execute(activity)); + } + } + + return @event; + } + + protected internal virtual void CompositeEvent(Event @event, + Expression> trackingPropertyExpression, + params Event[] events) + => CompositeEvent(@event, new StructCompositeEventStatusAccessor(trackingPropertyExpression.GetPropertyInfo()), CompositeEventOptions.None, events); + + protected internal virtual Event CompositeEvent(Event @event, + Expression> trackingPropertyExpression, + CompositeEventOptions options, + params Event[] events) + => CompositeEvent(@event, new StructCompositeEventStatusAccessor(trackingPropertyExpression.GetPropertyInfo()), options, events); + + protected internal virtual Event CompositeEvent(Event @event, + Expression> trackingPropertyExpression, + params Event[] events) + => CompositeEvent(@event, new IntCompositeEventStatusAccessor(trackingPropertyExpression.GetPropertyInfo()), CompositeEventOptions.None, events); + + protected internal virtual Event CompositeEvent(Event @event, + Expression> trackingPropertyExpression, + CompositeEventOptions options, + params Event[] events) + => CompositeEvent(@event, new IntCompositeEventStatusAccessor(trackingPropertyExpression.GetPropertyInfo()), options, events); + + Event CompositeEvent(Event @event, CompositeEventStatusAccessor accessor, + CompositeEventOptions options, Event[] events) + { + if (events == null) + throw new ArgumentNullException(nameof(events)); + if (events.Length > 31) + throw new ArgumentException("No more than 31 events can be combined into a single event"); + if (events.Length == 0) + throw new ArgumentException("At least one event must be specified for a composite event"); + if (events.Any(x => x == null)) + throw new ArgumentException("One or more events specified has not yet been initialized"); + + var complete = new CompositeEventStatus(Enumerable.Range(0, events.Length) + .Aggregate(0, (current, x) => current | (1 << x))); + + for (int i = 0; i < events.Length; i++) + { + int flag = 1 << i; + + var activity = new CompositeEventActivity(accessor, flag, complete, @event); + + bool Filter(State x) => options.HasFlag(CompositeEventOptions.IncludeInitial) || !Equals(x, Initial); + + foreach (var state in _stateCache.Values.Where(Filter)) + { + During(state, + When(events[i]) + .Execute(activity)); + } + } + + return @event; + } + /// /// Declares a state on the state machine, and initialized the property /// /// The state property - protected virtual void State(Expression> propertyExpression) + protected internal virtual void State(Expression> propertyExpression) { PropertyInfo property = propertyExpression.GetPropertyInfo(); DeclareState(property); } + protected internal virtual State State(string name) + { + if (TryGetState(name, out var foundState)) + return foundState; + + var state = new StateMachineState(this, name, _eventObservers); + SetState(name, state); + + return state; + } + void DeclareState(PropertyInfo property) { string name = property.Name; @@ -465,7 +609,7 @@ void DeclareState(PropertyInfo property) /// /// The property containing the state /// The state property - protected virtual void State(Expression> propertyExpression, + protected internal virtual void State(Expression> propertyExpression, Expression> statePropertyExpression) where TProperty : class { @@ -508,7 +652,7 @@ static StateMachineState GetStateProperty(PropertyInfo sta /// /// The state property expression /// The superstate of which this state is a substate - protected virtual void SubState(Expression> propertyExpression, State superState) + protected internal virtual void SubState(Expression> propertyExpression, State superState) { if (superState == null) throw new ArgumentNullException(nameof(superState)); @@ -533,13 +677,43 @@ protected virtual void SubState(Expression> propertyExpression, Stat SetState(name, state); } + protected internal virtual State SubState(string name, State superState) + { + if (superState == null) + throw new ArgumentNullException(nameof(superState)); + + State superStateInstance = GetState(superState.Name); + + // If the state was already defined, don't define it again + if (TryGetState(name, out var existingState) && + name.Equals(existingState?.Name) && + superState.Name.Equals(existingState?.SuperState?.Name)) + return existingState; + + var state = new StateMachineState(this, name, _eventObservers, superStateInstance); + + SetState(name, state); + return state; + } + + protected internal virtual void SubState(State subState, State superState) + { + if (superState == null) + throw new ArgumentNullException(nameof(superState)); + + State superStateInstance = GetState(superState.Name); + + // If the state was already defined, don't define it again + superStateInstance.AddSubstate(subState); + } + /// /// Declares a state on the state machine, and initialized the property /// /// The property containing the state /// The state property /// The superstate of which this state is a substate - protected virtual void SubState(Expression> propertyExpression, + protected internal virtual void SubState(Expression> propertyExpression, Expression> statePropertyExpression, State superState) where TProperty : class { @@ -588,7 +762,7 @@ void SetState(string name, StateMachineState state) /// /// The state /// The event and activities - protected void During(State state, params EventActivities[] activities) + protected internal void During(State state, params EventActivities[] activities) { ActivityBinder[] activitiesBinder = activities.SelectMany(x => x.GetStateActivityBinders()).ToArray(); @@ -601,7 +775,7 @@ protected void During(State state, params EventActivities[] activitie /// The state /// The other state /// The event and activities - protected void During(State state1, State state2, params EventActivities[] activities) + protected internal void During(State state1, State state2, params EventActivities[] activities) { ActivityBinder[] activitiesBinder = activities.SelectMany(x => x.GetStateActivityBinders()).ToArray(); @@ -616,7 +790,7 @@ protected void During(State state1, State state2, params EventActivitiesThe other state /// The other other state /// The event and activities - protected void During(State state1, State state2, State state3, params EventActivities[] activities) + protected internal void During(State state1, State state2, State state3, params EventActivities[] activities) { ActivityBinder[] activitiesBinder = activities.SelectMany(x => x.GetStateActivityBinders()).ToArray(); @@ -633,7 +807,7 @@ protected void During(State state1, State state2, State state3, params EventActi /// The other other state /// Okay, this is getting a bit ridiculous at this point /// The event and activities - protected void During(State state1, State state2, State state3, State state4, + protected internal void During(State state1, State state2, State state3, State state4, params EventActivities[] activities) { ActivityBinder[] activitiesBinder = activities.SelectMany(x => x.GetStateActivityBinders()).ToArray(); @@ -649,7 +823,7 @@ protected void During(State state1, State state2, State state3, State state4, /// /// The states /// The event and activities - protected void During(IEnumerable states, params EventActivities[] activities) + protected internal void During(IEnumerable states, params EventActivities[] activities) { ActivityBinder[] activitiesBinder = activities.SelectMany(x => x.GetStateActivityBinders()).ToArray(); @@ -669,7 +843,7 @@ void BindActivitiesToState(State state, IEnumerable> e /// Declares the events and activities that are handled during the initial state /// /// The event and activities - protected void Initially(params EventActivities[] activities) + protected internal void Initially(params EventActivities[] activities) { During(Initial, activities); } @@ -678,7 +852,7 @@ protected void Initially(params EventActivities[] activities) /// Declares events and activities that are handled during any state exception Initial and Final /// /// The event and activities - protected void DuringAny(params EventActivities[] activities) + protected internal void DuringAny(params EventActivities[] activities) { IEnumerable> states = _stateCache.Values.Where(x => !Equals(x, Initial) && !Equals(x, Final)); @@ -695,7 +869,7 @@ protected void DuringAny(params EventActivities[] activities) /// When the Final state is entered, execute the chained activities. This occurs in any state that is not the initial or final state /// /// Specify the activities that are executes when the Final state is entered. - protected void Finally(Func, EventActivityBinder> activityCallback) + protected internal void Finally(Func, EventActivityBinder> activityCallback) { EventActivityBinder binder = When(Final.Enter); @@ -718,7 +892,7 @@ void BindTransitionEvents(State state, IEnumerable /// The fired event /// - protected EventActivityBinder When(Event @event) + protected internal EventActivityBinder When(Event @event) { return new TriggerEventActivityBinder(this, @event); } @@ -729,7 +903,7 @@ protected EventActivityBinder When(Event @event) /// The fired event /// The filter applied to the event /// - protected EventActivityBinder When(Event @event, StateMachineEventFilter filter) + protected internal EventActivityBinder When(Event @event, StateMachineEventFilter filter) { return new TriggerEventActivityBinder(this, @event, filter); } @@ -741,7 +915,7 @@ protected EventActivityBinder When(Event @event, StateMachineEventFil /// /// /// - protected void WhenEnter(State state, Func, EventActivityBinder> activityCallback) + protected internal void WhenEnter(State state, Func, EventActivityBinder> activityCallback) { State activityState = GetState(state.Name); @@ -757,7 +931,7 @@ protected void WhenEnter(State state, Func, Event /// /// /// - protected void WhenEnterAny(Func, EventActivityBinder> activityCallback) + protected internal void WhenEnterAny(Func, EventActivityBinder> activityCallback) { BindEveryTransitionEvent(activityCallback, x => x.Enter); } @@ -767,7 +941,7 @@ protected void WhenEnterAny(Func, EventActivityBi /// /// /// - protected void WhenLeaveAny(Func, EventActivityBinder> activityCallback) + protected internal void WhenLeaveAny(Func, EventActivityBinder> activityCallback) { BindEveryTransitionEvent(activityCallback, x => x.Leave); } @@ -798,7 +972,7 @@ void BindEveryTransitionEvent(Func, EventActivity /// /// /// - protected void BeforeEnterAny(Func, EventActivityBinder> activityCallback) + protected internal void BeforeEnterAny(Func, EventActivityBinder> activityCallback) { BindEveryTransitionEvent(activityCallback, x => x.BeforeEnter); } @@ -808,7 +982,7 @@ protected void BeforeEnterAny(Func, EventA /// /// /// - protected void AfterLeaveAny(Func, EventActivityBinder> activityCallback) + protected internal void AfterLeaveAny(Func, EventActivityBinder> activityCallback) { BindEveryTransitionEvent(activityCallback, x => x.AfterLeave); } @@ -840,7 +1014,7 @@ void BindEveryTransitionEvent(Func, EventA /// /// /// - protected void WhenLeave(State state, Func, EventActivityBinder> activityCallback) + protected internal void WhenLeave(State state, Func, EventActivityBinder> activityCallback) { State activityState = GetState(state.Name); @@ -857,7 +1031,7 @@ protected void WhenLeave(State state, Func, Event /// /// /// - protected void BeforeEnter(State state, + protected internal void BeforeEnter(State state, Func, EventActivityBinder> activityCallback) { State activityState = GetState(state.Name); @@ -875,7 +1049,7 @@ protected void BeforeEnter(State state, /// /// /// - protected void AfterLeave(State state, + protected internal void AfterLeave(State state, Func, EventActivityBinder> activityCallback) { State activityState = GetState(state.Name); @@ -893,7 +1067,7 @@ protected void AfterLeave(State state, /// The event data type /// The fired event /// - protected EventActivityBinder When(Event @event) + protected internal EventActivityBinder When(Event @event) { return new DataEventActivityBinder(this, @event); } @@ -905,7 +1079,7 @@ protected EventActivityBinder When(Event @event) /// The fired event /// The filter applied to the event /// - protected EventActivityBinder When(Event @event, + protected internal EventActivityBinder When(Event @event, StateMachineEventFilter filter) { return new DataEventActivityBinder(this, @event, filter); @@ -916,7 +1090,7 @@ protected EventActivityBinder When(Event @event, /// /// The ignored event /// - protected EventActivities Ignore(Event @event) + protected internal EventActivities Ignore(Event @event) { ActivityBinder activityBinder = new IgnoreEventActivityBinder(@event); @@ -929,7 +1103,7 @@ protected EventActivities Ignore(Event @event) /// The event data type /// The ignored event /// - protected EventActivities Ignore(Event @event) + protected internal EventActivities Ignore(Event @event) { ActivityBinder activityBinder = new IgnoreEventActivityBinder(@event); @@ -943,7 +1117,7 @@ protected EventActivities Ignore(Event @event) /// The ignored event /// The filter to apply to the event data /// - protected EventActivities Ignore(Event @event, + protected internal EventActivities Ignore(Event @event, StateMachineEventFilter filter) { ActivityBinder activityBinder = new IgnoreEventActivityBinder(@event, filter); @@ -955,7 +1129,7 @@ protected EventActivities Ignore(Event @event, /// Specifies a callback to invoke when an event is raised in a state where the event is not handled /// /// The unhandled event callback - protected void OnUnhandledEvent(UnhandledEventCallback callback) + protected internal void OnUnhandledEvent(UnhandledEventCallback callback) { if (callback == null) throw new ArgumentNullException(nameof(callback)); @@ -979,7 +1153,6 @@ void RegisterImplicit() declaration.Declare(this); } - protected static class ConfigurationHelpers { public static StateMachineRegistration[] GetRegistrations(AutomatonymousStateMachine stateMachine) @@ -1185,5 +1358,23 @@ public void Declare(object stateMachine) } } } + + private StateMachine Modify(Action> modifier) + { + StateMachineModifier builder = new InternalStateMachineModifier(this); + modifier(builder); + builder.Apply(); + + return this; + } + + private class BuilderStateMachine : AutomatonymousStateMachine { } + + public static AutomatonymousStateMachine Build(Action> modifier) + { + var machine = new BuilderStateMachine(); + machine.Modify(modifier); + return machine; + } } } diff --git a/src/Automatonymous/Builders/InternalStateMachineEventActivitiesBuilder.cs b/src/Automatonymous/Builders/InternalStateMachineEventActivitiesBuilder.cs new file mode 100644 index 00000000..18b94d25 --- /dev/null +++ b/src/Automatonymous/Builders/InternalStateMachineEventActivitiesBuilder.cs @@ -0,0 +1,174 @@ +using Automatonymous.Binders; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Automatonymous +{ + internal class InternalStateMachineEventActivitiesBuilder : StateMachineEventActivitiesBuilder + where TInstance : class + { + readonly AutomatonymousStateMachine _machine; + readonly StateMachineModifier _modifier; + readonly Action[]> _committer; + readonly List> _activities; + + public InternalStateMachineEventActivitiesBuilder(AutomatonymousStateMachine machine, StateMachineModifier modifier, Action[]> committer) + { + _machine = machine ?? throw new ArgumentNullException(nameof(machine)); + _modifier = modifier ?? throw new ArgumentNullException(nameof(modifier)); + _committer = committer ?? throw new ArgumentNullException(nameof(committer)); + _activities = new List>(); + IsCommitted = false; + } + + public bool IsCommitted { get; private set; } + + public State Initial => _modifier.Initial; + public State Final => _modifier.Final; + + public StateMachineModifier CommitActivities() + { + _committer(_activities.ToArray()); + IsCommitted = true; + return _modifier; + } + + #region Pass-through Modifier + public StateMachineModifier AfterLeave(State state, Func, EventActivityBinder> activityCallback) + => CommitActivities().AfterLeave(state, activityCallback); + + public StateMachineModifier AfterLeaveAny(Func, EventActivityBinder> activityCallback) + => CommitActivities().AfterLeaveAny(activityCallback); + + public StateMachineModifier BeforeEnter(State state, Func, EventActivityBinder> activityCallback) + => CommitActivities().BeforeEnter(state, activityCallback); + + public StateMachineModifier BeforeEnterAny(Func, EventActivityBinder> activityCallback) + => CommitActivities().BeforeEnterAny(activityCallback); + + public StateMachineModifier CompositeEvent(Event @event, Expression> trackingPropertyExpression, params Event[] events) + => CommitActivities().CompositeEvent(@event, trackingPropertyExpression, events); + + public StateMachineModifier CompositeEvent(Event @event, Expression> trackingPropertyExpression, CompositeEventOptions options, params Event[] events) + => CommitActivities().CompositeEvent(@event, trackingPropertyExpression, options, events); + + public StateMachineModifier CompositeEvent(Event @event, Expression> trackingPropertyExpression, params Event[] events) + => CommitActivities().CompositeEvent(@event, trackingPropertyExpression, events); + + public StateMachineModifier CompositeEvent(Event @event, Expression> trackingPropertyExpression, CompositeEventOptions options, params Event[] events) + => CommitActivities().CompositeEvent(@event, trackingPropertyExpression, options, events); + + public StateMachineEventActivitiesBuilder During(params State[] states) + => CommitActivities().During(states); + + public StateMachineEventActivitiesBuilder DuringAny() + => CommitActivities().DuringAny(); + + public StateMachineModifier Event(string name, out Event @event) + => CommitActivities().Event(name, out @event); + + public StateMachineModifier Event(string name, out Event @event) + => CommitActivities().Event(name, out @event); + + public StateMachineModifier Event(Expression> propertyExpression, Expression>> eventPropertyExpression) where TProperty : class + => CommitActivities().Event(propertyExpression, eventPropertyExpression); + + public StateMachineModifier Finally(Func, EventActivityBinder> activityCallback) + => CommitActivities().Finally(activityCallback); + + public StateMachineEventActivitiesBuilder Initially() + => CommitActivities().Initially(); + + public StateMachineModifier InstanceState(Expression> instanceStateProperty) + => CommitActivities().InstanceState(instanceStateProperty); + + public StateMachineModifier InstanceState(Expression> instanceStateProperty) + => CommitActivities().InstanceState(instanceStateProperty); + + public StateMachineModifier InstanceState(Expression> instanceStateProperty, params State[] states) + => CommitActivities().InstanceState(instanceStateProperty, states); + + public StateMachineModifier Name(string machineName) + => CommitActivities().Name(machineName); + + public StateMachineModifier OnUnhandledEvent(UnhandledEventCallback callback) + => CommitActivities().OnUnhandledEvent(callback); + + public StateMachineModifier State(string name, out State state) + => CommitActivities().State(name, out state); + + public StateMachineModifier State(string name, out State state) + => CommitActivities().State(name, out state); + + public StateMachineModifier State(Expression> propertyExpression, Expression> statePropertyExpression) where TProperty : class + => CommitActivities().State(propertyExpression, statePropertyExpression); + + public StateMachineModifier SubState(State subState, State superState) + => CommitActivities().SubState(subState, superState); + + public StateMachineModifier SubState(string name, State superState, out State subState) + => CommitActivities().SubState(name, superState, out subState); + + public StateMachineModifier SubState(Expression> propertyExpression, Expression> statePropertyExpression, State superState) where TProperty : class + => CommitActivities().SubState(propertyExpression, statePropertyExpression, superState); + + public StateMachineModifier WhenEnter(State state, Func, EventActivityBinder> activityCallback) + => CommitActivities().WhenEnter(state, activityCallback); + + public StateMachineModifier WhenEnterAny(Func, EventActivityBinder> activityCallback) + => CommitActivities().WhenEnterAny(activityCallback); + + public StateMachineModifier WhenLeave(State state, Func, EventActivityBinder> activityCallback) + => CommitActivities().WhenLeave(state, activityCallback); + + public StateMachineModifier WhenLeaveAny(Func, EventActivityBinder> activityCallback) + => CommitActivities().WhenLeaveAny(activityCallback); + #endregion + + public StateMachineEventActivitiesBuilder When(Event @event, Func, EventActivityBinder> configure) + { + _activities.Add(configure(_machine.When(@event))); + return this; + } + + public StateMachineEventActivitiesBuilder When(Event @event, StateMachineEventFilter filter, Func, EventActivityBinder> configure) + { + _activities.Add(configure(_machine.When(@event, filter))); + return this; + } + + public StateMachineEventActivitiesBuilder When(Event @event, Func, EventActivityBinder> configure) + { + _activities.Add(configure(_machine.When(@event))); + return this; + } + + public StateMachineEventActivitiesBuilder When(Event @event, StateMachineEventFilter filter, Func, EventActivityBinder> configure) + { + _activities.Add(configure(_machine.When(@event, filter))); + return this; + } + + public StateMachineEventActivitiesBuilder Ignore(Event @event) + { + _activities.Add(_machine.Ignore(@event)); + return this; + } + + public StateMachineEventActivitiesBuilder Ignore(Event @event) + { + _activities.Add(_machine.Ignore(@event)); + return this; + } + + public StateMachineEventActivitiesBuilder Ignore(Event @event, StateMachineEventFilter filter) + { + _activities.Add(_machine.Ignore(@event, filter)); + return this; + } + + public void Apply() + => CommitActivities().Apply(); + } +} diff --git a/src/Automatonymous/Builders/InternalStateMachineModifier.cs b/src/Automatonymous/Builders/InternalStateMachineModifier.cs new file mode 100644 index 00000000..0ccab148 --- /dev/null +++ b/src/Automatonymous/Builders/InternalStateMachineModifier.cs @@ -0,0 +1,230 @@ +using Automatonymous.Binders; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Automatonymous +{ + internal class InternalStateMachineModifier : StateMachineModifier + where TInstance : class + { + readonly AutomatonymousStateMachine _machine; + readonly List> _activityBuilders; + + public State Initial => _machine.Initial; + public State Final => _machine.Final; + + public InternalStateMachineModifier(AutomatonymousStateMachine machine) + { + _machine = machine ?? throw new ArgumentNullException(nameof(machine)); + _activityBuilders = new List>(); + } + + public void Apply() + { + var uncommittedActivities = _activityBuilders + .Where(builder => !builder.IsCommitted) + .ToArray(); + + foreach (var builder in uncommittedActivities) + builder.CommitActivities(); + } + + public StateMachineEventActivitiesBuilder During(params State[] states) + { + var builder = new InternalStateMachineEventActivitiesBuilder(_machine, this, (activities) => _machine.During(states, activities)); + _activityBuilders.Add(builder); + return builder; + } + + public StateMachineEventActivitiesBuilder DuringAny() + { + var builder = new InternalStateMachineEventActivitiesBuilder(_machine, this, (activities) => _machine.DuringAny(activities)); + _activityBuilders.Add(builder); + return builder; + } + + public StateMachineEventActivitiesBuilder Initially() + { + var builder = new InternalStateMachineEventActivitiesBuilder(_machine, this, (activities) => _machine.Initially(activities)); + _activityBuilders.Add(builder); + return builder; + } + + public StateMachineModifier AfterLeave(State state, Func, EventActivityBinder> activityCallback) + { + _machine.AfterLeave(state, activityCallback); + return this; + } + + public StateMachineModifier AfterLeaveAny(Func, EventActivityBinder> activityCallback) + { + _machine.AfterLeaveAny(activityCallback); + return this; + } + + public StateMachineModifier BeforeEnter(State state, Func, EventActivityBinder> activityCallback) + { + _machine.BeforeEnter(state, activityCallback); + return this; + } + + public StateMachineModifier BeforeEnterAny(Func, EventActivityBinder> activityCallback) + { + _machine.BeforeEnterAny(activityCallback); + return this; + } + + public StateMachineModifier CompositeEvent(Event @event, Expression> trackingPropertyExpression, params Event[] events) + { + _machine.CompositeEvent(@event, trackingPropertyExpression, events); + return this; + } + + public StateMachineModifier CompositeEvent(Event @event, Expression> trackingPropertyExpression, CompositeEventOptions options, params Event[] events) + { + _machine.CompositeEvent(@event, trackingPropertyExpression, options, events); + return this; + } + + public StateMachineModifier CompositeEvent(Event @event, Expression> trackingPropertyExpression, params Event[] events) + { + _machine.CompositeEvent(@event, trackingPropertyExpression, events); + return this; + } + + public StateMachineModifier CompositeEvent(Event @event, Expression> trackingPropertyExpression, CompositeEventOptions options, params Event[] events) + { + _machine.CompositeEvent(@event, trackingPropertyExpression, options, events); + return this; + } + + public StateMachineModifier Event(string name, out Event @event) + { + @event = _machine.Event(name); + return this; + } + + public StateMachineModifier Event(string name, out Event @event) + { + @event = _machine.Event(name); + return this; + } + + public StateMachineModifier Event(Expression> propertyExpression, Expression>> eventPropertyExpression) where TProperty : class + { + _machine.Event(propertyExpression, eventPropertyExpression); + return this; + } + + public StateMachineModifier Finally(Func, EventActivityBinder> activityCallback) + { + _machine.Finally(activityCallback); + return this; + } + + public StateMachineModifier InstanceState(Expression> instanceStateProperty) + { + _machine.InstanceState(instanceStateProperty); + return this; + } + + public StateMachineModifier InstanceState(Expression> instanceStateProperty) + { + _machine.InstanceState(instanceStateProperty); + return this; + } + + public StateMachineModifier InstanceState(Expression> instanceStateProperty, params State[] states) + { + _machine.InstanceState(instanceStateProperty, states); + return this; + } + + public StateMachineModifier InstanceState(Expression> instanceStateProperty, params string[] stateNames) + { + // NOTE: May need to re-think this; Assumes the states have already been declared. + State[] states = stateNames + .Select(name => _machine.GetState(name)) + .ToArray(); + + _machine.InstanceState(instanceStateProperty, states); + return this; + } + + public StateMachineModifier Name(string machineName) + { + _machine.Name(machineName); + return this; + } + + public StateMachineModifier OnUnhandledEvent(UnhandledEventCallback callback) + { + _machine.OnUnhandledEvent(callback); + return this; + } + + public StateMachineModifier State(string name, out State state) + { + state = _machine.State(name); + return this; + } + + public StateMachineModifier State(string name, out State state) + { + state = _machine.State(name); + return this; + } + + public StateMachineModifier State(Expression> propertyExpression, Expression> statePropertyExpression) where TProperty : class + { + _machine.State(propertyExpression, statePropertyExpression); + return this; + } + + public StateMachineModifier SubState(State subState, State superState) + { + _machine.SubState(subState, superState); + return this; + } + + public StateMachineModifier SubState(string name, State superState, out State subState) + { + subState = _machine.SubState(name, superState); + return this; + } + + public StateMachineModifier SubState(Expression> propertyExpression, Expression> statePropertyExpression, State superState) where TProperty : class + { + _machine.SubState(propertyExpression, statePropertyExpression, superState); + return this; + } + + public StateMachineModifier WhenEnter(State state, Func, EventActivityBinder> activityCallback) + { + _machine.WhenEnter(state, activityCallback); + return this; + } + + public StateMachineModifier WhenEnterAny(Func, EventActivityBinder> activityCallback) + { + _machine.WhenEnterAny(activityCallback); + return this; + } + + public StateMachineModifier WhenLeave(State state, Func, EventActivityBinder> activityCallback) + { + _machine.WhenLeave(state, activityCallback); + return this; + } + + public StateMachineModifier WhenLeaveAny(Func, EventActivityBinder> activityCallback) + { + _machine.WhenLeaveAny(activityCallback); + return this; + } + + + } +} diff --git a/src/Automatonymous/Builders/StateMachineEventActivitiesBuilder.cs b/src/Automatonymous/Builders/StateMachineEventActivitiesBuilder.cs new file mode 100644 index 00000000..7baf5832 --- /dev/null +++ b/src/Automatonymous/Builders/StateMachineEventActivitiesBuilder.cs @@ -0,0 +1,23 @@ +using Automatonymous.Binders; +using System; + +namespace Automatonymous +{ + public interface StateMachineEventActivitiesBuilder : + StateMachineModifier + where TInstance : class + { + StateMachineEventActivitiesBuilder When(Event @event, Func, EventActivityBinder> configure); + StateMachineEventActivitiesBuilder When(Event @event, StateMachineEventFilter filter, Func, EventActivityBinder> configure); + StateMachineEventActivitiesBuilder When(Event @event, Func, EventActivityBinder> configure); + StateMachineEventActivitiesBuilder When(Event @event, + StateMachineEventFilter filter, Func, EventActivityBinder> configure); + StateMachineEventActivitiesBuilder Ignore(Event @event); + StateMachineEventActivitiesBuilder Ignore(Event @event); + StateMachineEventActivitiesBuilder Ignore(Event @event, + StateMachineEventFilter filter); + + bool IsCommitted { get; } + StateMachineModifier CommitActivities(); + } +} diff --git a/src/Automatonymous/Builders/StateMachineModifier.cs b/src/Automatonymous/Builders/StateMachineModifier.cs new file mode 100644 index 00000000..3792de48 --- /dev/null +++ b/src/Automatonymous/Builders/StateMachineModifier.cs @@ -0,0 +1,67 @@ +using Automatonymous.Binders; +using System; +using System.Linq.Expressions; + +namespace Automatonymous +{ + public interface StateMachineModifier + where TInstance : class + { + State Initial { get; } + State Final { get; } + + StateMachineModifier InstanceState(Expression> instanceStateProperty); + StateMachineModifier InstanceState(Expression> instanceStateProperty); + StateMachineModifier InstanceState(Expression> instanceStateProperty, params State[] states); + StateMachineModifier Name(string machineName); + StateMachineModifier Event(string name, out Event @event); + StateMachineModifier Event(string name, out Event @event); + + StateMachineModifier Event(Expression> propertyExpression, + Expression>> eventPropertyExpression) + where TProperty : class; + StateMachineModifier CompositeEvent(Event @event, + Expression> trackingPropertyExpression, + params Event[] events); + StateMachineModifier CompositeEvent(Event @event, + Expression> trackingPropertyExpression, + CompositeEventOptions options, + params Event[] events); + StateMachineModifier CompositeEvent(Event @event, + Expression> trackingPropertyExpression, + params Event[] events); + StateMachineModifier CompositeEvent(Event @event, + Expression> trackingPropertyExpression, + CompositeEventOptions options, + params Event[] events); + StateMachineModifier State(string name, out State state); + StateMachineModifier State(string name, out State state); + StateMachineModifier State(Expression> propertyExpression, + Expression> statePropertyExpression) + where TProperty : class; + StateMachineModifier SubState(State subState, State superState); + StateMachineModifier SubState(string name, State superState, out State subState); + StateMachineModifier SubState(Expression> propertyExpression, + Expression> statePropertyExpression, State superState) + where TProperty : class; + + StateMachineEventActivitiesBuilder During(params State[] states); + StateMachineEventActivitiesBuilder Initially(); + StateMachineEventActivitiesBuilder DuringAny(); + StateMachineModifier Finally(Func, EventActivityBinder> activityCallback); + + StateMachineModifier WhenEnter(State state, Func, EventActivityBinder> activityCallback); + StateMachineModifier WhenEnterAny(Func, EventActivityBinder> activityCallback); + StateMachineModifier WhenLeaveAny(Func, EventActivityBinder> activityCallback); + StateMachineModifier BeforeEnterAny(Func, EventActivityBinder> activityCallback); + StateMachineModifier AfterLeaveAny(Func, EventActivityBinder> activityCallback); + StateMachineModifier WhenLeave(State state, Func, EventActivityBinder> activityCallback); + StateMachineModifier BeforeEnter(State state, + Func, EventActivityBinder> activityCallback); + StateMachineModifier AfterLeave(State state, + Func, EventActivityBinder> activityCallback); + StateMachineModifier OnUnhandledEvent(UnhandledEventCallback callback); + + void Apply(); + } +} diff --git a/src/Automatonymous/State.cs b/src/Automatonymous/State.cs index 06f1a3c9..c7adfcf3 100644 --- a/src/Automatonymous/State.cs +++ b/src/Automatonymous/State.cs @@ -85,6 +85,7 @@ public interface State : /// /// void AddSubstate(State subState); + void SetSuperState(State superState); /// /// True if the specified state is included in the state diff --git a/src/Automatonymous/States/StateMachineState.cs b/src/Automatonymous/States/StateMachineState.cs index 737ef863..fedcb52b 100644 --- a/src/Automatonymous/States/StateMachineState.cs +++ b/src/Automatonymous/States/StateMachineState.cs @@ -21,7 +21,7 @@ public class StateMachineState : readonly string _name; readonly EventObserver _observer; readonly HashSet> _subStates; - readonly State _superState; + State _superState; public StateMachineState(AutomatonymousStateMachine machine, string name, EventObserver observer, State superState = null) @@ -44,8 +44,6 @@ public StateMachineState(AutomatonymousStateMachine machine, string n Ignore(AfterLeave); _subStates = new HashSet>(); - _superState = superState; - superState?.AddSubstate(this); } @@ -54,7 +52,7 @@ public bool Equals(State other) return string.CompareOrdinal(_name, other?.Name ?? "") == 0; } - public State SuperState => _superState; + public State SuperState { get; } public string Name => _name; public Event Enter { get; } public Event Leave { get; } @@ -216,9 +214,15 @@ public void AddSubstate(State subState) if (_name.Equals(subState.Name)) throw new ArgumentException("A state cannot be a substate of itself", nameof(subState)); + subState.SetSuperState(this); _subStates.Add(subState); } + public void SetSuperState(State superState) + { + _superState = superState; + } + public bool HasState(State state) { return _name.Equals(state.Name) || _subStates.Any(s => s.HasState(state)); From e9fef1f892cb5447222096202aaf4d6f75688320 Mon Sep 17 00:00:00 2001 From: Justin Coulston Date: Wed, 11 Nov 2020 00:26:22 -0500 Subject: [PATCH 2/8] Incremented version number based on SemVer standards. --- src/Automatonymous/Automatonymous.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Automatonymous/Automatonymous.csproj b/src/Automatonymous/Automatonymous.csproj index 8e72ff0b..dbb8338b 100644 --- a/src/Automatonymous/Automatonymous.csproj +++ b/src/Automatonymous/Automatonymous.csproj @@ -10,6 +10,9 @@ MassTransit;Automatonymous Automatonymous, an open source state machine library, usable with MassTransit + 1.1.0.0 + 1.1.0.0 + 1.1.0 From fe1523ecb102d56b641c792d220f4794305410fd Mon Sep 17 00:00:00 2001 From: Justin Coulston Date: Wed, 11 Nov 2020 00:41:29 -0500 Subject: [PATCH 3/8] Revert "Incremented version number based on SemVer standards." This reverts commit e9fef1f892cb5447222096202aaf4d6f75688320. --- src/Automatonymous/Automatonymous.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Automatonymous/Automatonymous.csproj b/src/Automatonymous/Automatonymous.csproj index dbb8338b..8e72ff0b 100644 --- a/src/Automatonymous/Automatonymous.csproj +++ b/src/Automatonymous/Automatonymous.csproj @@ -10,9 +10,6 @@ MassTransit;Automatonymous Automatonymous, an open source state machine library, usable with MassTransit - 1.1.0.0 - 1.1.0.0 - 1.1.0 From 9364281bc8592f75dd7abb9e791face793c4a4fc Mon Sep 17 00:00:00 2001 From: Justin Coulston Date: Wed, 11 Nov 2020 09:49:42 -0500 Subject: [PATCH 4/8] Re-worked `Event` methods in Automatonymous to re-use code as much as possible. --- .../AutomatonymousStateMachine.cs | 58 +++++++------------ 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/src/Automatonymous/AutomatonymousStateMachine.cs b/src/Automatonymous/AutomatonymousStateMachine.cs index b74289f3..16eebcee 100644 --- a/src/Automatonymous/AutomatonymousStateMachine.cs +++ b/src/Automatonymous/AutomatonymousStateMachine.cs @@ -248,57 +248,41 @@ protected internal void Name(string machineName) /// /// protected internal virtual void Event(Expression> propertyExpression) - { - PropertyInfo property = propertyExpression.GetPropertyInfo(); - - DeclareTriggerEvent(property); - } + => DeclarePropertyBasedEvent(prop => DeclareTriggerEvent(prop.Name), propertyExpression); protected internal virtual Event Event(string name) - { - var @event = new TriggerEvent(name); - _eventCache[name] = new StateMachineEvent(@event, false); - return @event; - } + => DeclareTriggerEvent(name); - void DeclareTriggerEvent(PropertyInfo property) - { - string name = property.Name; - - var @event = new TriggerEvent(name); - - ConfigurationHelpers.InitializeEvent(this, property, @event); - - _eventCache[name] = new StateMachineEvent(@event, false); - } + Event DeclareTriggerEvent(string name) + => DeclareEvent(name => new TriggerEvent(name), name); /// /// Declares a data event on the state machine, and initializes the property /// /// The event property protected internal virtual void Event(Expression>> propertyExpression) - { - PropertyInfo property = propertyExpression.GetPropertyInfo(); - - DeclareDataEvent(property); - } + => DeclarePropertyBasedEvent(prop => DeclareDataEvent(prop.Name), propertyExpression); protected internal virtual Event Event(string name) - { - var @event = new DataEvent(name); - _eventCache[name] = new StateMachineEvent(@event, false); - return @event; - } + => DeclareDataEvent(name); - void DeclareDataEvent(PropertyInfo property) - { - string name = property.Name; - - var @event = new DataEvent(name); + Event DeclareDataEvent(string name) + => DeclareEvent(name => new DataEvent(name), name); + void DeclarePropertyBasedEvent(Func ctor, Expression> propertyExpression) + where TEvent : Event + { + PropertyInfo property = propertyExpression.GetPropertyInfo(); + TEvent @event = ctor(property); ConfigurationHelpers.InitializeEvent(this, property, @event); + } + TEvent DeclareEvent(Func ctor, string name) + where TEvent : Event + { + var @event = ctor(name); _eventCache[name] = new StateMachineEvent(@event, false); + return @event; } /// @@ -1331,7 +1315,7 @@ public void Declare(object stateMachine) if (existing != null) return; - machine.DeclareTriggerEvent(_propertyInfo); + machine.DeclareTriggerEvent(_propertyInfo.Name); } } @@ -1354,7 +1338,7 @@ public void Declare(object stateMachine) if (existing != null) return; - machine.DeclareDataEvent(_propertyInfo); + machine.DeclareDataEvent(_propertyInfo.Name); } } } From 6eabdbf626879df2c252aac1485c7a56c4ee0da1 Mon Sep 17 00:00:00 2001 From: Justin Coulston Date: Wed, 11 Nov 2020 09:57:44 -0500 Subject: [PATCH 5/8] Corrected a bug with not initializing property properly with Declare Event changes. --- .../AutomatonymousStateMachine.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Automatonymous/AutomatonymousStateMachine.cs b/src/Automatonymous/AutomatonymousStateMachine.cs index 16eebcee..4a7ad6a5 100644 --- a/src/Automatonymous/AutomatonymousStateMachine.cs +++ b/src/Automatonymous/AutomatonymousStateMachine.cs @@ -248,7 +248,7 @@ protected internal void Name(string machineName) /// /// protected internal virtual void Event(Expression> propertyExpression) - => DeclarePropertyBasedEvent(prop => DeclareTriggerEvent(prop.Name), propertyExpression); + => DeclarePropertyBasedEvent(prop => DeclareTriggerEvent(prop.Name), propertyExpression.GetPropertyInfo()); protected internal virtual Event Event(string name) => DeclareTriggerEvent(name); @@ -261,7 +261,7 @@ Event DeclareTriggerEvent(string name) /// /// The event property protected internal virtual void Event(Expression>> propertyExpression) - => DeclarePropertyBasedEvent(prop => DeclareDataEvent(prop.Name), propertyExpression); + => DeclarePropertyBasedEvent(prop => DeclareDataEvent(prop.Name), propertyExpression.GetPropertyInfo()); protected internal virtual Event Event(string name) => DeclareDataEvent(name); @@ -269,10 +269,9 @@ protected internal virtual Event Event(string name) Event DeclareDataEvent(string name) => DeclareEvent(name => new DataEvent(name), name); - void DeclarePropertyBasedEvent(Func ctor, Expression> propertyExpression) + void DeclarePropertyBasedEvent(Func ctor, PropertyInfo property) where TEvent : Event { - PropertyInfo property = propertyExpression.GetPropertyInfo(); TEvent @event = ctor(property); ConfigurationHelpers.InitializeEvent(this, property, @event); } @@ -432,23 +431,23 @@ void CompositeEvent(Expression> propertyExpression, CompositeEventSt } } - protected internal virtual void CompositeEvent(string name, + internal virtual void CompositeEvent(string name, Expression> trackingPropertyExpression, params Event[] events) => CompositeEvent(name, new StructCompositeEventStatusAccessor(trackingPropertyExpression.GetPropertyInfo()), CompositeEventOptions.None, events); - protected internal virtual Event CompositeEvent(string name, + internal virtual Event CompositeEvent(string name, Expression> trackingPropertyExpression, CompositeEventOptions options, params Event[] events) => CompositeEvent(name, new StructCompositeEventStatusAccessor(trackingPropertyExpression.GetPropertyInfo()), options, events); - protected internal virtual Event CompositeEvent(string name, + internal virtual Event CompositeEvent(string name, Expression> trackingPropertyExpression, params Event[] events) => CompositeEvent(name, new IntCompositeEventStatusAccessor(trackingPropertyExpression.GetPropertyInfo()), CompositeEventOptions.None, events); - protected internal virtual Event CompositeEvent(string name, + internal virtual Event CompositeEvent(string name, Expression> trackingPropertyExpression, CompositeEventOptions options, params Event[] events) @@ -1315,7 +1314,8 @@ public void Declare(object stateMachine) if (existing != null) return; - machine.DeclareTriggerEvent(_propertyInfo.Name); + machine.DeclarePropertyBasedEvent((prop) => machine.DeclareTriggerEvent(prop.Name), _propertyInfo); + //machine.DeclareTriggerEvent(_propertyInfo.Name); } } @@ -1338,7 +1338,8 @@ public void Declare(object stateMachine) if (existing != null) return; - machine.DeclareDataEvent(_propertyInfo.Name); + machine.DeclarePropertyBasedEvent((prop) => machine.DeclareDataEvent(prop.Name), _propertyInfo); + //machine.DeclareDataEvent(_propertyInfo.Name); } } } From 30514794e47747a0d98edb4656eea1ade1f75e81 Mon Sep 17 00:00:00 2001 From: Justin Coulston Date: Wed, 11 Nov 2020 10:18:11 -0500 Subject: [PATCH 6/8] Moved builder-related interfaces and classes to Automatonymous.Builder namespace to avoid cluttering the root namespace --- src/Automatonymous.Tests/Dynamic Modify/Request_Specs.cs | 1 + src/Automatonymous/AutomatonymousStateMachine.cs | 1 + .../Builders/InternalStateMachineEventActivitiesBuilder.cs | 2 +- src/Automatonymous/Builders/InternalStateMachineModifier.cs | 2 +- .../Builders/StateMachineEventActivitiesBuilder.cs | 2 +- src/Automatonymous/Builders/StateMachineModifier.cs | 2 +- 6 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Automatonymous.Tests/Dynamic Modify/Request_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Request_Specs.cs index 956d521b..ba996fc9 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/Request_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/Request_Specs.cs @@ -6,6 +6,7 @@ namespace Request_Specs using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; + using Automatonymous.Builder; using Binders; using GreenPipes; using NUnit.Framework; diff --git a/src/Automatonymous/AutomatonymousStateMachine.cs b/src/Automatonymous/AutomatonymousStateMachine.cs index 4a7ad6a5..d6bf2d07 100644 --- a/src/Automatonymous/AutomatonymousStateMachine.cs +++ b/src/Automatonymous/AutomatonymousStateMachine.cs @@ -9,6 +9,7 @@ namespace Automatonymous using System.Threading.Tasks; using Accessors; using Activities; + using Automatonymous.Builder; using Binders; using Contexts; using Events; diff --git a/src/Automatonymous/Builders/InternalStateMachineEventActivitiesBuilder.cs b/src/Automatonymous/Builders/InternalStateMachineEventActivitiesBuilder.cs index 18b94d25..422e81f2 100644 --- a/src/Automatonymous/Builders/InternalStateMachineEventActivitiesBuilder.cs +++ b/src/Automatonymous/Builders/InternalStateMachineEventActivitiesBuilder.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq.Expressions; -namespace Automatonymous +namespace Automatonymous.Builder { internal class InternalStateMachineEventActivitiesBuilder : StateMachineEventActivitiesBuilder where TInstance : class diff --git a/src/Automatonymous/Builders/InternalStateMachineModifier.cs b/src/Automatonymous/Builders/InternalStateMachineModifier.cs index 0ccab148..5b082ff0 100644 --- a/src/Automatonymous/Builders/InternalStateMachineModifier.cs +++ b/src/Automatonymous/Builders/InternalStateMachineModifier.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Linq.Expressions; -namespace Automatonymous +namespace Automatonymous.Builder { internal class InternalStateMachineModifier : StateMachineModifier where TInstance : class diff --git a/src/Automatonymous/Builders/StateMachineEventActivitiesBuilder.cs b/src/Automatonymous/Builders/StateMachineEventActivitiesBuilder.cs index 7baf5832..53fe4e2d 100644 --- a/src/Automatonymous/Builders/StateMachineEventActivitiesBuilder.cs +++ b/src/Automatonymous/Builders/StateMachineEventActivitiesBuilder.cs @@ -1,7 +1,7 @@ using Automatonymous.Binders; using System; -namespace Automatonymous +namespace Automatonymous.Builder { public interface StateMachineEventActivitiesBuilder : StateMachineModifier diff --git a/src/Automatonymous/Builders/StateMachineModifier.cs b/src/Automatonymous/Builders/StateMachineModifier.cs index 3792de48..94e9fbd2 100644 --- a/src/Automatonymous/Builders/StateMachineModifier.cs +++ b/src/Automatonymous/Builders/StateMachineModifier.cs @@ -2,7 +2,7 @@ using System; using System.Linq.Expressions; -namespace Automatonymous +namespace Automatonymous.Builder { public interface StateMachineModifier where TInstance : class From faf324d5c7cdf5f38c5af76cc637d5d50a25d461 Mon Sep 17 00:00:00 2001 From: Justin Coulston Date: Wed, 11 Nov 2020 11:33:45 -0500 Subject: [PATCH 7/8] Removed `SetSuperState` as it wasn't necessary and modified the general approach for the builder. --- .../Dynamic Modify/Observable_Specs.cs | 6 ++---- .../Dynamic Modify/Telephone_Sample.cs | 3 +-- .../AutomatonymousStateMachine.cs | 18 ++++++++---------- ...ternalStateMachineEventActivitiesBuilder.cs | 3 --- .../Builders/InternalStateMachineModifier.cs | 6 ------ .../Builders/StateMachineModifier.cs | 2 +- src/Automatonymous/State.cs | 2 +- src/Automatonymous/States/StateMachineState.cs | 8 ++------ 8 files changed, 15 insertions(+), 33 deletions(-) diff --git a/src/Automatonymous.Tests/Dynamic Modify/Observable_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Observable_Specs.cs index f8379e8c..5045cdf5 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/Observable_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/Observable_Specs.cs @@ -150,11 +150,10 @@ public void Specifying_an_event_activity() _machine = AutomatonymousStateMachine .Build(builder => builder .State("Running", out Running) - .State("Resting", out Resting) .Event("Initialized", out Initialized) .Event("LegCramped", out LegCramped) .Event("Finish", out Finish) - .SubState(Resting, Running) + .SubState("Resting", Running, out Resting) .During(builder.Initial) .When(Initialized, b => b.TransitionTo(Running)) .During(Running) @@ -298,12 +297,11 @@ public void Specifying_an_event_activity() _machine = AutomatonymousStateMachine .Build(builder => builder .State("Running", out Running) - .State("Resting", out Resting) .Event("Initialized", out Initialized) .Event("LegCramped", out LegCramped) .Event("Finish", out Finish) .Event("Recovered", out Recovered) - .SubState(Resting, Running) + .SubState("Resting", Running, out Resting) .During(builder.Initial) .When(Initialized, b => b.TransitionTo(Running)) .During(Running) diff --git a/src/Automatonymous.Tests/Dynamic Modify/Telephone_Sample.cs b/src/Automatonymous.Tests/Dynamic Modify/Telephone_Sample.cs index a58062e3..f74e9213 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/Telephone_Sample.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/Telephone_Sample.cs @@ -209,7 +209,6 @@ public StateMachine CreateDynamically() .State("OffHook", out OffHook) .State("Ringing", out Ringing) .State("Connected", out Connected) - .State("OnHold", out OnHold) .State("PhoneDestroyed", out PhoneDestroyed) .Event("ServiceEstablished", out ServiceEstablished) .Event("CallDialed", out CallDialed) @@ -220,7 +219,7 @@ public StateMachine CreateDynamically() .Event("TakenOffHold", out TakenOffHold) .Event("PhoneHurledAgainstWall", out PhoneHurledAgainstWall) .InstanceState(x => x.CurrentState) - .SubState(OnHold, Connected) + .SubState("OnHold", Connected, out OnHold) .Initially() .When(ServiceEstablished, b => b .Then(context => context.Instance.Number = context.Data.Digits) diff --git a/src/Automatonymous/AutomatonymousStateMachine.cs b/src/Automatonymous/AutomatonymousStateMachine.cs index d6bf2d07..f6d557cc 100644 --- a/src/Automatonymous/AutomatonymousStateMachine.cs +++ b/src/Automatonymous/AutomatonymousStateMachine.cs @@ -680,16 +680,16 @@ protected internal virtual State SubState(string name, State superSta return state; } - protected internal virtual void SubState(State subState, State superState) - { - if (superState == null) - throw new ArgumentNullException(nameof(superState)); + //protected internal virtual void SubState(State subState, State superState) + //{ + // if (superState == null) + // throw new ArgumentNullException(nameof(superState)); - State superStateInstance = GetState(superState.Name); + // State superStateInstance = GetState(superState.Name); - // If the state was already defined, don't define it again - superStateInstance.AddSubstate(subState); - } + // // If the state was already defined, don't define it again + // superStateInstance.AddSubstate(subState); + //} /// /// Declares a state on the state machine, and initialized the property @@ -1316,7 +1316,6 @@ public void Declare(object stateMachine) return; machine.DeclarePropertyBasedEvent((prop) => machine.DeclareTriggerEvent(prop.Name), _propertyInfo); - //machine.DeclareTriggerEvent(_propertyInfo.Name); } } @@ -1340,7 +1339,6 @@ public void Declare(object stateMachine) return; machine.DeclarePropertyBasedEvent((prop) => machine.DeclareDataEvent(prop.Name), _propertyInfo); - //machine.DeclareDataEvent(_propertyInfo.Name); } } } diff --git a/src/Automatonymous/Builders/InternalStateMachineEventActivitiesBuilder.cs b/src/Automatonymous/Builders/InternalStateMachineEventActivitiesBuilder.cs index 422e81f2..50095faa 100644 --- a/src/Automatonymous/Builders/InternalStateMachineEventActivitiesBuilder.cs +++ b/src/Automatonymous/Builders/InternalStateMachineEventActivitiesBuilder.cs @@ -104,9 +104,6 @@ public StateMachineModifier State(string name, out State state) public StateMachineModifier State(Expression> propertyExpression, Expression> statePropertyExpression) where TProperty : class => CommitActivities().State(propertyExpression, statePropertyExpression); - public StateMachineModifier SubState(State subState, State superState) - => CommitActivities().SubState(subState, superState); - public StateMachineModifier SubState(string name, State superState, out State subState) => CommitActivities().SubState(name, superState, out subState); diff --git a/src/Automatonymous/Builders/InternalStateMachineModifier.cs b/src/Automatonymous/Builders/InternalStateMachineModifier.cs index 5b082ff0..b6ecf84b 100644 --- a/src/Automatonymous/Builders/InternalStateMachineModifier.cs +++ b/src/Automatonymous/Builders/InternalStateMachineModifier.cs @@ -183,12 +183,6 @@ public StateMachineModifier State(Expression SubState(State subState, State superState) - { - _machine.SubState(subState, superState); - return this; - } - public StateMachineModifier SubState(string name, State superState, out State subState) { subState = _machine.SubState(name, superState); diff --git a/src/Automatonymous/Builders/StateMachineModifier.cs b/src/Automatonymous/Builders/StateMachineModifier.cs index 94e9fbd2..19ea1492 100644 --- a/src/Automatonymous/Builders/StateMachineModifier.cs +++ b/src/Automatonymous/Builders/StateMachineModifier.cs @@ -39,7 +39,7 @@ StateMachineModifier CompositeEvent(Event @event, StateMachineModifier State(Expression> propertyExpression, Expression> statePropertyExpression) where TProperty : class; - StateMachineModifier SubState(State subState, State superState); + StateMachineModifier SubState(string name, State superState, out State subState); StateMachineModifier SubState(Expression> propertyExpression, Expression> statePropertyExpression, State superState) diff --git a/src/Automatonymous/State.cs b/src/Automatonymous/State.cs index c7adfcf3..ecbb6c14 100644 --- a/src/Automatonymous/State.cs +++ b/src/Automatonymous/State.cs @@ -85,7 +85,7 @@ public interface State : /// /// void AddSubstate(State subState); - void SetSuperState(State superState); + //void SetSuperState(State superState); /// /// True if the specified state is included in the state diff --git a/src/Automatonymous/States/StateMachineState.cs b/src/Automatonymous/States/StateMachineState.cs index fedcb52b..8869e84f 100644 --- a/src/Automatonymous/States/StateMachineState.cs +++ b/src/Automatonymous/States/StateMachineState.cs @@ -44,6 +44,8 @@ public StateMachineState(AutomatonymousStateMachine machine, string n Ignore(AfterLeave); _subStates = new HashSet>(); + + _superState = superState; superState?.AddSubstate(this); } @@ -214,15 +216,9 @@ public void AddSubstate(State subState) if (_name.Equals(subState.Name)) throw new ArgumentException("A state cannot be a substate of itself", nameof(subState)); - subState.SetSuperState(this); _subStates.Add(subState); } - public void SetSuperState(State superState) - { - _superState = superState; - } - public bool HasState(State state) { return _name.Equals(state.Name) || _subStates.Any(s => s.HasState(state)); From e74ac2b8ca408ca5e467b1d642c036e331cc0b2c Mon Sep 17 00:00:00 2001 From: Justin Coulston Date: Wed, 11 Nov 2020 11:37:07 -0500 Subject: [PATCH 8/8] Renamed Build to Create on the ASM Removed some unnecessary comments. --- .../Dynamic Modify/Activity_Specs.cs | 8 ++++---- .../Dynamic Modify/AnyStateTransition_Specs.cs | 2 +- .../Dynamic Modify/Anytime_Specs.cs | 2 +- .../Dynamic Modify/AsyncActivity_Specs.cs | 2 +- .../AutomatonymousStateMachine_Specs.cs | 2 +- .../Dynamic Modify/Combine_Specs.cs | 4 ++-- .../Dynamic Modify/CompositeCondition_Specs.cs | 2 +- .../Dynamic Modify/CompositeOrder_Specs.cs | 2 +- .../Dynamic Modify/Condition_Specs.cs | 4 ++-- .../Dynamic Modify/DataActivity_Specs.cs | 2 +- .../Dynamic Modify/Declarative_Specs.cs | 4 ++-- .../Dynamic Modify/Dependency_Specs.cs | 2 +- .../Dynamic Modify/EventLift_Specs.cs | 4 ++-- .../Dynamic Modify/EventObservable_Specs.cs | 2 +- .../Dynamic Modify/Event_Specs.cs | 2 +- .../Dynamic Modify/Exception_Specs.cs | 8 ++++---- .../Dynamic Modify/Faulted_Specs.cs | 2 +- .../Dynamic Modify/FilterExpression_Specs.cs | 2 +- .../Dynamic Modify/Group_Specs.cs | 2 +- .../Dynamic Modify/InstanceLift_Specs.cs | 4 ++-- .../Dynamic Modify/Introspection_Specs.cs | 2 +- .../Dynamic Modify/Observable_Specs.cs | 6 +++--- .../Dynamic Modify/RaiseEvent_Specs.cs | 2 +- .../Dynamic Modify/Request_Specs.cs | 2 +- .../Dynamic Modify/SerializeState_Specs.cs | 2 +- .../Dynamic Modify/StateExpression_Specs.cs | 6 +++--- .../Dynamic Modify/State_Specs.cs | 6 +++--- .../Dynamic Modify/Telephone_Sample.cs | 2 +- .../Dynamic Modify/Transition_Specs.cs | 4 ++-- .../Dynamic Modify/UnobservedEvent_Specs.cs | 8 ++++---- .../Dynamic Modify/Visualizer_Specs.cs | 2 +- src/Automatonymous/AutomatonymousStateMachine.cs | 13 +------------ src/Automatonymous/State.cs | 1 - 33 files changed, 53 insertions(+), 65 deletions(-) diff --git a/src/Automatonymous.Tests/Dynamic Modify/Activity_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Activity_Specs.cs index 7412e78f..92c7a4f5 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/Activity_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/Activity_Specs.cs @@ -23,7 +23,7 @@ public void Specifying_an_event_activity() { _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .Event("Initialized", out Initialized) .InstanceState(b => b.CurrentState) @@ -63,7 +63,7 @@ public void Specifying_an_event_activity() { _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .Event("Initialized", out Initialized) .InstanceState(b => b.CurrentState) @@ -128,7 +128,7 @@ public void Specifying_an_event_activity() _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .Event("Initialized", out Initialized) .InstanceState(b => b.CurrentState) @@ -189,7 +189,7 @@ public void Specifying_an_event_activity() { _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .State("Initializing", out Initializing) .Event("Initialized", out Initialized) diff --git a/src/Automatonymous.Tests/Dynamic Modify/AnyStateTransition_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/AnyStateTransition_Specs.cs index 88842485..beab03bd 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/AnyStateTransition_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/AnyStateTransition_Specs.cs @@ -36,7 +36,7 @@ public void Setup() { _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .Event("Initialized", out Initialized) .Event("Finish", out Finish) diff --git a/src/Automatonymous.Tests/Dynamic Modify/Anytime_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Anytime_Specs.cs index 33463b0d..4e4d1d0b 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/Anytime_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/Anytime_Specs.cs @@ -56,7 +56,7 @@ public void Should_not_be_handled_on_initial() public void A_state_is_declared() { _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Ready", out Ready) .Event("Init", out Init) .Event("Hello", out Hello) diff --git a/src/Automatonymous.Tests/Dynamic Modify/AsyncActivity_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/AsyncActivity_Specs.cs index 4b056a00..a0db641c 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/AsyncActivity_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/AsyncActivity_Specs.cs @@ -15,7 +15,7 @@ public async Task Should_capture_the_value() var claim = new TestInstance(); var machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out State Running) .Event("Create", out Create) .InstanceState(b => b.CurrentState) diff --git a/src/Automatonymous.Tests/Dynamic Modify/AutomatonymousStateMachine_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/AutomatonymousStateMachine_Specs.cs index 2575e5b0..a27c0bcf 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/AutomatonymousStateMachine_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/AutomatonymousStateMachine_Specs.cs @@ -91,7 +91,7 @@ class Instance { } class EventData { } private StateMachine CreateStateMachine() - => AutomatonymousStateMachine.Build(builder => builder + => AutomatonymousStateMachine.Create(builder => builder .State("ThisIsAState", out ThisIsAState) .Event("ThisIsASimpleEvent", out ThisIsASimpleEvent) .Event("ThisIsAnEventConsumingData", out ThisIsAnEventConsumingData) diff --git a/src/Automatonymous.Tests/Dynamic Modify/Combine_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Combine_Specs.cs index 106d9137..7c26e0ae 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/Combine_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/Combine_Specs.cs @@ -64,7 +64,7 @@ class Instance private StateMachine CreateStateMachine() { return AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Waiting", out Waiting) .Event("Start", out Start) .Event("First", out First) @@ -157,7 +157,7 @@ class Instance private StateMachine CreateStateMachine() { return AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Waiting", out Waiting) .Event("Start", out Start) .Event("First", out First) diff --git a/src/Automatonymous.Tests/Dynamic Modify/CompositeCondition_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/CompositeCondition_Specs.cs index 48880f3b..687ea643 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/CompositeCondition_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/CompositeCondition_Specs.cs @@ -59,7 +59,7 @@ class Instance private StateMachine CreateStateMachine() { return AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Waiting", out Waiting) .Event("Start", out Start) .Event("First", out First) diff --git a/src/Automatonymous.Tests/Dynamic Modify/CompositeOrder_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/CompositeOrder_Specs.cs index 0c2d122e..7e2e3fdb 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/CompositeOrder_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/CompositeOrder_Specs.cs @@ -77,7 +77,7 @@ class Instance private StateMachine CreateStateMachine() { return AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Waiting", out Waiting) .Event("Start", out Start) .Event("First", out First) diff --git a/src/Automatonymous.Tests/Dynamic Modify/Condition_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Condition_Specs.cs index c1421548..9f34d8c6 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/Condition_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/Condition_Specs.cs @@ -46,7 +46,7 @@ public void Specifying_an_event_activity() { _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .State("Initialized", out Initialized) .State("ShouldNotBeHere", out ShouldNotBeHere) @@ -159,7 +159,7 @@ public void Specifying_an_event_activity() { _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .State("Initialized", out Initialized) .State("ShouldNotBeHere", out ShouldNotBeHere) diff --git a/src/Automatonymous.Tests/Dynamic Modify/DataActivity_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/DataActivity_Specs.cs index e85c4d3c..bf795b4e 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/DataActivity_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/DataActivity_Specs.cs @@ -36,7 +36,7 @@ public void Specifying_an_event_activity_with_data() { _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .Event("Initialized", out Initialized) .Event("PassedValue", out PassedValue) diff --git a/src/Automatonymous.Tests/Dynamic Modify/Declarative_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Declarative_Specs.cs index 0ec0ab09..83dec6c3 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/Declarative_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/Declarative_Specs.cs @@ -28,7 +28,7 @@ public void Specifying_an_event_activity_with_data() _instance = new MyState(); _top = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Greeted", out TopGreeted) .Event("Initialized", out TopInitialized) .InstanceState(b => b.Top) @@ -36,7 +36,7 @@ public void Specifying_an_event_activity_with_data() .When(TopInitialized, b => b.TransitionTo(TopGreeted)) ); _bottom = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Ignored", out BottomIgnored) .Event("Initialized", out BottomInitialized) .InstanceState(b => b.Bottom) diff --git a/src/Automatonymous.Tests/Dynamic Modify/Dependency_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Dependency_Specs.cs index 91f1d69e..b3e50267 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/Dependency_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/Dependency_Specs.cs @@ -26,7 +26,7 @@ public void Specifying_an_event_activity() { _claim = new ClaimAdjustmentInstance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .Event("Create", out Create) .InstanceState(x => x.CurrentState) diff --git a/src/Automatonymous.Tests/Dynamic Modify/EventLift_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/EventLift_Specs.cs index 1c7aa4e7..7a7470c9 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/EventLift_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/EventLift_Specs.cs @@ -21,7 +21,7 @@ public void Should_raise_the_event() public void Specifying_an_event_activity() { _instance = new Instance(); - _machine = AutomatonymousStateMachine.Build(builder => builder + _machine = AutomatonymousStateMachine.Create(builder => builder .State("Running", out Running) .Event("Initialized", out Initialized) .During(builder.Initial) @@ -60,7 +60,7 @@ public void Specifying_an_event_activity() { _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .Event("Initialized", out Initialized) .InstanceState(b => b.CurrentState) diff --git a/src/Automatonymous.Tests/Dynamic Modify/EventObservable_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/EventObservable_Specs.cs index 4bf63b1b..932e0a59 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/EventObservable_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/EventObservable_Specs.cs @@ -30,7 +30,7 @@ public void Specifying_an_event_activity() { _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .Event("Initialized", out Initialized) .State("Running", out Running) .During(builder.Initial) diff --git a/src/Automatonymous.Tests/Dynamic Modify/Event_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Event_Specs.cs index 6b32cecb..3a1545c3 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/Event_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/Event_Specs.cs @@ -63,7 +63,7 @@ class Instance public void A_state_is_declared() { _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .Event("Hello", out Hello) .Event("EventA", out EventA) .Event("EventInt", out EventInt) diff --git a/src/Automatonymous.Tests/Dynamic Modify/Exception_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Exception_Specs.cs index 3fe69985..638698ba 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/Exception_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/Exception_Specs.cs @@ -107,7 +107,7 @@ public void Specifying_an_event_activity() { _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Failed", out Failed) .Event("Initialized", out Initialized) .During(builder.Initial) @@ -224,7 +224,7 @@ public void Specifying_an_event_activity() { _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Failed", out Failed) .Event("Initialized", out Initialized) .InstanceState(b => b.CurrentState) @@ -283,7 +283,7 @@ public void Specifying_an_event_activity() _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .Event("Initialized", out Initialized) .State("Failed", out State Failed) .InstanceState(b => b.CurrentState) @@ -386,7 +386,7 @@ public void Specifying_an_event_activity() { _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Failed", out Failed) .Event("Initialized", out Initialized) .InstanceState(b => b.CurrentState) diff --git a/src/Automatonymous.Tests/Dynamic Modify/Faulted_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Faulted_Specs.cs index 0565c0a7..9ef6cffb 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/Faulted_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/Faulted_Specs.cs @@ -35,7 +35,7 @@ public void Specifying_an_event_activity() _claim = new ClaimAdjustmentInstance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .Event("Create", out Create) .InstanceState(b => b.CurrentState) diff --git a/src/Automatonymous.Tests/Dynamic Modify/FilterExpression_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/FilterExpression_Specs.cs index ce7010ee..dae2b88c 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/FilterExpression_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/FilterExpression_Specs.cs @@ -16,7 +16,7 @@ public async Task Should_transition_to_the_proper_state() var instance = new Instance(); var machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("True", out True) .State("False", out False) .Event("Thing", out Thing) diff --git a/src/Automatonymous.Tests/Dynamic Modify/Group_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Group_Specs.cs index a0420c6a..0297cb53 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/Group_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/Group_Specs.cs @@ -30,7 +30,7 @@ public void Setup() { _instance = new PitStopInstance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("BeingServiced", out BeingServiced) .Event("VehicleArrived", out VehicleArrived) .InstanceState(b => b.OverallState) diff --git a/src/Automatonymous.Tests/Dynamic Modify/InstanceLift_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/InstanceLift_Specs.cs index 6cbf2b91..5bcbb223 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/InstanceLift_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/InstanceLift_Specs.cs @@ -22,7 +22,7 @@ public void Specifying_an_event_activity() { _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .Event("Initialized", out Initialized) .During(builder.Initial) @@ -78,7 +78,7 @@ public void Specifying_an_event_activity() { _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .Event("Initialized", out Initialized) .During(builder.Initial) diff --git a/src/Automatonymous.Tests/Dynamic Modify/Introspection_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Introspection_Specs.cs index e5fbf9f0..b64924dd 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/Introspection_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/Introspection_Specs.cs @@ -71,7 +71,7 @@ public void A_state_is_declared() { _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .Event("Ignored", out Ignored) .Event("Handshake", out Handshake) .Event("Hello", out Hello) diff --git a/src/Automatonymous.Tests/Dynamic Modify/Observable_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Observable_Specs.cs index 5045cdf5..3635ab94 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/Observable_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/Observable_Specs.cs @@ -42,7 +42,7 @@ public void Specifying_an_event_activity() _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .Event("Initialized", out Initialized) .Event("Finish", out Finish) @@ -148,7 +148,7 @@ public void Specifying_an_event_activity() _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .Event("Initialized", out Initialized) .Event("LegCramped", out LegCramped) @@ -295,7 +295,7 @@ public void Specifying_an_event_activity() _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .Event("Initialized", out Initialized) .Event("LegCramped", out LegCramped) diff --git a/src/Automatonymous.Tests/Dynamic Modify/RaiseEvent_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/RaiseEvent_Specs.cs index 842d998b..ea669dc7 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/RaiseEvent_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/RaiseEvent_Specs.cs @@ -16,7 +16,7 @@ public async Task Should_include_payload() var instance = new Instance(); var machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("True", out True) .State("False", out State False) .Event("Thing", out Thing) diff --git a/src/Automatonymous.Tests/Dynamic Modify/Request_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Request_Specs.cs index ba996fc9..1bab352f 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/Request_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/Request_Specs.cs @@ -22,7 +22,7 @@ public async Task Should_property_initialize() Event QuoteRequested = null; var machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .InstanceState(x => x.CurrentState) .Event("QuoteRequested", out QuoteRequested) .Request(x => x.ServiceAddress = new Uri("loopback://localhost/my_queue"), "QuoteRequest", out QuoteRequest) diff --git a/src/Automatonymous.Tests/Dynamic Modify/SerializeState_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/SerializeState_Specs.cs index 969a7081..57bfc404 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/SerializeState_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/SerializeState_Specs.cs @@ -17,7 +17,7 @@ public async Task Should_properly_handle_the_state_property() var instance = new Instance(); var machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("True", out True) .State("False", out False) .Event("Thing", out Thing) diff --git a/src/Automatonymous.Tests/Dynamic Modify/StateExpression_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/StateExpression_Specs.cs index 095bdd4b..934e5180 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/StateExpression_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/StateExpression_Specs.cs @@ -53,7 +53,7 @@ public void It_should_match_the_state_not_requested() public void A_state_is_declared() { _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .Event("Started", out Started) .InstanceState(x => x.CurrentState, Running) @@ -131,7 +131,7 @@ public void It_should_match_the_state_not_requested() public void A_state_is_declared() { _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .Event("Started", out Started) .InstanceState(x => x.CurrentState) @@ -208,7 +208,7 @@ public void It_should_match_the_state_not_requested() public void A_state_is_declared() { _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .Event("Started", out Started) .InstanceState(x => x.CurrentState) diff --git a/src/Automatonymous.Tests/Dynamic Modify/State_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/State_Specs.cs index 44ebb284..549dab50 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/State_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/State_Specs.cs @@ -44,7 +44,7 @@ class Instance public void A_state_is_declared() { _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .InstanceState(x => x.CurrentState) ); @@ -69,7 +69,7 @@ public void It_should_get_the_name_right() public void A_state_is_declared() { _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .Event("Started", out Started) .State("Running", out State Running) .InstanceState(x => x.CurrentState) @@ -117,7 +117,7 @@ public void It_should_get_the_name_right() public void A_state_is_declared() { _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .Event("Started", out Started) .InstanceState(x => x.CurrentState, Running) diff --git a/src/Automatonymous.Tests/Dynamic Modify/Telephone_Sample.cs b/src/Automatonymous.Tests/Dynamic Modify/Telephone_Sample.cs index f74e9213..c998a639 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/Telephone_Sample.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/Telephone_Sample.cs @@ -205,7 +205,7 @@ void StartCallTimer(PrincessModelTelephone instance) public StateMachine CreateDynamically() { return AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("OffHook", out OffHook) .State("Ringing", out Ringing) .State("Connected", out Connected) diff --git a/src/Automatonymous.Tests/Dynamic Modify/Transition_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Transition_Specs.cs index c8c9eb0b..6f32402f 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/Transition_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/Transition_Specs.cs @@ -43,7 +43,7 @@ public void Specifying_an_event_activity() { _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .Event("Initialized", out Event Initialized) .Event("Finish", out Event Finish) @@ -125,7 +125,7 @@ public void Specifying_an_event_activity() { _instance = new Instance(); _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .Event("Initialized", out Initialized) .Event("Finish", out Finish) diff --git a/src/Automatonymous.Tests/Dynamic Modify/UnobservedEvent_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/UnobservedEvent_Specs.cs index 5a1c93bf..c9e2d931 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/UnobservedEvent_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/UnobservedEvent_Specs.cs @@ -33,7 +33,7 @@ class Instance public void A_state_is_declared() { _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out State Running) .Event("Start", out Start) .Initially() @@ -73,7 +73,7 @@ class Instance public void A_state_is_declared() { _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out State Running) .Event("Start", out Start) .Event("Charge", out Charge) @@ -157,7 +157,7 @@ class Instance public void A_state_is_declared() { _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .State("Running", out Running) .Event("Start", out Start) .Event("Charge", out Charge) @@ -204,7 +204,7 @@ class Instance public void A_state_is_declared() { _machine = AutomatonymousStateMachine - .Build(builder => builder + .Create(builder => builder .Event("Start", out Start) .State("Running", out State Running) .OnUnhandledEvent(x => x.Ignore()) diff --git a/src/Automatonymous.Tests/Dynamic Modify/Visualizer_Specs.cs b/src/Automatonymous.Tests/Dynamic Modify/Visualizer_Specs.cs index 75bb5062..42baffbb 100644 --- a/src/Automatonymous.Tests/Dynamic Modify/Visualizer_Specs.cs +++ b/src/Automatonymous.Tests/Dynamic Modify/Visualizer_Specs.cs @@ -36,7 +36,7 @@ public void Should_show_the_goods() public void Setup() { _machine = AutomatonymousStateMachine - .Build(b => b + .Create(b => b .State("Running", out State Running) .State("Suspended", out State Suspended) .State("Failed", out State Failed) diff --git a/src/Automatonymous/AutomatonymousStateMachine.cs b/src/Automatonymous/AutomatonymousStateMachine.cs index f6d557cc..8e33190b 100644 --- a/src/Automatonymous/AutomatonymousStateMachine.cs +++ b/src/Automatonymous/AutomatonymousStateMachine.cs @@ -680,17 +680,6 @@ protected internal virtual State SubState(string name, State superSta return state; } - //protected internal virtual void SubState(State subState, State superState) - //{ - // if (superState == null) - // throw new ArgumentNullException(nameof(superState)); - - // State superStateInstance = GetState(superState.Name); - - // // If the state was already defined, don't define it again - // superStateInstance.AddSubstate(subState); - //} - /// /// Declares a state on the state machine, and initialized the property /// @@ -1354,7 +1343,7 @@ private StateMachine Modify(Action> m private class BuilderStateMachine : AutomatonymousStateMachine { } - public static AutomatonymousStateMachine Build(Action> modifier) + public static AutomatonymousStateMachine Create(Action> modifier) { var machine = new BuilderStateMachine(); machine.Modify(modifier); diff --git a/src/Automatonymous/State.cs b/src/Automatonymous/State.cs index ecbb6c14..06f1a3c9 100644 --- a/src/Automatonymous/State.cs +++ b/src/Automatonymous/State.cs @@ -85,7 +85,6 @@ public interface State : /// /// void AddSubstate(State subState); - //void SetSuperState(State superState); /// /// True if the specified state is included in the state