From b1c666af4c0d5b8061a9f048a5cc686481d73a32 Mon Sep 17 00:00:00 2001 From: Arnaud Date: Sun, 3 Mar 2019 08:28:09 +0100 Subject: [PATCH] Added async conditions support for If --- src/Automatonymous.Tests/Condition_Specs.cs | 92 +++++++++++++++++++ src/Automatonymous.Tests/Exception_Specs.cs | 21 +++++ .../Activities/ConditionActivity.cs | 14 +-- .../Activities/ConditionExceptionActivity.cs | 14 +-- .../Binders/CatchExceptionActivityBinder.cs | 26 ++++++ .../Binders/ConditionalActivityBinder.cs | 16 +++- .../ConditionalExceptionActivityBinder.cs | 17 +++- .../Binders/DataEventActivityBinder.cs | 12 +++ .../Binders/EventActivityBinder.cs | 18 ++++ .../Binders/ExceptionActivityBinder.cs | 21 ++++- .../Binders/TriggerEventActivityBinder.cs | 12 +++ .../StateMachineAsyncCondition.cs | 34 +++++++ .../StateMachineAsyncExceptionCondition.cs | 50 ++++++++++ 13 files changed, 326 insertions(+), 21 deletions(-) create mode 100644 src/Automatonymous/StateMachineAsyncCondition.cs create mode 100644 src/Automatonymous/StateMachineAsyncExceptionCondition.cs diff --git a/src/Automatonymous.Tests/Condition_Specs.cs b/src/Automatonymous.Tests/Condition_Specs.cs index f4470ee..6316925 100644 --- a/src/Automatonymous.Tests/Condition_Specs.cs +++ b/src/Automatonymous.Tests/Condition_Specs.cs @@ -104,6 +104,98 @@ class Start } + class StartedExplicitFilter + { + } + } + + [TestFixture] + public class Using_an_async_condition_in_a_state_machine + { + [Test] + public async Task Should_allow_the_condition_to_be_used() + { + await _machine.RaiseEvent(_instance, _machine.Started, new Start {InitializeOnly = true}); + + Assert.That(_instance.CurrentState, Is.EqualTo(_machine.Initialized)); + } + + [Test] + public async Task Should_work() + { + await _machine.RaiseEvent(_instance, _machine.Started, new Start()); + + Assert.That(_instance.CurrentState, Is.EqualTo(_machine.Running)); + } + + [Test] + public async Task Should_allow_if_condition_to_be_evaluated() + { + await _machine.RaiseEvent(_instance, _machine.ExplicitFilterStarted, new StartedExplicitFilter()); + + Assert.That(_instance.CurrentState, Is.Not.EqualTo(_machine.ShouldNotBeHere)); + } + + [SetUp] + public void Specifying_an_event_activity() + { + _instance = new Instance(); + _machine = new InstanceStateMachine(); + } + + Instance _instance; + InstanceStateMachine _machine; + + + class Instance + { + public bool InitializeOnly { get; set; } + public State CurrentState { get; set; } + } + + + class InstanceStateMachine : + AutomatonymousStateMachine + { + public InstanceStateMachine() + { + During(Initial, + When(Started) + .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(Initial, + When(ExplicitFilterStarted, context => true) + .IfAsync(context => Task.FromResult(false), binder => binder + .Then(context => Console.WriteLine("Should not be here!")) + .TransitionTo(ShouldNotBeHere)) + .IfAsync(context => Task.FromResult(true), binder => binder.Then(context => Console.WriteLine("Initializing Only!")))); + + During(Running, + When(Finish) + .Finalize()); + + WhenEnter(Initialized, x => x.IfAsync(context => Task.FromResult(!context.Instance.InitializeOnly), b => b.TransitionTo(Running))); + } + + public State Running { get; private set; } + public State Initialized { get; private set; } + public State ShouldNotBeHere { get; private set; } + + public Event Started { get; private set; } + public Event ExplicitFilterStarted { get; private set; } + + public Event Finish { get; private set; } + } + + + class Start + { + public bool InitializeOnly { get; set; } + } + + class StartedExplicitFilter { } diff --git a/src/Automatonymous.Tests/Exception_Specs.cs b/src/Automatonymous.Tests/Exception_Specs.cs index a5d766c..00ffc45 100644 --- a/src/Automatonymous.Tests/Exception_Specs.cs +++ b/src/Automatonymous.Tests/Exception_Specs.cs @@ -13,6 +13,7 @@ namespace Automatonymous.Tests { using System; + using System.Threading.Tasks; using GreenPipes; using GreenPipes.Introspection; using NUnit.Framework; @@ -50,6 +51,7 @@ public Instance() public bool ShouldNotBeCalled { get; set; } public bool CalledThenClause { get; set; } + public bool CalledSecondThenClause { get; set; } } @@ -67,6 +69,9 @@ public InstanceStateMachine() .If(context => true, b => b .Then(context => context.Instance.CalledThenClause = true) ) + .IfAsync(context => Task.FromResult(true), b => b + .Then(context => context.Instance.CalledSecondThenClause = true) + ) .Then(context => { context.Instance.ExceptionMessage = context.Exception.Message; @@ -133,6 +138,12 @@ public void Should_have_called_the_first_if_block() { Assert.IsTrue(_instance.CalledThenClause); } + + [Test] + public void Should_have_called_the_async_if_block() + { + Assert.IsTrue(_instance.CalledSecondThenClause); + } } [TestFixture] @@ -304,6 +315,7 @@ public Instance() public State CurrentState { get; set; } public bool CalledThenClause { get; set; } + public bool CalledSecondThenClause { get; set; } } @@ -328,6 +340,9 @@ public InstanceStateMachine() .If(context => true, b => b .Then(context => context.Instance.CalledThenClause = true) ) + .IfAsync(context => Task.FromResult(true), b => b + .Then(context => context.Instance.CalledSecondThenClause = true) + ) .Then(context => { context.Instance.ExceptionMessage = context.Exception.Message; @@ -377,5 +392,11 @@ public void Should_have_called_the_first_if_block() { Assert.IsTrue(_instance.CalledThenClause); } + + [Test] + public void Should_have_called_the_async_if_block() + { + Assert.IsTrue(_instance.CalledSecondThenClause); + } } } \ No newline at end of file diff --git a/src/Automatonymous/Activities/ConditionActivity.cs b/src/Automatonymous/Activities/ConditionActivity.cs index 94f6b3a..4edf61c 100644 --- a/src/Automatonymous/Activities/ConditionActivity.cs +++ b/src/Automatonymous/Activities/ConditionActivity.cs @@ -21,9 +21,9 @@ public class ConditionActivity : where TInstance : class { readonly Behavior _behavior; - readonly StateMachineCondition _condition; + readonly StateMachineAsyncCondition _condition; - public ConditionActivity(StateMachineCondition condition, Behavior behavior) + public ConditionActivity(StateMachineAsyncCondition condition, Behavior behavior) { _condition = condition; _behavior = behavior; @@ -43,7 +43,7 @@ void Visitable.Accept(StateMachineVisitor visitor) async Task Activity.Execute(BehaviorContext context, Behavior next) { - if (_condition(context)) + if (await _condition(context).ConfigureAwait(false)) await _behavior.Execute(context).ConfigureAwait(false); await next.Execute(context).ConfigureAwait(false); @@ -51,7 +51,7 @@ async Task Activity.Execute(BehaviorContext context, Behav async Task Activity.Execute(BehaviorContext context, Behavior next) { - if (_condition(context)) + if (await _condition(context).ConfigureAwait(false)) await _behavior.Execute(context).ConfigureAwait(false); await next.Execute(context).ConfigureAwait(false); @@ -75,9 +75,9 @@ public class ConditionActivity : where TInstance : class { readonly Behavior _behavior; - readonly StateMachineCondition _condition; + readonly StateMachineAsyncCondition _condition; - public ConditionActivity(StateMachineCondition condition, Behavior behavior) + public ConditionActivity(StateMachineAsyncCondition condition, Behavior behavior) { _condition = condition; _behavior = behavior; @@ -105,7 +105,7 @@ async Task Activity.Execute(BehaviorContext context, var behaviorContext = context as BehaviorContext; if (behaviorContext != null) { - if (_condition(behaviorContext)) + if (await _condition(behaviorContext).ConfigureAwait(false)) await _behavior.Execute(behaviorContext).ConfigureAwait(false); } diff --git a/src/Automatonymous/Activities/ConditionExceptionActivity.cs b/src/Automatonymous/Activities/ConditionExceptionActivity.cs index 019cd65..92446f1 100644 --- a/src/Automatonymous/Activities/ConditionExceptionActivity.cs +++ b/src/Automatonymous/Activities/ConditionExceptionActivity.cs @@ -23,9 +23,9 @@ public class ConditionExceptionActivity : where TConditionException : Exception { readonly Behavior _behavior; - readonly StateMachineExceptionCondition _condition; + readonly StateMachineAsyncExceptionCondition _condition; - public ConditionExceptionActivity(StateMachineExceptionCondition condition, Behavior behavior) + public ConditionExceptionActivity(StateMachineAsyncExceptionCondition condition, Behavior behavior) { _condition = condition; _behavior = behavior; @@ -58,7 +58,7 @@ async Task Activity.Faulted(BehaviorExceptionContext; if (behaviorContext != null) { - if (_condition(behaviorContext)) + if (await _condition(behaviorContext).ConfigureAwait(false)) { await _behavior.Faulted(context).ConfigureAwait(false); } @@ -73,7 +73,7 @@ async Task Activity.Faulted(BehaviorExceptionContext; if (behaviorContext != null) { - if (_condition(behaviorContext)) + if (await _condition(behaviorContext).ConfigureAwait(false)) { await _behavior.Faulted(context).ConfigureAwait(false); } @@ -90,9 +90,9 @@ public class ConditionExceptionActivity : where TConditionException : Exception { readonly Behavior _behavior; - readonly StateMachineExceptionCondition _condition; + readonly StateMachineAsyncExceptionCondition _condition; - public ConditionExceptionActivity(StateMachineExceptionCondition condition, Behavior behavior) + public ConditionExceptionActivity(StateMachineAsyncExceptionCondition condition, Behavior behavior) { _condition = condition; _behavior = behavior; @@ -131,7 +131,7 @@ async Task Activity.Faulted(BehaviorExceptionContext; if (behaviorContext != null) { - if (_condition(behaviorContext)) + if (await _condition(behaviorContext).ConfigureAwait(false)) { await _behavior.Faulted(context).ConfigureAwait(false); } diff --git a/src/Automatonymous/Binders/CatchExceptionActivityBinder.cs b/src/Automatonymous/Binders/CatchExceptionActivityBinder.cs index 5b3e0e6..bacb7ad 100644 --- a/src/Automatonymous/Binders/CatchExceptionActivityBinder.cs +++ b/src/Automatonymous/Binders/CatchExceptionActivityBinder.cs @@ -86,6 +86,19 @@ public ExceptionActivityBinder If( return new CatchExceptionActivityBinder(_machine, _event, _activities, conditionBinder); } + + public ExceptionActivityBinder IfAsync( + StateMachineAsyncExceptionCondition condition, + Func, ExceptionActivityBinder> activityCallback) + { + ExceptionActivityBinder binder = new CatchExceptionActivityBinder(_machine, _event); + + binder = activityCallback(binder); + + var conditionBinder = new ConditionalExceptionActivityBinder(_event, condition, binder); + + return new CatchExceptionActivityBinder(_machine, _event, _activities, conditionBinder); + } } @@ -167,5 +180,18 @@ public ExceptionActivityBinder If(StateMachineExce return new CatchExceptionActivityBinder(_machine, _event, _activities, conditionBinder); } + + public ExceptionActivityBinder IfAsync(StateMachineAsyncExceptionCondition condition, + Func, ExceptionActivityBinder> activityCallback) + { + ExceptionActivityBinder binder = + new CatchExceptionActivityBinder(_machine, _event); + + binder = activityCallback(binder); + + var conditionBinder = new ConditionalExceptionActivityBinder(_event, condition, binder); + + return new CatchExceptionActivityBinder(_machine, _event, _activities, conditionBinder); + } } } \ No newline at end of file diff --git a/src/Automatonymous/Binders/ConditionalActivityBinder.cs b/src/Automatonymous/Binders/ConditionalActivityBinder.cs index 29e458b..7608ce6 100644 --- a/src/Automatonymous/Binders/ConditionalActivityBinder.cs +++ b/src/Automatonymous/Binders/ConditionalActivityBinder.cs @@ -12,6 +12,7 @@ // specific language governing permissions and limitations under the License. namespace Automatonymous.Binders { + using System.Threading.Tasks; using Activities; using Behaviors; @@ -21,10 +22,15 @@ public class ConditionalActivityBinder : where TInstance : class { readonly EventActivities _activities; - readonly StateMachineCondition _condition; + readonly StateMachineAsyncCondition _condition; readonly Event _event; public ConditionalActivityBinder(Event @event, StateMachineCondition condition, EventActivities activities) + : this(@event, context => Task.FromResult(condition(context)), activities) + { + } + + public ConditionalActivityBinder(Event @event, StateMachineAsyncCondition condition, EventActivities activities) { _activities = activities; _condition = condition; @@ -72,11 +78,17 @@ public class ConditionalActivityBinder : where TInstance : class { readonly EventActivities _activities; - readonly StateMachineCondition _condition; + readonly StateMachineAsyncCondition _condition; readonly Event _event; public ConditionalActivityBinder(Event @event, StateMachineCondition condition, EventActivities activities) + : this(@event, context => Task.FromResult(condition(context)), activities) + { + } + + public ConditionalActivityBinder(Event @event, StateMachineAsyncCondition condition, + EventActivities activities) { _activities = activities; _condition = condition; diff --git a/src/Automatonymous/Binders/ConditionalExceptionActivityBinder.cs b/src/Automatonymous/Binders/ConditionalExceptionActivityBinder.cs index 3df49fc..9a82b35 100644 --- a/src/Automatonymous/Binders/ConditionalExceptionActivityBinder.cs +++ b/src/Automatonymous/Binders/ConditionalExceptionActivityBinder.cs @@ -13,6 +13,7 @@ namespace Automatonymous.Binders { using System; + using System.Threading.Tasks; using Activities; using Behaviors; @@ -23,10 +24,15 @@ public class ConditionalExceptionActivityBinder : where TException : Exception { readonly EventActivities _activities; - readonly StateMachineExceptionCondition _condition; + readonly StateMachineAsyncExceptionCondition _condition; readonly Event _event; public ConditionalExceptionActivityBinder(Event @event, StateMachineExceptionCondition condition, EventActivities activities) + :this(@event, context => Task.FromResult(condition(context)), activities) + { + } + + public ConditionalExceptionActivityBinder(Event @event, StateMachineAsyncExceptionCondition condition, EventActivities activities) { _activities = activities; _condition = condition; @@ -75,10 +81,15 @@ public class ConditionalExceptionActivityBinder : where TException : Exception { readonly EventActivities _activities; - readonly StateMachineExceptionCondition _condition; + readonly StateMachineAsyncExceptionCondition _condition; readonly Event _event; - public ConditionalExceptionActivityBinder(Event @event, StateMachineExceptionCondition condition, + public ConditionalExceptionActivityBinder(Event @event, StateMachineExceptionCondition condition, EventActivities activities) + : this(@event, context => Task.FromResult(condition(context)), activities) + { + } + + public ConditionalExceptionActivityBinder(Event @event, StateMachineAsyncExceptionCondition condition, EventActivities activities) { _activities = activities; diff --git a/src/Automatonymous/Binders/DataEventActivityBinder.cs b/src/Automatonymous/Binders/DataEventActivityBinder.cs index f80cebb..fa299b9 100644 --- a/src/Automatonymous/Binders/DataEventActivityBinder.cs +++ b/src/Automatonymous/Binders/DataEventActivityBinder.cs @@ -94,6 +94,18 @@ EventActivityBinder EventActivityBinder.If(S return new DataEventActivityBinder(_machine, _event, _filter, _activities, conditionBinder); } + EventActivityBinder EventActivityBinder.IfAsync(StateMachineAsyncCondition condition, + Func, EventActivityBinder> activityCallback) + { + EventActivityBinder binder = new DataEventActivityBinder(_machine, _event); + + binder = activityCallback(binder); + + var conditionBinder = new ConditionalActivityBinder(_event, condition, binder); + + return new DataEventActivityBinder(_machine, _event, _filter, _activities, conditionBinder); + } + StateMachine EventActivityBinder.StateMachine => _machine; public IEnumerable> GetStateActivityBinders() diff --git a/src/Automatonymous/Binders/EventActivityBinder.cs b/src/Automatonymous/Binders/EventActivityBinder.cs index 49902c0..56c3f90 100644 --- a/src/Automatonymous/Binders/EventActivityBinder.cs +++ b/src/Automatonymous/Binders/EventActivityBinder.cs @@ -43,6 +43,15 @@ EventActivityBinder Catch( /// EventActivityBinder If(StateMachineCondition condition, Func, EventActivityBinder> activityCallback); + + /// + /// Create a conditional branch of activities for processing + /// + /// + /// + /// + EventActivityBinder IfAsync(StateMachineAsyncCondition condition, + Func, EventActivityBinder> activityCallback); } @@ -76,5 +85,14 @@ EventActivityBinder Catch( /// EventActivityBinder If(StateMachineCondition condition, Func, EventActivityBinder> activityCallback); + + /// + /// Create a conditional branch of activities for processing + /// + /// + /// + /// + EventActivityBinder IfAsync(StateMachineAsyncCondition condition, + Func, EventActivityBinder> activityCallback); } } \ No newline at end of file diff --git a/src/Automatonymous/Binders/ExceptionActivityBinder.cs b/src/Automatonymous/Binders/ExceptionActivityBinder.cs index a0b7155..aaafe35 100644 --- a/src/Automatonymous/Binders/ExceptionActivityBinder.cs +++ b/src/Automatonymous/Binders/ExceptionActivityBinder.cs @@ -44,6 +44,15 @@ ExceptionActivityBinder Catch( /// ExceptionActivityBinder If(StateMachineExceptionCondition condition, Func, ExceptionActivityBinder> activityCallback); + + /// + /// Create a conditional branch of activities for processing + /// + /// + /// + /// + ExceptionActivityBinder IfAsync(StateMachineAsyncExceptionCondition condition, + Func, ExceptionActivityBinder> activityCallback); } @@ -77,7 +86,15 @@ ExceptionActivityBinder Catch( /// /// ExceptionActivityBinder If(StateMachineExceptionCondition condition, - Func, ExceptionActivityBinder> - activityCallback); + Func, ExceptionActivityBinder> activityCallback); + + /// + /// Create a conditional branch of activities for processing + /// + /// + /// + /// + ExceptionActivityBinder IfAsync(StateMachineAsyncExceptionCondition condition, + Func, ExceptionActivityBinder> activityCallback); } } \ No newline at end of file diff --git a/src/Automatonymous/Binders/TriggerEventActivityBinder.cs b/src/Automatonymous/Binders/TriggerEventActivityBinder.cs index b3d7edb..946f984 100644 --- a/src/Automatonymous/Binders/TriggerEventActivityBinder.cs +++ b/src/Automatonymous/Binders/TriggerEventActivityBinder.cs @@ -88,6 +88,18 @@ EventActivityBinder EventActivityBinder.If(StateMachineCon return new TriggerEventActivityBinder(_machine, _event, _filter, _activities, conditionBinder); } + EventActivityBinder EventActivityBinder.IfAsync(StateMachineAsyncCondition condition, + Func, EventActivityBinder> activityCallback) + { + EventActivityBinder binder = new TriggerEventActivityBinder(_machine, _event); + + binder = activityCallback(binder); + + var conditionBinder = new ConditionalActivityBinder(_event, condition, binder); + + return new TriggerEventActivityBinder(_machine, _event, _filter, _activities, conditionBinder); + } + StateMachine EventActivityBinder.StateMachine => _machine; public IEnumerable> GetStateActivityBinders() diff --git a/src/Automatonymous/StateMachineAsyncCondition.cs b/src/Automatonymous/StateMachineAsyncCondition.cs new file mode 100644 index 0000000..4fdb6db --- /dev/null +++ b/src/Automatonymous/StateMachineAsyncCondition.cs @@ -0,0 +1,34 @@ +// Copyright 2011-2019 Chris Patterson, Dru Sellers +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +namespace Automatonymous +{ + using System.Threading.Tasks; + + + /// + /// Filters activities based on the async conditional statement + /// + /// + /// + /// + public delegate Task StateMachineAsyncCondition(BehaviorContext context); + + + /// + /// Filters activities based on the async conditional statement + /// + /// + /// + /// + public delegate Task StateMachineAsyncCondition(BehaviorContext context); +} diff --git a/src/Automatonymous/StateMachineAsyncExceptionCondition.cs b/src/Automatonymous/StateMachineAsyncExceptionCondition.cs new file mode 100644 index 0000000..fb4bfe1 --- /dev/null +++ b/src/Automatonymous/StateMachineAsyncExceptionCondition.cs @@ -0,0 +1,50 @@ +// Copyright 2011-2019 Chris Patterson, Dru Sellers +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// Copyright 2011-2019 Chris Patterson, Dru Sellers +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +namespace Automatonymous +{ + using System; + using System.Threading.Tasks; + + + /// + /// Filters activities based on the conditional statement + /// + /// + /// + /// + /// + public delegate Task StateMachineAsyncExceptionCondition(BehaviorExceptionContext context) + where TException : Exception; + + + /// + /// Filters activities based on the conditional statement + /// + /// + /// + /// + public delegate Task StateMachineAsyncExceptionCondition(BehaviorExceptionContext context) + where TException : Exception; +} \ No newline at end of file