public void should_not_relay_state_argument_on_no_relaying_raise(RaiseWay raiseWay) { var entered = false; // --arrange var builder = new Builder <string, int>(OnException); builder.DefineState(Initial).AddTransition(Event1, State1); builder.DefineState(State1) .OnEnter <int>(_ => { }) .AddTransition(Event2, State2); builder.DefineState(State2) .OnEnter(() => entered = true); var target = builder.Build(Initial); target.Raise(Event1, 93); // --act target.Raise(raiseWay, Event2); // --assert entered.Should().BeTrue(); }
public void should_call_exit_and_enter_on_reentering(RaiseWay raiseWay) { const string enter = nameof(enter); const string exit = nameof(exit); var actual = new List <string>(); // --arrange var builder = new Builder <string, int>(OnException); builder.DefineState(Initial).AddTransition(Event1, State1); builder .DefineState(State1) .OnEnter(_ => actual.Add(enter)) .OnExit(() => actual.Add(exit)) .AllowReentrancy(Event1); var target = builder.Build(Initial); target.Raise(raiseWay, Event1); // --act target.Raise(raiseWay, Event1); // --assert actual.Should().BeEquivalentTo(enter, exit, enter); }
public void should_relay_argument_from_parent_of_active_state(RaiseWay raiseWay) { const int expected = 3987; var actual = expected - 87; // --arrange var builder = new Builder <string, int>(OnException); builder.DefineState(Initial).AddTransition(Event1, Child); builder.DefineState(Parent) .OnEnter <int>(_ => { }); builder.DefineState(Child) .AsSubstateOf(Parent) .AddTransition(Event2, State2); builder.DefineState(State2) .OnEnter <int>(value => actual = value); var target = builder.Build(Initial); target.Raise(Event1, expected); // --act target.Relaying <int>().Raise(raiseWay, Event2); // --assert actual.Should().Be(expected); }
public void should_relay_state_argument_to_the_next_state(RaiseWay raiseWay) { const int expected = 3987; var actual = expected - 3; // --arrange var builder = new Builder <string, int>(OnException); builder.DefineState(Initial).AddTransition(Event1, State1); builder.DefineState(State1) .OnEnter <int>(_ => { }) .AddTransition(Event2, State2); builder.DefineState(State2) .OnEnter <int>(value => actual = value); var target = builder.Build(Initial); target.Raise(Event1, expected); // --act target.Relaying <int>().Raise(raiseWay, Event2); // --assert actual.Should().Be(expected); }
public void should_exit_all_parent_states(RaiseWay raiseWay) { var actual = new List <string>(); async Task EnterAsync(IStateMachine <string> stateMachine, string state) { while (stateMachine.InMyState) { await Task.Delay(1); } actual.Add(state + Exit); } // --arrange var builder = new Builder <string, string>(OnException); builder .DefineState(Initial) .AddTransition(Branch1Level3, Branch1Level3); builder .DefineState(Root) .OnEnter(_ => EnterAsync(_, Root)) .OnExit(() => actual.Add(Root)); builder .DefineState(Branch1Level1) .AsSubstateOf(Root) .OnEnter(_ => EnterAsync(_, Branch1Level1)) .OnExit(() => actual.Add(Branch1Level1)); builder .DefineState(Branch1Level2) .AsSubstateOf(Branch1Level1) .OnEnter(_ => EnterAsync(_, Branch1Level2)) .OnExit(() => actual.Add(Branch1Level2)); builder .DefineState(Branch1Level3) .AsSubstateOf(Branch1Level2) .OnEnter(_ => EnterAsync(_, Branch1Level3)) .OnExit(() => actual.Add(Branch1Level3)) .AddTransition(Free1, Free1); builder .DefineState(Free1) .OnEnter(_ => actual.Add(Free1)); var target = builder.Build(Initial); target.Raise(raiseWay, Branch1Level3); // --act target.Raise(raiseWay, Free1); // --assert actual.Should() .Equal(Branch1Level3 + Exit, Branch1Level3, Branch1Level2 + Exit, Branch1Level2, Branch1Level1 + Exit, Branch1Level1, Root + Exit, Root, Free1); }
public void should_not_exit_common_root(RaiseWay raiseWay) { var actual = new List <string>(); async Task RootEnterAsync(IStateMachine <string> stateMachine) { actual.Add(Root); while (stateMachine.InMyState) { await Task.Delay(1); } actual.Add(Root + Exit); } // --arrange var builder = new Builder <string, string>(OnException); builder .DefineState(Initial) .AddTransition(Branch1Level2, Branch1Level2); builder .DefineState(Root) .OnEnter(RootEnterAsync) .OnExit(() => actual.Add(Root)); builder .DefineState(Branch1Level1) .AsSubstateOf(Root) .OnExit(() => actual.Add(Branch1Level1)); builder .DefineState(Branch1Level2) .AsSubstateOf(Branch1Level1) .OnExit(() => actual.Add(Branch1Level2)) .AddTransition(Branch2Level2, Branch2Level2); builder .DefineState(Branch2Level1) .AsSubstateOf(Root) .OnEnter(_ => actual.Add(Branch2Level1)); builder .DefineState(Branch2Level2) .AsSubstateOf(Branch2Level1) .OnEnter(_ => actual.Add(Branch2Level2)); var target = builder.Build(Initial); target.Raise(raiseWay, Branch1Level2); // --act target.Raise(raiseWay, Branch2Level2); // --assert actual.Should().Equal(Root, Branch1Level2, Branch1Level1, Branch2Level1, Branch2Level2); }
public void raise_should_return_false_if_no_transition_found(RaiseWay raiseWay) { // --arrange var builder = new Builder <string, string>(OnException); builder.DefineState(Initial).AddTransition(State1, State1); builder.DefineState(State1).OnEnter(() => Assert.Fail("No transition should be performed")); var target = builder.Build(Initial); // --act var actual = target.Raise(raiseWay, "WrongEvent"); // --assert actual.Should().BeFalse(); }
public void should_not_boxing_passed_value_type_arguments(RaiseWay raiseWay) { var expected1 = new ValueType1(389); var expected2 = new ValueType2(659); ValueType1 actual1 = default; ValueType2 actual2 = default; ITuple <ValueType2, ValueType1> actualTuple = null; // --arrange var builder = new Builder <string, int>(OnException); builder.DefineState(Initial).AddTransition(Event1, State1); builder.DefineState(State1) .OnEnter <ValueType1>(value => { }) .AddTransition(Event2, State2); builder.DefineState(Parent) .OnEnter <ValueType2>(value => actual2 = value); builder.DefineState(Child) .AsSubstateOf(Parent) .OnEnter <ValueType1>(value => actual1 = value); builder.DefineState(State2) .AsSubstateOf(Child) .OnEnter <ITuple <ValueType2, ValueType1> >(value => actualTuple = value); var target = builder.Build(Initial, true); // --act target.Raise(raiseWay, Event1, expected1); // pass to State1 target.Relaying <ValueType1>().Raise(raiseWay, Event2, expected2); // pass everywhere // --assert // actual.Should().Be(expected); -- this method leads boxing actual1.Value.Should().Be(expected1.Value); actual2.Value.Should().Be(expected2.Value); actualTuple !.RelayedArgument.Value.Should().Be(expected1.Value); actualTuple.PassedArgument.Value.Should().Be(expected2.Value); }
public void should_finish_enter_before_call_exit_and_call_next_enter(RaiseWay raiseWay) { var actual = new List <string>(); const string enter1 = nameof(enter1); const string exit1 = nameof(exit1); const string enter2 = nameof(enter2); // --arrange var builder = new Builder <string, int>(OnException); builder.DefineState(Initial).AddTransition(Event1, State1); builder .DefineState(State1) .OnEnter( _ => { Thread.Sleep(299); actual.Add(enter1); }) .OnExit( () => { Thread.Sleep(382); actual.Add(exit1); }) .AddTransition(Event2, State2); builder.DefineState(State2) .OnEnter(_ => actual.Add(enter2)); var target = builder.Build(Initial); target.Raise(raiseWay, Event1); // --act target.Raise(raiseWay, Event2); // --assert actual.Should().BeEquivalentTo(enter1, exit1, enter2); }
public void should_call_enter_on_activation(RaiseWay raiseWay) { var actual = new List <string>(); // --arrange var builder = new Builder <string, int>(OnException); builder.DefineState(Initial) .AddTransition(Event1, State1); builder.DefineState(State1) .OnEnter(_ => actual.Add(State1)); var stateMachine = builder.Build(Initial); // --act stateMachine.Raise(raiseWay, Event1); // --assert actual.Should().BeEquivalentTo(State1); }
public void should_enter_all_parent_states(RaiseWay raiseWay) { var actual = new List <string>(); // --arrange var builder = new Builder <string, string>(OnException); builder .DefineState(Initial) .AddTransition(Branch1Level3, Branch1Level3); builder .DefineState(Root) .OnEnter(_ => actual.Add(Root)); builder .DefineState(Branch1Level1) .AsSubstateOf(Root) .OnEnter(_ => actual.Add(Branch1Level1)); builder .DefineState(Branch1Level2) .AsSubstateOf(Branch1Level1) .OnEnter(_ => actual.Add(Branch1Level2)); builder .DefineState(Branch1Level3) .AsSubstateOf(Branch1Level2) .OnEnter(_ => actual.Add(Branch1Level3)); var target = builder.Build(Initial); // --act target.Raise(raiseWay, Branch1Level3); // --assert actual.Should().Equal(Root, Branch1Level1, Branch1Level2, Branch1Level3); }
public void should_call_action_on_transition_between_exit_and_enter(RaiseWay raiseWay) { const string exit = "Exit"; const string transition = "Transition"; var actual = new List <string>(); // --arrange var builder = new Builder <string, int>(OnException); builder.DefineState(Initial) .OnExit(() => actual.Add(exit)) .AddTransition(Event1, State1, () => actual.Add(transition)); builder.DefineState(State1) .OnEnter(() => actual.Add(State1)); var target = builder.Build(Initial); // --act target.Raise(raiseWay, Event1); // --assert actual.Should().BeEquivalentTo(exit, transition, State1); }
public void async_enter_should_not_block(RaiseWay raiseWay) { // --arrange var entered = new ManualResetEvent(false); async Task AsyncEnter(IStateMachine <int> stateMachine) { entered.Set(); while (stateMachine.InMyState) { await Task.Delay(546); } } var builder = new Builder <string, int>(OnException); builder.DefineState(Initial).AddTransition(Event1, State1); builder .DefineState(State1) .OnEnter(AsyncEnter) .AddTransition(Event2, State2); builder.DefineState(State2); var target = builder.Build(Initial); // --act target.Raise(raiseWay, Event1); // --assert entered.WaitOne(TimeSpan.FromSeconds(4)).Should().BeTrue(); // --cleanup target.Raise(Event2); // exit async method }
public void should_pass_argument_to_enter(RaiseWay raiseWay) { const string expected = "expected"; var actual = expected + "bad"; // --arrange var builder = new Builder <string, int>(OnException); builder .DefineState(Initial) .AddTransition(Event1, State1); builder .DefineState(State1) .OnEnter <string>((sm, param) => actual = param); var target = builder.Build(Initial); // --act target.Raise(raiseWay, Event1, expected); // --assert actual.Should().Be(expected); }
public void should_throw_exception_if_no_argument_specified_for_enter_action_with_argument(RaiseWay raiseWay) { // --arrange var builder = new Builder <string, int>(OnException); builder .DefineState(Initial) .AddTransition(Event1, State1); builder .DefineState(State1) .OnEnter <int>(value => { }); var stateMachine = builder.Build(Initial); // --act Action target = () => stateMachine.Raise(raiseWay, Event1); // --assert target.Should() .ThrowExactly <TransitionException>() .WithMessage($"The enter action of the state '{State1}' is configured as required an argument but no argument was specified."); }
public static bool Raise <TState, TEvent, TA>(this IStateMachine <TState, TEvent> stateMachine, RaiseWay way, TEvent @event, TA arg) => Call(way, () => stateMachine.Raise(@event, arg), () => stateMachine.RaiseAsync(@event, arg).Result);
public void should_pass_null_if_active_state_has_no_argument_and_relayArgumentIsRequired_is_false(RaiseWay raiseWay) { var actual = "bad"; // --arrange var builder = new Builder <string, int>(OnException); builder.DefineState(Initial).AddTransition(Event1, State1); builder.DefineState(State1).OnEnter <string>(value => actual = value); var stateMachine = builder.Build(Initial); // --act const bool relayArgumentIsRequired = false; stateMachine.Relaying <string>(relayArgumentIsRequired).Raise(raiseWay, Event1); // --assert actual.Should().BeNull(); }
public void should_pass_relayed_passed_and_tuple_arguments_depending_on_enter_type(RaiseWay raiseWay) { var expectedRelayed = new MemoryStream(); const string expectedPassed = "stringValue"; IDisposable actualRelayed = null; object actualPassed = null; ITuple <object, IDisposable> actualTuple = null; // --arrange var builder = new Builder <string, int>(OnException); builder.DefineState(Initial).AddTransition(Event1, State1); builder.DefineState(State1) .OnEnter <MemoryStream>(_ => { }) .AddTransition(Event2, Child); builder.DefineState(Root) .OnEnter <object>(value => actualPassed = value); builder.DefineState(Parent) .AsSubstateOf(Root) .OnEnter <ITuple <object, IDisposable> >(value => actualTuple = value); builder.DefineState(Child) .AsSubstateOf(Parent) .OnEnter <IDisposable>(value => actualRelayed = value); var target = builder.Build(Initial, true); target.Raise(raiseWay, Event1, expectedRelayed); // attach MemoryStream to State1 // --act target.Relaying <MemoryStream>().Raise(raiseWay, Event2, expectedPassed); // pass string and relay MemoryStream to Child // --assert actualPassed.Should().Be(expectedPassed); actualRelayed.Should().Be(expectedRelayed); actualTuple !.PassedArgument.Should().Be(expectedPassed); actualTuple.RelayedArgument.Should().Be(expectedRelayed); }
public void should_throw_exception_if_target_state_has_no_argument_and_relaying_is_called(RaiseWay raiseWay) { // --arrange var builder = new Builder <string, int>(OnException); builder.DefineState(Initial).AddTransition(Event1, State1); builder.DefineState(State1) .OnEnter <int>(_ => { }) .AddTransition(Event2, State2); builder.DefineState(State2) .OnEnter(() => { }); var stateMachine = builder.Build(Initial); stateMachine.Raise(Event1, 93); // --act Action target = () => stateMachine.Relaying <int>().Raise(raiseWay, Event2); // --assert target.Should() .ThrowExactly <TransitionException>() .WithMessage( $"Transition from the state '{State1}' by the event '{Event2}' will activate following states [{State2}]. No one of them are defined " + "with the enter action accepting an argument, but argument was passed or relayed"); }
private static bool Call(RaiseWay way, Func <bool> syncAction, Func <bool> asyncAction) => way switch {
public void should_pass_both_passed_and_relayed_arguments_with_variance_conversion(RaiseWay raiseWay) { var expectedRelayedValue = new MemoryStream(); const string expectedPassedValue = "stringValue"; ITuple <object, IDisposable> actual = null; // --arrange var builder = new Builder <string, int>(OnException); builder.DefineState(Initial).AddTransition(Event1, State1); builder.DefineState(State1) .OnEnter <Stream>(_ => { }) .AddTransition(Event2, State2); builder.DefineState(State2) .OnEnter <ITuple <object, IDisposable> >(value => actual = value); var target = builder.Build(Initial); target.Raise(raiseWay, Event1, expectedRelayedValue); // attach an argument to State1 // --act target.Relaying <Stream>().Raise(raiseWay, Event2, expectedPassedValue); // pass and relay arguments to State2 // --assert actual.Should().NotBeNull(); actual !.PassedArgument.Should().Be(expectedPassedValue); actual.RelayedArgument.Should().Be(expectedRelayedValue); }
public void should_throw_exception_if_argument_is_not_assignable_to_enter_action(RaiseWay raiseWay) { // --arrange var builder = new Builder <string, int>(OnException); builder .DefineState(Initial) .AddTransition(Event1, State1); builder .DefineState(State1) .OnEnter <string>((sm, value) => { }); var stateMachine = builder.Build(Initial); // --act Action target = () => stateMachine.Raise(raiseWay, Event1, 983); // --assert target.Should() .ThrowExactly <TransitionException>() .WithMessage($"The state '{State1}' requires argument of type '{typeof(string)}' but no argument of compatible type has passed nor relayed"); }
public void should_pass_argument_if_parent_and_child_argument_are_differ_but_assignable_and_enter_with_no_argument_on_the_pass(RaiseWay raiseWay) { IDisposable actualDisposable = null; Stream actualStream = null; var expected = new MemoryStream(); // --arrange var builder = new Builder <string, string>(OnException); builder.DefineState(Initial).AddTransition(Child, Child); builder.DefineState(Root).OnEnter <IDisposable>(value => actualDisposable = value); builder.DefineState(Parent).AsSubstateOf(Root).OnEnter(sm => { }); builder.DefineState(Child).AsSubstateOf(Parent).OnEnter <Stream>(value => actualStream = value); var target = builder.Build(Initial); // --act target.Raise(raiseWay, Child, expected); // --assert actualStream.Should().BeSameAs(expected); actualDisposable.Should().BeSameAs(expected); }
public void should_pass_argument_if_argument_is_differ_but_assignable_to_enter_action_argument(RaiseWay raiseWay) { IDisposable actual = null; var expected = new MemoryStream(); // --arrange var builder = new Builder <string, int>(OnException); builder.DefineState(Initial).AddTransition(Event1, State1); builder.DefineState(State1).OnEnter <IDisposable>((sm, value) => actual = value); var target = builder.Build(Initial); // --act target.Raise(raiseWay, Event1, expected); // --assert actual.Should().BeSameAs(expected); }
public void should_throw_exception_if_parent_and_child_state_has_not_assignable_arguments_enable_loose_relaying_is_true_and_argument_is_passed(RaiseWay way) { // --arrange var builder = new Builder <string, int>(OnException); builder.DefineState(Initial).AddTransition(Event1, Child); builder.DefineState(Parent) .OnEnter <int>((stateMachine, value) => { }); builder.DefineState(Child) .AsSubstateOf(Parent) .OnEnter <string>(value => { }); // --act var sm = builder.Build(Initial, true); Action target = () => sm.Raise(Event1, "stringArgument"); // --assert target .Should() .Throw <TransitionException>() .WithMessage($"The state '{Parent}' requires argument of type '{typeof(int)}' but no argument of compatible type has passed nor relayed"); }
public void should_throw_exception_if_argument_specified_and_no_argument_required_by_all_activated_states(RaiseWay raiseWay) { // --arrange var builder = new Builder <string, int>(OnException); builder .DefineState(Initial) .AddTransition(Event1, Child); builder .DefineState(Parent) .OnEnter(sm => { }); builder .DefineState(Child) .AsSubstateOf(Parent) .OnEnter(sm => { }); var stateMachine = builder.Build(Initial); // --act Action target = () => stateMachine.Raise(raiseWay, Event1, "argument"); // --assert target.Should() .ThrowExactly <TransitionException>() .WithMessage( $"Transition from the state '{Initial}' by the event '{Event1}' will activate following states [{Parent}->{Child}]. No one of them are defined with the enter " + "action accepting an argument, but argument was passed or relayed"); }