From 911d3ffb78444223cded660f46712763d5701e4d Mon Sep 17 00:00:00 2001 From: snikolayev Date: Sun, 3 Jun 2018 16:55:10 -0400 Subject: [PATCH] Action triggers #161 - Add a notion of action triggers, that indicate how the activation was triggered (create, update, removal) - Ability to associate expressions with individual action triggers. Including expressions on action removals - Changed how linked facts are managed, to leverate action triggers - Tests --- .../Dsl/IRightHandSideExpression.cs | 32 +- .../Expressions/BuilderExtensions.cs | 4 +- .../Expressions/RightHandSideExpression.cs | 95 ++-- src/NRules/NRules.RuleModel/ActionElement.cs | 38 +- .../Builders/ActionGroupBuilder.cs | 23 +- src/NRules/NRules.RuleModel/IMatch.cs | 31 ++ src/NRules/NRules/ActionExecutor.cs | 5 + src/NRules/NRules/Activation.cs | 33 +- src/NRules/NRules/ActivationQueue.cs | 24 +- src/NRules/NRules/Agenda.cs | 60 +-- src/NRules/NRules/CompiledRule.cs | 7 + src/NRules/NRules/RuleAction.cs | 7 +- src/NRules/NRules/Session.cs | 3 +- .../NRules/Utilities/ExpressionCompiler.cs | 2 +- .../NRules/Utilities/TriggerExtensions.cs | 12 + .../ActionTriggerNonRepeatableTest.cs | 415 ++++++++++++++++++ .../ActionTriggerTest.cs | 413 +++++++++++++++++ .../ForwardChainingLinkedTest.cs | 1 + src/NRules/Tests/NRules.Tests/AgendaTest.cs | 48 +- src/NRules/Tests/NRules.Tests/SessionTest.cs | 6 +- 20 files changed, 1141 insertions(+), 118 deletions(-) create mode 100644 src/NRules/NRules/Utilities/TriggerExtensions.cs create mode 100644 src/NRules/Tests/NRules.IntegrationTests/ActionTriggerNonRepeatableTest.cs create mode 100644 src/NRules/Tests/NRules.IntegrationTests/ActionTriggerTest.cs diff --git a/src/NRules/NRules.Fluent/Dsl/IRightHandSideExpression.cs b/src/NRules/NRules.Fluent/Dsl/IRightHandSideExpression.cs index 8c519387..c7426429 100644 --- a/src/NRules/NRules.Fluent/Dsl/IRightHandSideExpression.cs +++ b/src/NRules/NRules.Fluent/Dsl/IRightHandSideExpression.cs @@ -10,12 +10,29 @@ namespace NRules.Fluent.Dsl public interface IRightHandSideExpression { /// - /// Defines rule's action that engine executes when the rule fires. + /// Defines rule's action that engine executes for a given trigger. + /// + /// Action expression. + /// Events that should trigger this action. + /// Right hand side expression builder. + IRightHandSideExpression Action(Expression> action, ActionTrigger actionTrigger); + + /// + /// Defines rule's action that engine executes when the rule fires + /// due to the initial rule match or due to an update. /// /// Action expression. /// Right hand side expression builder. IRightHandSideExpression Do(Expression> action); + /// + /// Defines rule's action that engine executes when the rule fires + /// due to the match removal (provided the rule previously fired on the match). + /// + /// Action expression. + /// Right hand side expression builder. + IRightHandSideExpression Undo(Expression> action); + /// /// Defines rule's action that yields a linked fact when the rule fires. /// If the rule is fired due to an update, the linked fact is also updated with the new yielded value. @@ -34,5 +51,18 @@ public interface IRightHandSideExpression /// Action expression that yields an updated linked fact if the linked fact already exists. /// Right hand side expression builder. IRightHandSideExpression Yield(Expression> yieldInsert, Expression> yieldUpdate); + + /// + /// Defines rule's action that yields a linked fact when the rule fires. + /// If the rule is fired due to an update, the update expression is evaluated to produce an updated linked fact. + /// If the rule's conditions are no longer true and the rule's activation is removed, the remove expression is evaluated and the linked fact is retracted. + /// + /// Type of fact to yield. + /// Action expression that yields a new linked fact if the linked fact does not yet exist. + /// Action expression that yields an updated linked fact if the linked fact already exists. + /// Action expression that is evaluated before the linked fact is retracted. + /// Right hand side expression builder. + IRightHandSideExpression Yield(Expression> yieldInsert, Expression> yieldUpdate, + Expression> yieldRemove); } } \ No newline at end of file diff --git a/src/NRules/NRules.Fluent/Expressions/BuilderExtensions.cs b/src/NRules/NRules.Fluent/Expressions/BuilderExtensions.cs index ed3e2bd2..6ecfb6e6 100644 --- a/src/NRules/NRules.Fluent/Expressions/BuilderExtensions.cs +++ b/src/NRules/NRules.Fluent/Expressions/BuilderExtensions.cs @@ -35,11 +35,11 @@ public static void DslBindingExpression(this BindingBuilder builder, IEnumerable builder.BindingExpression(rewrittenExpression); } - public static void DslAction(this ActionGroupBuilder builder, IEnumerable declarations, Expression> action) + public static void DslAction(this ActionGroupBuilder builder, IEnumerable declarations, Expression> action, ActionTrigger actionTrigger) { var rewriter = new ExpressionRewriter(declarations); var rewrittenAction = rewriter.Rewrite(action); - builder.Action(rewrittenAction); + builder.Action(rewrittenAction, actionTrigger); } public static LambdaExpression DslPatternExpression(this PatternBuilder builder, IEnumerable declarations, LambdaExpression expression) diff --git a/src/NRules/NRules.Fluent/Expressions/RightHandSideExpression.cs b/src/NRules/NRules.Fluent/Expressions/RightHandSideExpression.cs index 5f5729ee..6c1894c2 100644 --- a/src/NRules/NRules.Fluent/Expressions/RightHandSideExpression.cs +++ b/src/NRules/NRules.Fluent/Expressions/RightHandSideExpression.cs @@ -17,71 +17,90 @@ public RightHandSideExpression(RuleBuilder builder) _builder = builder; } - public IRightHandSideExpression Do(Expression> action) + public IRightHandSideExpression Action(Expression> action, ActionTrigger actionTrigger) { var rightHandSide = _builder.RightHandSide(); - rightHandSide.DslAction(rightHandSide.Declarations, action); + rightHandSide.DslAction(rightHandSide.Declarations, action, actionTrigger); return this; } + public IRightHandSideExpression Do(Expression> action) + { + return Action(action, ActionTrigger.Activated | ActionTrigger.Reactivated); + } + + public IRightHandSideExpression Undo(Expression> action) + { + return Action(action, ActionTrigger.Deactivated); + } + public IRightHandSideExpression Yield(Expression> yield) { - _linkedCount++; var context = yield.Parameters[0]; var linkedFact = Expression.Parameter(typeof(TFact)); + var yieldUpdate = Expression.Lambda>(yield.Body, context, linkedFact); + return Yield(yield, yieldUpdate); + } + + public IRightHandSideExpression Yield(Expression> yieldInsert, Expression> yieldUpdate) + { + var yieldRemove = Expression.Lambda>(Expression.Empty(), yieldUpdate.Parameters); + return Yield(yieldInsert, yieldUpdate, yieldRemove); + } + + public IRightHandSideExpression Yield(Expression> yieldInsert, Expression> yieldUpdate, Expression> yieldRemove) + { + _linkedCount++; + var linkedFact = Expression.Parameter(typeof(TFact), "$temp"); var linkedKey = Expression.Constant($"$linkedkey{_linkedCount}$"); - var action = Expression.Lambda>( + var insertContext = yieldInsert.Parameters[0]; + var insertAction = Expression.Lambda>( + Expression.Block( + new[] {linkedFact}, + Expression.Assign(linkedFact, Expression.Invoke(yieldInsert, insertContext)), + Expression.Call(insertContext, + typeof(IContext).GetTypeInfo().GetDeclaredMethod(nameof(IContext.InsertLinked)), + linkedKey, linkedFact)), + insertContext); + + var updateContext = yieldUpdate.Parameters[0]; + var updateAction = Expression.Lambda>( Expression.Block( new[] {linkedFact}, Expression.Assign(linkedFact, Expression.Convert( - Expression.Call(context, + Expression.Call(updateContext, typeof(IContext).GetTypeInfo().GetDeclaredMethod(nameof(IContext.GetLinked)), linkedKey), typeof(TFact))), - Expression.IfThenElse( - Expression.Equal(linkedFact, Expression.Constant(null)), - Expression.Call(context, - typeof(IContext).GetTypeInfo().GetDeclaredMethod(nameof(IContext.InsertLinked)), linkedKey, - yield.Body), - Expression.Call(context, - typeof(IContext).GetTypeInfo().GetDeclaredMethod(nameof(IContext.UpdateLinked)), linkedKey, - yield.Body)) - ), - context); - return Do(action); - } + Expression.Assign(linkedFact, Expression.Invoke(yieldUpdate, updateContext, linkedFact)), + Expression.Call(updateContext, + typeof(IContext).GetTypeInfo().GetDeclaredMethod(nameof(IContext.UpdateLinked)), + linkedKey, linkedFact)), + updateContext); - public IRightHandSideExpression Yield(Expression> yieldInsert, Expression> yieldUpdate) - { - _linkedCount++; - var context = yieldInsert.Parameters[0]; - var linkedFact = Expression.Parameter(typeof(TFact)); - var linkedKey = Expression.Constant($"$linkedkey{_linkedCount}$"); - - var action = Expression.Lambda>( + var removeContext = yieldRemove.Parameters[0]; + var removeAction = Expression.Lambda>( Expression.Block( new[] {linkedFact}, Expression.Assign(linkedFact, Expression.Convert( - Expression.Call(context, + Expression.Call(removeContext, typeof(IContext).GetTypeInfo().GetDeclaredMethod(nameof(IContext.GetLinked)), linkedKey), typeof(TFact))), - Expression.IfThenElse( - Expression.Equal(linkedFact, Expression.Constant(null)), - Expression.Call(context, - typeof(IContext).GetTypeInfo().GetDeclaredMethod(nameof(IContext.InsertLinked)), linkedKey, - yieldInsert.Body), - Expression.Block( - Expression.Assign(linkedFact, Expression.Invoke(yieldUpdate, context, linkedFact)), - Expression.Call(context, - typeof(IContext).GetTypeInfo().GetDeclaredMethod(nameof(IContext.UpdateLinked)), - linkedKey, linkedFact))) + Expression.Invoke(yieldRemove, removeContext, linkedFact), + Expression.Call(removeContext, + typeof(IContext).GetTypeInfo().GetDeclaredMethod(nameof(IContext.RetractLinked)), + linkedKey, linkedFact) ), - context); - return Do(action); + removeContext); + + var rhs = Action(insertAction, ActionTrigger.Activated) + .Action(updateAction, ActionTrigger.Reactivated) + .Action(removeAction, ActionTrigger.Deactivated); + return rhs; } } } \ No newline at end of file diff --git a/src/NRules/NRules.RuleModel/ActionElement.cs b/src/NRules/NRules.RuleModel/ActionElement.cs index 7a618221..41957c46 100644 --- a/src/NRules/NRules.RuleModel/ActionElement.cs +++ b/src/NRules/NRules.RuleModel/ActionElement.cs @@ -1,20 +1,54 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; namespace NRules.RuleModel { + /// + /// Activation events that trigger the actions. + /// + [Flags] + public enum ActionTrigger + { + /// + /// Action is not triggered. + /// + None = 0x0, + + /// + /// Action is triggered when activation is created. + /// + Activated = 0x1, + + /// + /// Action is triggered when activation is updated. + /// + Reactivated = 0x2, + + /// + /// Action is triggered when activation is removed. + /// + Deactivated = 0x4, + } + /// /// Action executed by the engine when the rule fires. /// [DebuggerDisplay("{Expression.ToString()}")] public class ActionElement : ExpressionElement { - internal ActionElement(IEnumerable declarations, IEnumerable references, LambdaExpression expression) + internal ActionElement(IEnumerable declarations, IEnumerable references, LambdaExpression expression, ActionTrigger actionTrigger) : base(declarations, references, expression) { + ActionTrigger = actionTrigger; } + /// + /// Activation events that trigger this action. + /// + public ActionTrigger ActionTrigger { get; } + internal override void Accept(TContext context, RuleElementVisitor visitor) { visitor.VisitAction(context, this); diff --git a/src/NRules/NRules.RuleModel/Builders/ActionGroupBuilder.cs b/src/NRules/NRules.RuleModel/Builders/ActionGroupBuilder.cs index f3dfc509..20979649 100644 --- a/src/NRules/NRules.RuleModel/Builders/ActionGroupBuilder.cs +++ b/src/NRules/NRules.RuleModel/Builders/ActionGroupBuilder.cs @@ -12,17 +12,32 @@ public class ActionGroupBuilder : RuleRightElementBuilder, IBuilder _actions = new List(); + private const ActionTrigger DefaultTrigger = ActionTrigger.Activated | ActionTrigger.Reactivated; + internal ActionGroupBuilder(SymbolTable scope) : base(scope) { } /// /// Adds a rule action to the group. + /// The action will be executed on new and updated rule activations. /// /// Rule action expression. /// The first parameter of the action expression must be . /// Names and types of the rest of the expression parameters must match the names and types defined in the pattern declarations. public void Action(LambdaExpression expression) + { + Action(expression, DefaultTrigger); + } + + /// + /// Adds a rule action to the group. + /// + /// Rule action expression. + /// The first parameter of the action expression must be . + /// Names and types of the rest of the expression parameters must match the names and types defined in the pattern declarations. + /// Activation events that trigger the action. + public void Action(LambdaExpression expression, ActionTrigger actionTrigger) { if (expression.Parameters.Count == 0 || expression.Parameters.First().Type != typeof(IContext)) @@ -30,9 +45,15 @@ public void Action(LambdaExpression expression) throw new ArgumentException( $"Action expression must have {typeof(IContext)} as its first parameter"); } + + if (actionTrigger == ActionTrigger.None) + { + throw new ArgumentException("Action trigger not specified"); + } + IEnumerable parameters = expression.Parameters.Skip(1); IEnumerable references = parameters.Select(p => Scope.Lookup(p.Name, p.Type)); - var actionElement = new ActionElement(Scope.VisibleDeclarations, references, expression); + var actionElement = new ActionElement(Scope.VisibleDeclarations, references, expression, actionTrigger); _actions.Add(actionElement); } diff --git a/src/NRules/NRules.RuleModel/IMatch.cs b/src/NRules/NRules.RuleModel/IMatch.cs index b0d9dd61..437d53c4 100644 --- a/src/NRules/NRules.RuleModel/IMatch.cs +++ b/src/NRules/NRules.RuleModel/IMatch.cs @@ -2,6 +2,32 @@ namespace NRules.RuleModel { + /// + /// Event that triggered the match. + /// + public enum MatchTrigger + { + /// + /// Match is not active. + /// + None = 0, + + /// + /// Match is triggered due to activation creation. + /// + Created = 1, + + /// + /// Match is triggered due to activation update. + /// + Updated = 2, + + /// + /// Match is triggered due to activation removal. + /// + Removed = 4, + } + /// /// Represents a match of all rule's conditions. /// @@ -16,5 +42,10 @@ public interface IMatch /// Facts matched by the rule. /// IEnumerable Facts { get; } + + /// + /// Event that triggered the match. + /// + MatchTrigger Trigger { get; } } } \ No newline at end of file diff --git a/src/NRules/NRules/ActionExecutor.cs b/src/NRules/NRules/ActionExecutor.cs index 5cd60501..fa350769 100644 --- a/src/NRules/NRules/ActionExecutor.cs +++ b/src/NRules/NRules/ActionExecutor.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using NRules.RuleModel; +using NRules.Utilities; namespace NRules { @@ -44,9 +46,12 @@ public void Execute(IExecutionContext executionContext, IActionContext actionCon private IEnumerable CreateInvocations(IExecutionContext executionContext, IActionContext actionContext) { ICompiledRule compiledRule = actionContext.CompiledRule; + MatchTrigger trigger = actionContext.Activation.Trigger; var invocations = new List(); foreach (IRuleAction action in compiledRule.Actions) { + if (!trigger.Matches(action.Trigger)) continue; + var args = action.GetArguments(executionContext, actionContext); var invocation = new ActionInvocation(executionContext, actionContext, action, args); invocations.Add(invocation); diff --git a/src/NRules/NRules/Activation.cs b/src/NRules/NRules/Activation.cs index 37349d9e..a0bd15a0 100644 --- a/src/NRules/NRules/Activation.cs +++ b/src/NRules/NRules/Activation.cs @@ -37,16 +37,43 @@ internal Activation(ICompiledRule compiledRule, Tuple tuple, IndexMap factMap) /// public IEnumerable Facts => GetMatchedFacts(); + /// + /// Event that triggered the match. + /// + public MatchTrigger Trigger { get; private set; } + internal ICompiledRule CompiledRule { get; } internal Tuple Tuple { get; } internal IndexMap FactMap { get; } - internal bool IsActive {get; set; } - internal bool IsRefracted {get; set; } + internal bool IsEnqueued { get; set; } + internal bool HasFired { get; set; } + + internal void Insert() + { + Trigger = MatchTrigger.Created; + } + + internal void Update() + { + Trigger = HasFired ? MatchTrigger.Updated : MatchTrigger.Created; + } + + internal void Remove() + { + Trigger = HasFired ? MatchTrigger.Removed : MatchTrigger.None; + } + + internal void Clear() + { + HasFired = false; + Trigger = MatchTrigger.None; + } - internal void RaiseRuleFiring() + internal void RuleFiring() { OnRuleFiring?.Invoke(this, new ActivationEventArgs(this)); + HasFired = Trigger != MatchTrigger.Removed; } internal T GetState(object key) diff --git a/src/NRules/NRules/ActivationQueue.cs b/src/NRules/NRules/ActivationQueue.cs index ebe69335..a165b278 100644 --- a/src/NRules/NRules/ActivationQueue.cs +++ b/src/NRules/NRules/ActivationQueue.cs @@ -1,5 +1,7 @@ -using NRules.Collections; +using System; +using NRules.Collections; using NRules.RuleModel; +using NRules.Utilities; namespace NRules { @@ -9,16 +11,9 @@ internal class ActivationQueue public void Enqueue(int priority, Activation activation) { - if (activation.CompiledRule.Repeatability == RuleRepeatability.NonRepeatable) + if (!activation.IsEnqueued) { - if (activation.IsRefracted) - return; - activation.IsRefracted = true; - } - - if (!activation.IsActive) - { - activation.IsActive = true; + activation.IsEnqueued = true; _queue.Enqueue(priority, activation); } } @@ -32,14 +27,13 @@ public Activation Peek() public Activation Dequeue() { Activation activation = _queue.Dequeue(); - activation.IsActive = false; + activation.IsEnqueued = false; return activation; } public void Remove(Activation activation) { - activation.IsActive = false; - activation.IsRefracted = false; + activation.IsEnqueued = false; } public bool HasActive() @@ -53,7 +47,7 @@ private void PurgeQueue() while (!_queue.IsEmpty) { Activation current = _queue.Peek(); - if (current.IsActive) return; + if (current.IsEnqueued && current.Trigger.Matches(current.CompiledRule.ActionTriggers)) return; _queue.Dequeue(); } } @@ -63,7 +57,7 @@ public void Clear() while (!_queue.IsEmpty) { Activation activation = Dequeue(); - activation.IsRefracted = false; + activation.Clear(); } } } diff --git a/src/NRules/NRules/Agenda.cs b/src/NRules/NRules/Agenda.cs index c607f377..5ee0892e 100644 --- a/src/NRules/NRules/Agenda.cs +++ b/src/NRules/NRules/Agenda.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using NRules.AgendaFilters; using NRules.RuleModel; @@ -16,8 +15,8 @@ public interface IAgenda /// /// Indicates whether there are any activations in the agenda. /// - /// If agenda is empty then true otherwise false. - bool IsEmpty(); + /// If agenda is empty then true otherwise false. + bool IsEmpty { get; } /// /// Retrieves the next match, without removing it from agenda. @@ -59,10 +58,7 @@ internal class Agenda : IAgendaInternal private readonly List _globalFilters = new List(); private readonly Dictionary> _ruleFilters = new Dictionary>(); - public bool IsEmpty() - { - return !_activationQueue.HasActive(); - } + public bool IsEmpty => !_activationQueue.HasActive(); public IMatch Peek() { @@ -93,31 +89,53 @@ public void AddFilter(IRuleDefinition rule, IAgendaFilter filter) public Activation Pop() { Activation activation = _activationQueue.Dequeue(); + activation.RuleFiring(); return activation; } public void Add(IExecutionContext context, Activation activation) { - Enqueue(context, activation); + activation.Insert(); + if (Accept(context, activation)) + { + _activationQueue.Enqueue(activation.CompiledRule.Priority, activation); + } + else + { + _activationQueue.Remove(activation); + } } public void Modify(IExecutionContext context, Activation activation) { - Enqueue(context, activation); - } + if (activation.CompiledRule.Repeatability == RuleRepeatability.NonRepeatable && + activation.HasFired) + { + return; + } - public void Remove(IExecutionContext context, Activation activation) - { - _activationQueue.Remove(activation); - UnlinkFacts(context.Session, activation); + activation.Update(); + if (Accept(context, activation)) + { + _activationQueue.Enqueue(activation.CompiledRule.Priority, activation); + } + else + { + _activationQueue.Remove(activation); + } } - private void Enqueue(IExecutionContext context, Activation activation) + public void Remove(IExecutionContext context, Activation activation) { - if (Accept(context, activation)) + activation.Remove(); + if (activation.HasFired) + { _activationQueue.Enqueue(activation.CompiledRule.Priority, activation); + } else + { _activationQueue.Remove(activation); + } } private bool Accept(IExecutionContext context, Activation activation) @@ -151,15 +169,5 @@ private bool AcceptActivation(AgendaContext context, Activation activation) } return true; } - - private static void UnlinkFacts(ISessionInternal session, Activation activation) - { - var linkedKeys = session.GetLinkedKeys(activation).ToList(); - foreach (var key in linkedKeys) - { - var linkedFact = session.GetLinked(activation, key); - session.RetractLinked(activation, key, linkedFact); - } - } } } \ No newline at end of file diff --git a/src/NRules/NRules/CompiledRule.cs b/src/NRules/NRules/CompiledRule.cs index 49aa008c..6ef52c8d 100644 --- a/src/NRules/NRules/CompiledRule.cs +++ b/src/NRules/NRules/CompiledRule.cs @@ -13,6 +13,7 @@ internal interface ICompiledRule IEnumerable Actions { get; } IEnumerable Dependencies { get; } IRuleFilter Filter { get; } + ActionTrigger ActionTriggers { get; } } [DebuggerDisplay("{Definition.Name}")] @@ -31,12 +32,18 @@ public CompiledRule(IRuleDefinition definition, IEnumerable declara _declarations = new List(declarations); _actions = new List(actions); _dependencies = new List(dependencies); + + foreach (var ruleAction in _actions) + { + ActionTriggers |= ruleAction.Trigger; + } } public int Priority { get; } public RuleRepeatability Repeatability { get; } public IRuleDefinition Definition { get; } public IRuleFilter Filter { get; } + public ActionTrigger ActionTriggers { get; } public IEnumerable Declarations => _declarations; public IEnumerable Actions => _actions; diff --git a/src/NRules/NRules/RuleAction.cs b/src/NRules/NRules/RuleAction.cs index 69928168..57bab629 100644 --- a/src/NRules/NRules/RuleAction.cs +++ b/src/NRules/NRules/RuleAction.cs @@ -10,6 +10,7 @@ namespace NRules internal interface IRuleAction { Expression Expression { get; } + ActionTrigger Trigger { get; } object[] GetArguments(IExecutionContext executionContext, IActionContext actionContext); void Invoke(IExecutionContext executionContext, IActionContext actionContext, object[] arguments); } @@ -19,17 +20,21 @@ internal class RuleAction : IRuleAction private readonly LambdaExpression _expression; private readonly IndexMap _tupleFactMap; private readonly IndexMap _dependencyFactMap; + private readonly ActionTrigger _actionTrigger; private readonly FastDelegate> _compiledExpression; - public RuleAction(LambdaExpression expression, FastDelegate> compiledExpression, IndexMap tupleFactMap, IndexMap dependencyFactMap) + public RuleAction(LambdaExpression expression, FastDelegate> compiledExpression, + IndexMap tupleFactMap, IndexMap dependencyFactMap, ActionTrigger actionTrigger) { _expression = expression; _tupleFactMap = tupleFactMap; _dependencyFactMap = dependencyFactMap; + _actionTrigger = actionTrigger; _compiledExpression = compiledExpression; } public Expression Expression => _expression; + public ActionTrigger Trigger => _actionTrigger; public object[] GetArguments(IExecutionContext executionContext, IActionContext actionContext) { diff --git a/src/NRules/NRules/Session.cs b/src/NRules/NRules/Session.cs index 38f46cae..5b34a195 100644 --- a/src/NRules/NRules/Session.cs +++ b/src/NRules/NRules/Session.cs @@ -488,12 +488,11 @@ public int Fire() public int Fire(int maxRulesNumber) { int ruleFiredCount = 0; - while (!_agenda.IsEmpty() && ruleFiredCount < maxRulesNumber) + while (!_agenda.IsEmpty && ruleFiredCount < maxRulesNumber) { Activation activation = _agenda.Pop(); IActionContext actionContext = new ActionContext(this, activation); - activation.RaiseRuleFiring(); _actionExecutor.Execute(_executionContext, actionContext); ruleFiredCount++; diff --git a/src/NRules/NRules/Utilities/ExpressionCompiler.cs b/src/NRules/NRules/Utilities/ExpressionCompiler.cs index 2cd1b77a..e7c0d4ab 100644 --- a/src/NRules/NRules/Utilities/ExpressionCompiler.cs +++ b/src/NRules/NRules/Utilities/ExpressionCompiler.cs @@ -41,7 +41,7 @@ public static IRuleAction CompileAction(ActionElement element, IEnumerable { _matchActionCount++; }; + private static readonly Action OnReMatchAction = () => { _rematchActionCount++; }; + private static readonly Action OnUnMatchAction = () => { _unmatchActionCount++; }; + + [Fact] + public void Fire_InsertThenFire_FiresOnMatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_FilterOffInsertThenFire_DoesNotFire() + { + //Arrange + var fact = new FactType(); + + //Act + fact.AcceptFilter = false; + Session.Insert(fact); + Session.Fire(); + + //Assert + Assert.Equal(0, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenUpdateThenFire_FiresOnMatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Update(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFilterOffThenUpdateThenFire_DoesNotFire() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + fact.AcceptFilter = false; + Session.Update(fact); + Session.Fire(); + + //Assert + Assert.Equal(0, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_FilterOffInsertThenFilterOnThenUpdateThenFire_FiresOnMatch() + { + //Arrange + var fact = new FactType(); + + //Act + fact.AcceptFilter = false; + Session.Insert(fact); + fact.AcceptFilter = true; + Session.Update(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenUpdateThenFire_FiresOnMatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + Session.Update(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenTwoUpdatesThenFire_FiresOnMatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + Session.Update(fact); + Session.Update(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenFilterOffThenUpdateThenFilterOnThenUpdateThenFire_FiresOnMatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + fact.AcceptFilter = false; + Session.Update(fact); + fact.AcceptFilter = true; + Session.Update(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenUpdateThenFilterOffThenUpdateThenFire_FiresOnMatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + Session.Update(fact); + fact.AcceptFilter = false; + Session.Update(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenUpdateThenFireThenUpdateThenFire_FiresOnMatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + Session.Update(fact); + Session.Fire(); + Session.Update(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenRetractThenFire_DoesNotFire() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Retract(fact); + Session.Fire(); + + //Assert + Assert.Equal(0, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenRetractThenFire_FiresOnMatchAndOnUnmatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + Session.Retract(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(1, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenFilterOffThenRetractThenFire_FiresOnMatchAndOnUnmatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + fact.AcceptFilter = false; + Session.Retract(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(1, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenUpdateThenFireThenRetractThenFire_FiresOnMatchAndOnUnmatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + Session.Update(fact); + Session.Fire(); + Session.Retract(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(1, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenFilterOffThenUpdateThenFireThenRetractThenFire_FiresOnMatchAndOnUnmatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + fact.AcceptFilter = false; + Session.Update(fact); + Session.Fire(); + Session.Retract(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(1, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenUpdateThenRetractThenFire_FiresOnMatchAndOnUnmatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + Session.Update(fact); + Session.Retract(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(1, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenFilterOffThenUpdateThenRetractThenFire_FiresOnMatchAndOnUnmatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + fact.AcceptFilter = false; + Session.Update(fact); + Session.Retract(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(1, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenRetractThenFireThenInsertThenFire_FiresOnMatchTwiceAndOnUnmatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + Session.Retract(fact); + Session.Fire(); + Session.Insert(fact); + Session.Fire(); + + //Assert + Assert.Equal(2, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(1, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenRetractThenInsertThenFire_FiresOnMatchTwiceAndOnUnmatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + Session.Retract(fact); + Session.Insert(fact); + Session.Fire(); + + //Assert + Assert.Equal(2, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(1, _unmatchActionCount); + } + + protected override void SetUpRules() + { + SetUpRule(); + } + + public class FactType + { + public bool AcceptFilter { get; set; } = true; + } + + [Repeatability(RuleRepeatability.NonRepeatable)] + public class TestRule : Rule + { + public override void Define() + { + FactType fact = null; + + + When() + .Match(() => fact); + + Filter() + .Where(() => fact.AcceptFilter); + + Then() + .Action(ctx => OnMatchAction(), ActionTrigger.Activated) + .Action(ctx => OnReMatchAction(), ActionTrigger.Reactivated) + .Action(ctx => OnUnMatchAction(), ActionTrigger.Deactivated); + } + } + } +} \ No newline at end of file diff --git a/src/NRules/Tests/NRules.IntegrationTests/ActionTriggerTest.cs b/src/NRules/Tests/NRules.IntegrationTests/ActionTriggerTest.cs new file mode 100644 index 00000000..0b283423 --- /dev/null +++ b/src/NRules/Tests/NRules.IntegrationTests/ActionTriggerTest.cs @@ -0,0 +1,413 @@ +using System; +using NRules.Fluent.Dsl; +using NRules.IntegrationTests.TestAssets; +using NRules.RuleModel; +using Xunit; + +namespace NRules.IntegrationTests +{ + public class ActionTriggerTest : BaseRuleTestFixture + { + public ActionTriggerTest() + { + _matchActionCount = 0; + _rematchActionCount = 0; + _unmatchActionCount = 0; + } + + private static int _matchActionCount; + private static int _rematchActionCount; + private static int _unmatchActionCount; + + private static readonly Action OnMatchAction = () => { _matchActionCount++; }; + private static readonly Action OnReMatchAction = () => { _rematchActionCount++; }; + private static readonly Action OnUnMatchAction = () => { _unmatchActionCount++; }; + + [Fact] + public void Fire_InsertThenFire_FiresOnMatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_FilterOffInsertThenFire_DoesNotFire() + { + //Arrange + var fact = new FactType(); + + //Act + fact.AcceptFilter = false; + Session.Insert(fact); + Session.Fire(); + + //Assert + Assert.Equal(0, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenUpdateThenFire_FiresOnMatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Update(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFilterOffThenUpdateThenFire_DoesNotFire() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + fact.AcceptFilter = false; + Session.Update(fact); + Session.Fire(); + + //Assert + Assert.Equal(0, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_FilterOffInsertThenFilterOnThenUpdateThenFire_FiresOnMatch() + { + //Arrange + var fact = new FactType(); + + //Act + fact.AcceptFilter = false; + Session.Insert(fact); + fact.AcceptFilter = true; + Session.Update(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenUpdateThenFire_FiresOnMatchAndOnRematch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + Session.Update(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(1, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenTwoUpdatesThenFire_FiresOnMatchAndOnRematch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + Session.Update(fact); + Session.Update(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(1, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenFilterOffThenUpdateThenFilterOnThenUpdateThenFire_FiresOnMatchAndOnRematch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + fact.AcceptFilter = false; + Session.Update(fact); + fact.AcceptFilter = true; + Session.Update(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(1, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenUpdateThenFilterOffThenUpdateThenFire_FiresOnMatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + Session.Update(fact); + fact.AcceptFilter = false; + Session.Update(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenUpdateThenFireThenUpdateThenFire_FiresOnMatchAndOnRematchTwice() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + Session.Update(fact); + Session.Fire(); + Session.Update(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(2, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenRetractThenFire_DoesNotFire() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Retract(fact); + Session.Fire(); + + //Assert + Assert.Equal(0, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(0, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenRetractThenFire_FiresOnMatchAndOnUnmatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + Session.Retract(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(1, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenFilterOffThenRetractThenFire_FiresOnMatchAndOnUnmatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + fact.AcceptFilter = false; + Session.Retract(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(1, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenUpdateThenFireThenRetractThenFire_FiresOnMatchAndOnRematchAndOnUnmatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + Session.Update(fact); + Session.Fire(); + Session.Retract(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(1, _rematchActionCount); + Assert.Equal(1, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenFilterOffThenUpdateThenFireThenRetractThenFire_FiresOnMatchAndOnUnmatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + fact.AcceptFilter = false; + Session.Update(fact); + Session.Fire(); + Session.Retract(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(1, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenUpdateThenRetractThenFire_FiresOnMatchAndOnUnmatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + Session.Update(fact); + Session.Retract(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(1, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenFilterOffThenUpdateThenRetractThenFire_FiresOnMatchAndOnUnmatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + fact.AcceptFilter = false; + Session.Update(fact); + Session.Retract(fact); + Session.Fire(); + + //Assert + Assert.Equal(1, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(1, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenRetractThenFireThenInsertThenFire_FiresOnMatchTwiceAndOnUnmatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + Session.Retract(fact); + Session.Fire(); + Session.Insert(fact); + Session.Fire(); + + //Assert + Assert.Equal(2, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(1, _unmatchActionCount); + } + + [Fact] + public void Fire_InsertThenFireThenRetractThenInsertThenFire_FiresOnMatchTwiceAndOnUnmatch() + { + //Arrange + var fact = new FactType(); + + //Act + Session.Insert(fact); + Session.Fire(); + Session.Retract(fact); + Session.Insert(fact); + Session.Fire(); + + //Assert + Assert.Equal(2, _matchActionCount); + Assert.Equal(0, _rematchActionCount); + Assert.Equal(1, _unmatchActionCount); + } + + protected override void SetUpRules() + { + SetUpRule(); + } + + public class FactType + { + public bool AcceptFilter { get; set; } = true; + } + + public class TestRule : Rule + { + public override void Define() + { + FactType fact = null; + + When() + .Match(() => fact); + + Filter() + .Where(() => fact.AcceptFilter); + + Then() + .Action(ctx => OnMatchAction(), ActionTrigger.Activated) + .Action(ctx => OnReMatchAction(), ActionTrigger.Reactivated) + .Action(ctx => OnUnMatchAction(), ActionTrigger.Deactivated); + } + } + } +} \ No newline at end of file diff --git a/src/NRules/Tests/NRules.IntegrationTests/ForwardChainingLinkedTest.cs b/src/NRules/Tests/NRules.IntegrationTests/ForwardChainingLinkedTest.cs index 07671f3b..1ce1424d 100644 --- a/src/NRules/Tests/NRules.IntegrationTests/ForwardChainingLinkedTest.cs +++ b/src/NRules/Tests/NRules.IntegrationTests/ForwardChainingLinkedTest.cs @@ -85,6 +85,7 @@ public void Fire_OneMatchingFactInsertedThenRetracted_FiresFirstRuleAndChainsSec //Act - II Session.Retract(fact1); + Session.Fire(); //Assert - II Assert.Equal(0, Session.Query().Count()); diff --git a/src/NRules/Tests/NRules.Tests/AgendaTest.cs b/src/NRules/Tests/NRules.Tests/AgendaTest.cs index ec18d3cd..5ca4b3aa 100644 --- a/src/NRules/Tests/NRules.Tests/AgendaTest.cs +++ b/src/NRules/Tests/NRules.Tests/AgendaTest.cs @@ -29,7 +29,7 @@ public void Agenda_Created_Empty() var target = CreateTarget(); // Assert - Assert.True(target.IsEmpty()); + Assert.True(target.IsEmpty); } [Fact] @@ -56,7 +56,7 @@ public void Peek_AgendaHasOneActivation_ReturnsActivationAgendaEmpty() var actualActivation = target.Pop(); // Assert - Assert.True(target.IsEmpty()); + Assert.True(target.IsEmpty); Assert.Same(activation, actualActivation); } @@ -74,11 +74,11 @@ public void Add_Called_ActivationInAgenda() target.Add(_context.Object, activation); // Assert - Assert.False(target.IsEmpty()); + Assert.False(target.IsEmpty); var actualActivation = target.Pop(); Assert.Equal(rule, actualActivation.CompiledRule); Assert.Equal(factObject, actualActivation.Tuple.RightFact.Object); - Assert.True(target.IsEmpty()); + Assert.True(target.IsEmpty); } [Fact] @@ -96,7 +96,7 @@ public void Add_AcceptingGlobalFilter_ActivationInAgenda() target.Add(_context.Object, activation); // Assert - Assert.False(target.IsEmpty()); + Assert.False(target.IsEmpty); } [Fact] @@ -114,7 +114,7 @@ public void Add_RejectingGlobalFilter_ActivationNotInAgenda() target.Add(_context.Object, activation); // Assert - Assert.True(target.IsEmpty()); + Assert.True(target.IsEmpty); } [Fact] @@ -133,7 +133,7 @@ public void Add_AcceptingAndRejectingGlobalFilter_ActivationNotInAgenda() target.Add(_context.Object, activation); // Assert - Assert.True(target.IsEmpty()); + Assert.True(target.IsEmpty); } [Fact] @@ -151,7 +151,7 @@ public void Add_AcceptingRuleFilter_ActivationInAgenda() target.Add(_context.Object, activation); // Assert - Assert.False(target.IsEmpty()); + Assert.False(target.IsEmpty); } [Fact] @@ -169,7 +169,7 @@ public void Add_RejectingRuleFilter_ActivationNotInAgenda() target.Add(_context.Object, activation); // Assert - Assert.True(target.IsEmpty()); + Assert.True(target.IsEmpty); } @@ -189,7 +189,7 @@ public void Add_RejectingRuleFilterForDifferentRule_ActivationInAgenda() target.Add(_context.Object, activation); // Assert - Assert.False(target.IsEmpty()); + Assert.False(target.IsEmpty); } [Fact] @@ -208,11 +208,11 @@ public void Modify_ActivationAlreadyInQueue_ActivationUpdatedInQueue() target.Modify(_context.Object, activation); // Assert - Assert.False(target.IsEmpty()); + Assert.False(target.IsEmpty); var actualActivation = target.Pop(); Assert.Equal(rule, actualActivation.CompiledRule); Assert.Equal(factObject.Value, ((FactObject)actualActivation.Tuple.RightFact.Object).Value); - Assert.True(target.IsEmpty()); + Assert.True(target.IsEmpty); } [Fact] @@ -231,10 +231,10 @@ public void Modify_ActivationNotInQueue_ActivationReAddedToQueue() target.Modify(_context.Object, activation); // Assert - Assert.False(target.IsEmpty()); + Assert.False(target.IsEmpty); var actualActivation = target.Pop(); Assert.Equal(rule, actualActivation.CompiledRule); - Assert.True(target.IsEmpty()); + Assert.True(target.IsEmpty); } [Fact] @@ -255,7 +255,7 @@ public void Modify_ActivationAlreadyInQueueRejectingFilter_ActivationRemovedFrom target.Modify(_context.Object, activation); // Assert - Assert.True(target.IsEmpty()); + Assert.True(target.IsEmpty); } [Fact] @@ -276,7 +276,7 @@ public void Modify_ActivationNotInQueue_ActivationNotReAddedToQueue() target.Modify(_context.Object, activation); // Assert - Assert.True(target.IsEmpty()); + Assert.True(target.IsEmpty); } [Fact] @@ -296,7 +296,7 @@ public void Remove_CalledAfterAdd_AgendaEmpty() target.Remove(_context.Object, activation); // Assert - Assert.True(target.IsEmpty()); + Assert.True(target.IsEmpty); } [Fact] @@ -314,9 +314,9 @@ public void Add_CalledWithMultipleRules_RulesAreQueuedInOrder() target.Add(_context.Object, activation2); // Assert - Assert.False(target.IsEmpty()); + Assert.False(target.IsEmpty); Assert.Equal(rule1, target.Pop().CompiledRule); - Assert.False(target.IsEmpty()); + Assert.False(target.IsEmpty); Assert.Equal(rule2, target.Pop().CompiledRule); } @@ -334,7 +334,7 @@ public void Peek_AgendaHasActivations_ReturnsActivationAgendaRamainsNonEmpty() var actualActivation = target.Peek(); // Assert - Assert.False(target.IsEmpty()); + Assert.False(target.IsEmpty); Assert.Same(activation, actualActivation); } @@ -362,7 +362,7 @@ public void Clear_CalledAfterActivation_AgendaEmpty() target.Clear(); // Assert - Assert.True(target.IsEmpty()); + Assert.True(target.IsEmpty); } private Agenda CreateTarget() @@ -373,8 +373,10 @@ private Agenda CreateTarget() private static ICompiledRule MockRule() { var compiledRuleMock = new Mock(); - var ruleMock = new Mock(); - compiledRuleMock.Setup(x => x.Definition).Returns(ruleMock.Object); + var ruleDefinitionMock = new Mock(); + compiledRuleMock.Setup(x => x.Definition).Returns(ruleDefinitionMock.Object); + var actionTrigger = ActionTrigger.Activated | ActionTrigger.Reactivated | ActionTrigger.Deactivated; + compiledRuleMock.Setup(x => x.ActionTriggers).Returns(actionTrigger); return compiledRuleMock.Object; } diff --git a/src/NRules/Tests/NRules.Tests/SessionTest.cs b/src/NRules/Tests/NRules.Tests/SessionTest.cs index b690a8d9..c0e99b98 100644 --- a/src/NRules/Tests/NRules.Tests/SessionTest.cs +++ b/src/NRules/Tests/NRules.Tests/SessionTest.cs @@ -150,7 +150,7 @@ public void Fire_NoActiveRules_ReturnsZero() { // Arrange var target = CreateTarget(); - _agenda.Setup(x => x.IsEmpty()).Returns(true); + _agenda.Setup(x => x.IsEmpty).Returns(true); // Act var actual = target.Fire(); @@ -165,7 +165,7 @@ public void Fire_ActiveRules_ReturnsNumberOfRulesFired() // Arrange var target = CreateTarget(); _agenda.Setup(x => x.Pop()).Returns(StubActivation()); - _agenda.SetupSequence(x => x.IsEmpty()) + _agenda.SetupSequence(x => x.IsEmpty) .Returns(false).Returns(false).Returns(true); // Act @@ -181,7 +181,7 @@ public void Fire_ActiveRulesMoreThanMax_FiresMaxRules() // Arrange var target = CreateTarget(); _agenda.Setup(x => x.Pop()).Returns(StubActivation()); - _agenda.SetupSequence(x => x.IsEmpty()) + _agenda.SetupSequence(x => x.IsEmpty) .Returns(false).Returns(false).Returns(true); // Act