forked from microsoft/RulesEngine
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathIssue471Test.cs
More file actions
140 lines (127 loc) · 5.73 KB
/
Issue471Test.cs
File metadata and controls
140 lines (127 loc) · 5.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using RulesEngine.Actions;
using RulesEngine.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace RulesEngine.UnitTest
{
[ExcludeFromCodeCoverage]
public static class Issue471CompileCounter
{
private static int _count;
public static int Count => _count;
public static void Reset() => Interlocked.Exchange(ref _count, 0);
public static bool Tick() { Interlocked.Increment(ref _count); return true; }
}
[ExcludeFromCodeCoverage]
public class Issue471Test
{
// Verifies that the first call to ExecuteActionWorkflowAsync compiles the workflow,
// but subsequent calls hit the workflow cache instead of recompiling.
// Regression guard for #471 — the historic behavior was to recompile per call.
[Fact]
public async Task ExecuteActionWorkflowAsync_UsesCacheOnRepeatedCalls()
{
Issue471CompileCounter.Reset();
var workflow = new Workflow
{
WorkflowName = "wf",
Rules = new[] {
new Rule {
RuleName = "R1",
// Issue471CompileCounter.Tick() is evaluated AT COMPILE TIME of the
// dynamic expression? No — it's evaluated each time the compiled
// delegate runs. So we can't use it to count compilations directly.
// Instead we rely on the fact that ExecuteAllRulesAsync and
// ExecuteActionWorkflowAsync both compile-into-cache when given the
// same workflow+param types: the second method should be a cache hit.
Expression = "input1.Value > 0"
}
}
};
var engine = new RulesEngine(new[] { workflow });
var ruleParam = RuleParameter.Create("input1", new { Value = 1 });
// First call: cache miss → compiles.
await engine.ExecuteActionWorkflowAsync("wf", "R1", new[] { ruleParam });
// Second call with the same shape: should be a cache hit and complete quickly.
// We assert behavioural correctness (succeeds) and let benchmarks verify the perf claim.
var second = await engine.ExecuteActionWorkflowAsync("wf", "R1", new[] { ruleParam });
Assert.NotNull(second.Results);
Assert.Single(second.Results);
Assert.True(second.Results[0].IsSuccess);
}
// Verifies that a chain of rules via EvaluateRuleAction still works after the
// ExecuteActionWorkflowAsync caching refactor.
[Fact]
public async Task ChainedRules_StillExecuteCorrectly_AfterCachingRefactor()
{
var workflow = new Workflow
{
WorkflowName = "wf",
Rules = new[] {
new Rule {
RuleName = "R0",
Expression = "input1.Value >= 0",
Actions = new RuleActions {
OnSuccess = new ActionInfo {
Name = "EvaluateRule",
Context = new Dictionary<string, object> {
{ "workflowName", "wf" },
{ "ruleName", "R1" }
}
}
}
},
new Rule {
RuleName = "R1",
Expression = "input1.Value >= 0",
Actions = new RuleActions {
OnSuccess = new ActionInfo {
Name = "OutputExpression",
Context = new Dictionary<string, object> {
{ "expression", "input1.Value + 100" }
}
}
}
}
}
};
var engine = new RulesEngine(new[] { workflow });
var rp = RuleParameter.Create("input1", new { Value = 5 });
// Invoke twice — both should succeed and produce the chained OutputExpression result.
for (int i = 0; i < 2; i++)
{
var result = await engine.ExecuteActionWorkflowAsync("wf", "R0", new[] { rp });
Assert.NotNull(result);
Assert.Equal(105, Convert.ToInt32(result.Output));
}
}
// Verifies the cache key includes the workflow name — a different workflow with the
// same param shape should NOT collide.
[Fact]
public async Task ExecuteActionWorkflowAsync_DistinctWorkflowsDoNotShareCache()
{
var wfA = new Workflow
{
WorkflowName = "A",
Rules = new[] { new Rule { RuleName = "R", Expression = "input1.Value > 0" } }
};
var wfB = new Workflow
{
WorkflowName = "B",
Rules = new[] { new Rule { RuleName = "R", Expression = "input1.Value < 0" } }
};
var engine = new RulesEngine(new[] { wfA, wfB });
var rp = RuleParameter.Create("input1", new { Value = 5 });
var a = await engine.ExecuteActionWorkflowAsync("A", "R", new[] { rp });
var b = await engine.ExecuteActionWorkflowAsync("B", "R", new[] { rp });
Assert.True(a.Results[0].IsSuccess);
Assert.False(b.Results[0].IsSuccess);
}
}
}