From 0ee13f5e9b1aaaf11e89e14710b5ecffaa1290ba Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 27 Nov 2022 17:13:16 +0000 Subject: [PATCH 01/54] feat: create rules source and middlewares support --- src/Rules.Framework/Source/GetRulesArgs.cs | 3 +++ src/Rules.Framework/Source/RulesSource.cs | 24 +++++++++---------- src/Rules.Framework/Source/UpdateRuleArgs.cs | 3 +++ .../Source/RulesSourceTests.cs | 5 ++++ 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/Rules.Framework/Source/GetRulesArgs.cs b/src/Rules.Framework/Source/GetRulesArgs.cs index d79c9179..d5245e61 100644 --- a/src/Rules.Framework/Source/GetRulesArgs.cs +++ b/src/Rules.Framework/Source/GetRulesArgs.cs @@ -1,6 +1,9 @@ namespace Rules.Framework.Source { using System; + using System.Collections.Generic; + using System.Runtime.InteropServices; + using System.Text; internal sealed class GetRulesArgs { diff --git a/src/Rules.Framework/Source/RulesSource.cs b/src/Rules.Framework/Source/RulesSource.cs index c21618be..013971bf 100644 --- a/src/Rules.Framework/Source/RulesSource.cs +++ b/src/Rules.Framework/Source/RulesSource.cs @@ -35,12 +35,12 @@ public async Task>> GetRulesAsync public async Task>> GetRulesFilteredAsync(GetRulesFilteredArgs args) { return await this.getRulesFilteredDelegate.Invoke(args).ConfigureAwait(false); - } + } public async Task UpdateRuleAsync(UpdateRuleArgs args) { await this.updateRuleDelegate.Invoke(args).ConfigureAwait(false); - } + } private static AddRuleDelegate CreateAddRulePipelineDelegate( IRulesDataSource rulesDataSource, @@ -53,15 +53,15 @@ private static AddRuleDelegate CreateAddRulePipeli var middlewareNode = middlewares.Last; while (middlewareNode is { }) - { + { var middleware = middlewareNode.Value; var immutableAction = action; action = async (args) => await middleware.HandleAddRuleAsync(args, immutableAction).ConfigureAwait(false); // Get previous middleware node. middlewareNode = middlewareNode.Previous; - } } + } return action; } @@ -72,9 +72,9 @@ private static GetRulesFilteredDelegate CreateGetR { GetRulesFilteredDelegate action = async (args) => - { + { RulesFilterArgs rulesFilterArgs = new() - { + { ContentType = args.ContentType, Name = args.Name, Priority = args.Priority, @@ -88,18 +88,18 @@ private static GetRulesFilteredDelegate CreateGetR var middlewareNode = middlewares.Last; while (middlewareNode is { }) - { + { var middleware = middlewareNode.Value; var immutableAction = action; action = async (args) => await middleware.HandleGetRulesFilteredAsync(args, immutableAction).ConfigureAwait(false); // Get previous middleware node. middlewareNode = middlewareNode.Previous; + } } - } return action; - } + } private static GetRulesDelegate CreateGetRulesPipelineDelegate( IRulesDataSource rulesDataSource, @@ -110,11 +110,11 @@ private static GetRulesDelegate CreateGetRulesPipe => await rulesDataSource.GetRulesAsync(args.ContentType, args.DateBegin, args.DateEnd).ConfigureAwait(false); if (middlewares.Count > 0) - { + { var middlewareNode = middlewares.Last; while (middlewareNode is { }) - { + { var middleware = middlewareNode.Value; var immutableAction = action; action = async (args) => await middleware.HandleGetRulesAsync(args, immutableAction).ConfigureAwait(false); @@ -122,7 +122,7 @@ private static GetRulesDelegate CreateGetRulesPipe // Get previous middleware node. middlewareNode = middlewareNode.Previous; } - } + } return action; } diff --git a/src/Rules.Framework/Source/UpdateRuleArgs.cs b/src/Rules.Framework/Source/UpdateRuleArgs.cs index 69ae0981..63a225b3 100644 --- a/src/Rules.Framework/Source/UpdateRuleArgs.cs +++ b/src/Rules.Framework/Source/UpdateRuleArgs.cs @@ -1,6 +1,9 @@ namespace Rules.Framework.Source { using Rules.Framework.Core; + using System; + using System.Collections.Generic; + using System.Text; internal sealed class UpdateRuleArgs { diff --git a/tests/Rules.Framework.Tests/Source/RulesSourceTests.cs b/tests/Rules.Framework.Tests/Source/RulesSourceTests.cs index aba74221..4c9c0e86 100644 --- a/tests/Rules.Framework.Tests/Source/RulesSourceTests.cs +++ b/tests/Rules.Framework.Tests/Source/RulesSourceTests.cs @@ -9,6 +9,11 @@ namespace Rules.Framework.Tests.Source using Rules.Framework.Core; using Rules.Framework.Source; using Rules.Framework.Tests.TestStubs; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; using Xunit; public class RulesSourceTests From 91aa8522aa53618fae3aca082977a667af97f6f2 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 2 Oct 2022 12:57:57 +0100 Subject: [PATCH 02/54] test: create benchmark project and add benchmarks --- .env | 1 + .gitattributes | 1 + rules-framework.sln | 7 + .../DataSource/ConditionNodeDataModel.cs | 19 + .../DataSource/RuleDataModel.cs | 21 + .../InMemoryRulesDataSource.cs | 62 + .../Rules.Framework.BenchmarkTests/Program.cs | 29 + .../Rules.Framework.BenchmarkTests.csproj | 21 + .../Tests/Benchmark1/Benchmark1.cs | 44 + .../Tests/Benchmark1/Benchmark1Data.cs | 36 + .../Tests/Benchmark1/ConditionTypes.cs | 16 + .../Tests/Benchmark1/ContentTypes.cs | 13 + .../Tests/Benchmark2/Benchmark2.cs | 41 + .../Tests/Benchmark2/Benchmark2Data.cs | 109 ++ .../Tests/Benchmark2/ConditionTypes.cs | 15 + .../Tests/Benchmark2/ContentTypes.cs | 7 + .../Tests/Benchmark3/Benchmark3.cs | 41 + .../Tests/Benchmark3/Benchmark3Data.Flush.cs | 68 + .../Benchmark3/Benchmark3Data.FourOfAKind.cs | 163 ++ .../Benchmark3/Benchmark3Data.HighCard.cs | 163 ++ .../Tests/Benchmark3/Benchmark3Data.Pair.cs | 163 ++ .../Benchmark3/Benchmark3Data.RoyalFlush.cs | 200 +++ .../Benchmark3/Benchmark3Data.Straight.cs | 380 +++++ .../Benchmark3Data.StraightFlush.cs | 1467 +++++++++++++++++ .../Benchmark3/Benchmark3Data.ThreeOfAKind.cs | 163 ++ .../Tests/Benchmark3/Benchmark3Data.cs | 55 + .../Tests/Benchmark3/CardPokerScore.cs | 15 + .../Tests/Benchmark3/ConditionTypes.cs | 75 + .../Tests/Benchmark3/ContentTypes.cs | 7 + .../Tests/IBenchmark.cs | 11 + .../Tests/IBenchmarkData.cs | 15 + .../Rules.Framework.Tests.csproj | 16 +- 32 files changed, 3434 insertions(+), 10 deletions(-) create mode 100644 .env create mode 100644 .gitattributes create mode 100644 tests/Rules.Framework.BenchmarkTests/DataSource/ConditionNodeDataModel.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/DataSource/RuleDataModel.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/InMemoryRulesDataSource.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Program.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1Data.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/ConditionTypes.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/ContentTypes.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2Data.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/ConditionTypes.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/ContentTypes.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Flush.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.FourOfAKind.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.HighCard.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Pair.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.RoyalFlush.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Straight.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.StraightFlush.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.ThreeOfAKind.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/CardPokerScore.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/ConditionTypes.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/ContentTypes.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/IBenchmark.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Tests/IBenchmarkData.cs diff --git a/.env b/.env new file mode 100644 index 00000000..b7cab7de --- /dev/null +++ b/.env @@ -0,0 +1 @@ +MONGO_DB_HOST=docker.local \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..5b4199da --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto \ No newline at end of file diff --git a/rules-framework.sln b/rules-framework.sln index 448f4305..e3057512 100644 --- a/rules-framework.sln +++ b/rules-framework.sln @@ -42,6 +42,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rules.Framework.WebUI", "sr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rules.Framework.WebUI.Tests", "tests\Rules.Framework.WebUI.Tests\Rules.Framework.WebUI.Tests.csproj", "{29DC6661-4F0C-46F7-AC91-968700D13C11}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rules.Framework.BenchmarkTests", "tests\Rules.Framework.BenchmarkTests\Rules.Framework.BenchmarkTests.csproj", "{16C9F383-3B58-4911-9D26-7FDB907DD0D2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -104,6 +106,10 @@ Global {29DC6661-4F0C-46F7-AC91-968700D13C11}.Debug|Any CPU.Build.0 = Debug|Any CPU {29DC6661-4F0C-46F7-AC91-968700D13C11}.Release|Any CPU.ActiveCfg = Release|Any CPU {29DC6661-4F0C-46F7-AC91-968700D13C11}.Release|Any CPU.Build.0 = Release|Any CPU + {16C9F383-3B58-4911-9D26-7FDB907DD0D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {16C9F383-3B58-4911-9D26-7FDB907DD0D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {16C9F383-3B58-4911-9D26-7FDB907DD0D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {16C9F383-3B58-4911-9D26-7FDB907DD0D2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -123,6 +129,7 @@ Global {F5E1BEF6-85CD-4966-94C2-DCA02D824043} = {3125C5B5-0FD5-4370-9E22-A44A3BCEED59} {7CE82611-FEC1-49E9-91FB-4C3ADF5ED56F} = {AEE746EC-CEAA-4892-8C29-0CAAB97A23A8} {29DC6661-4F0C-46F7-AC91-968700D13C11} = {74E24C97-8EE4-4B69-AECD-4765FD2C751F} + {16C9F383-3B58-4911-9D26-7FDB907DD0D2} = {74E24C97-8EE4-4B69-AECD-4765FD2C751F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FA9D4C31-972B-49C2-9F63-C56ED766DAB0} diff --git a/tests/Rules.Framework.BenchmarkTests/DataSource/ConditionNodeDataModel.cs b/tests/Rules.Framework.BenchmarkTests/DataSource/ConditionNodeDataModel.cs new file mode 100644 index 00000000..d6c890f6 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/DataSource/ConditionNodeDataModel.cs @@ -0,0 +1,19 @@ +namespace Rules.Framework.BenchmarkTests.DataSource +{ + using System.Collections.Generic; + + internal class ConditionNodeDataModel + { + public IEnumerable ChildConditionNodes { get; set; } + + public string ConditionType { get; set; } + + public string DataType { get; set; } + + public string LogicalOperator { get; set; } + + public string Operand { get; set; } + + public string Operator { get; set; } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/DataSource/RuleDataModel.cs b/tests/Rules.Framework.BenchmarkTests/DataSource/RuleDataModel.cs new file mode 100644 index 00000000..7592ccc2 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/DataSource/RuleDataModel.cs @@ -0,0 +1,21 @@ +namespace Rules.Framework.BenchmarkTests.DataSource +{ + using System; + + internal class RuleDataModel + { + public string Content { get; set; } + + public short ContentTypeCode { get; set; } + + public DateTime DateBegin { get; set; } + + public DateTime? DateEnd { get; set; } + + public string Name { get; set; } + + public int Priority { get; set; } + + public ConditionNodeDataModel RootCondition { get; set; } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/InMemoryRulesDataSource.cs b/tests/Rules.Framework.BenchmarkTests/InMemoryRulesDataSource.cs new file mode 100644 index 00000000..f22c7cbd --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/InMemoryRulesDataSource.cs @@ -0,0 +1,62 @@ +namespace Rules.Framework.BenchmarkTests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Rules.Framework.Core; + + internal class InMemoryRulesDataSource : IRulesDataSource + { + private readonly List> rules; + + internal InMemoryRulesDataSource(IEnumerable> rules) + { + this.rules = new List>(rules); + } + + public Task AddRuleAsync(Rule rule) + { + this.rules.Add(rule); + + return Task.CompletedTask; + } + + public Task>> GetRulesAsync( + TContentType contentType, + DateTime dateBegin, + DateTime dateEnd) + => Task.FromResult(this.rules.Where(r => object.Equals(r.ContentContainer.ContentType, contentType))); + + public Task>> GetRulesByAsync(RulesFilterArgs rulesFilterArgs) + { + IEnumerable> result = this.rules.AsEnumerable(); + + if (!object.Equals(rulesFilterArgs.ContentType, default(TContentType))) + { + result = result.Where(r => object.Equals(rulesFilterArgs.ContentType, r.ContentContainer.ContentType)); + } + + if (!string.IsNullOrWhiteSpace(rulesFilterArgs.Name)) + { + result = result.Where(r => string.Equals(r.Name, rulesFilterArgs.Name)); + } + + if (rulesFilterArgs.Priority.HasValue) + { + result = result.Where(r => r.Priority == rulesFilterArgs.Priority.GetValueOrDefault()); + } + + return Task.FromResult>>(result.Select(r => r.Clone()).ToList()); + } + + public Task UpdateRuleAsync(Rule rule) + { + this.rules.RemoveAll(r => string.Equals(r.Name, rule.Name)); + + this.rules.Add(rule); + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Program.cs b/tests/Rules.Framework.BenchmarkTests/Program.cs new file mode 100644 index 00000000..cc7e477d --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Program.cs @@ -0,0 +1,29 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Diagnostics.Windows; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Running; + +[assembly: SimpleJob(RuntimeMoniker.Net60)] + +internal class Program +{ + private static void Main(string[] args) + { + Console.WriteLine("Starting benchmark tests."); + Console.WriteLine(); + + var manualConfig = ManualConfig.CreateMinimumViable(); + manualConfig.AddDiagnoser(MemoryDiagnoser.Default); + manualConfig.AddHardwareCounters(HardwareCounter.BranchInstructions, HardwareCounter.BranchMispredictions); + manualConfig.AddExporter(HtmlExporter.Default); + + _ = BenchmarkRunner.Run(typeof(Program).Assembly, manualConfig); + + Console.WriteLine("Press any key to exit..."); + Console.Read(); + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj b/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj new file mode 100644 index 00000000..1cc2b2bb --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj @@ -0,0 +1,21 @@ + + + + Exe + net48;net6.0 + enable + enable + 10.0 + + + + + + + + + + + + + diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1.cs new file mode 100644 index 00000000..3fe424be --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1.cs @@ -0,0 +1,44 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark1 +{ + using BenchmarkDotNet.Attributes; + using Rules.Framework.Builder; + using Rules.Framework.Core; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + public class Benchmark1 : IBenchmark + { + private readonly Benchmark1Data benchmarkData = new Benchmark1Data(); + private RulesEngine rulesEngine; + + [Benchmark] + public async Task RunAsync() + { + await this.rulesEngine.MatchOneAsync(ContentTypes.ContentType1, this.benchmarkData.MatchDate, this.benchmarkData.Conditions).ConfigureAwait(false); + } + + [GlobalSetup] + public async Task SetUpAsync() + { + this.rulesEngine = RulesEngineBuilder.CreateRulesEngine() + .WithContentType() + .WithConditionType() + .SetDataSource(new InMemoryRulesDataSource(Enumerable.Empty>())) + .Build(); + + foreach (var rule in this.benchmarkData.Rules) + { + await this.rulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop).ConfigureAwait(false); + } + } + + [GlobalCleanup] + public async Task TearDownAsync() + { + this.rulesEngine = null; + await Task.CompletedTask.ConfigureAwait(false); + } + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1Data.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1Data.cs new file mode 100644 index 00000000..62787d02 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1Data.cs @@ -0,0 +1,36 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark1 +{ + using Rules.Framework.BenchmarkTests.Tests; + using Rules.Framework.Builder; + using Rules.Framework.Core; + using System; + using System.Collections.Generic; + + internal class Benchmark1Data : IBenchmarkData + { + public IEnumerable> Conditions => new[] { new Condition { Type = ConditionTypes.StringCondition, Value = "Let's benchmark this!" } }; + + public DateTime MatchDate => DateTime.Parse("2022-10-01"); + + public IEnumerable> Rules => this.GetRules(); + + private IEnumerable> GetRules() + { + var ruleResult = RuleBuilder.NewRule() + .WithName("Benchmark 1 - Test rule") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.ContentType1, "Dummy Content") + .WithCondition(x => + { + return x.AsValued(ConditionTypes.StringCondition) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand("Let's benchmark this!") + .Build(); + }) + .Build(); + + return new[] { ruleResult.Rule }; + } + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/ConditionTypes.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/ConditionTypes.cs new file mode 100644 index 00000000..0ef2ba63 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/ConditionTypes.cs @@ -0,0 +1,16 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark1 +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + internal enum ConditionTypes + { + IntegerCondition = 1, + DecimalCondition = 2, + BooleanCondition = 3, + StringCondition = 4 + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/ContentTypes.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/ContentTypes.cs new file mode 100644 index 00000000..01610090 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/ContentTypes.cs @@ -0,0 +1,13 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark1 +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + internal enum ContentTypes + { + ContentType1 = 1 + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2.cs new file mode 100644 index 00000000..c2d3196c --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2.cs @@ -0,0 +1,41 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark2 +{ + using BenchmarkDotNet.Attributes; + using Rules.Framework.Core; + using System.Linq; + using System.Threading.Tasks; + + public class Benchmark2 : IBenchmark + { + private readonly Benchmark2Data benchmarkData = new Benchmark2Data(); + private RulesEngine rulesEngine; + + [Benchmark] + public async Task RunAsync() + { + await this.rulesEngine.MatchOneAsync(ContentTypes.Songs, this.benchmarkData.MatchDate, this.benchmarkData.Conditions).ConfigureAwait(false); + } + + [GlobalSetup] + public async Task SetUpAsync() + { + this.rulesEngine = RulesEngineBuilder.CreateRulesEngine() + .WithContentType() + .WithConditionType() + .SetDataSource(new InMemoryRulesDataSource(Enumerable.Empty>())) + .Build(); + + foreach (var rule in this.benchmarkData.Rules) + { + await this.rulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop).ConfigureAwait(false); + } + } + + [GlobalCleanup] + public async Task TearDownAsync() + { + this.rulesEngine = null; + await Task.CompletedTask.ConfigureAwait(false); + } + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2Data.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2Data.cs new file mode 100644 index 00000000..28fae1c7 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2Data.cs @@ -0,0 +1,109 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark2 +{ + using Rules.Framework; + using Rules.Framework.Builder; + using Rules.Framework.Core; + using System; + using System.Collections.Generic; + + internal class Benchmark2Data : IBenchmarkData + { + public IEnumerable> Conditions => new[] + { + new Condition { Type = ConditionTypes.Artist, Value = "Queen" }, + new Condition { Type = ConditionTypes.Lyrics, Value = "Is this the real life?\nIs this just fantasy?\nCaught in a landside,\nNo escape from reality" }, + new Condition { Type = ConditionTypes.ReleaseYear, Value = 1975 } + }; + + public DateTime MatchDate => DateTime.Parse("2022-11-01"); + + public IEnumerable> Rules => this.GetRules(); + + private IEnumerable> GetRules() + { + var rule1Result = RuleBuilder.NewRule() + .WithName("Benchmark 2 - Bohemian Rapsody") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.Songs, "Bohemian Rapsody") + .WithCondition(x => + { + return x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.Artist) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand("Queen") + .Build()) + .AddCondition(c => + c.AsValued(ConditionTypes.Lyrics) + .OfDataType() + .WithComparisonOperator(Operators.Contains) + .SetOperand("real life") + .Build()) + .AddCondition(c => + c.AsValued(ConditionTypes.ReleaseYear) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1973) + .Build()) + .AddCondition(c => + c.AsValued(ConditionTypes.ReleaseYear) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1977) + .Build()) + .Build(); + }) + .Build(); + + var rule2Result = RuleBuilder.NewRule() + .WithName("Benchmark 2 - Stairway to Heaven") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.Songs, "Stairway to Heaven") + .WithCondition(x => + { + return x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.Artist) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand("Led Zeppelin") + .Build()) + .AddCondition(c => + c.AsComposed() + .WithLogicalOperator(LogicalOperators.Or) + .AddCondition(y => + y.AsValued(ConditionTypes.Lyrics) + .OfDataType() + .WithComparisonOperator(Operators.Contains) + .SetOperand("all that glitters is gold") + .Build()) + .AddCondition(y => + y.AsValued(ConditionTypes.Lyrics) + .OfDataType() + .WithComparisonOperator(Operators.Contains) + .SetOperand("it makes me wonder") + .Build()) + .Build()) + .AddCondition(c => + c.AsValued(ConditionTypes.ReleaseYear) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1973) + .Build()) + .AddCondition(c => + c.AsValued(ConditionTypes.ReleaseYear) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1977) + .Build()) + .Build(); + }) + .Build(); + + return new[] { rule1Result.Rule, rule2Result.Rule }; + } + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/ConditionTypes.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/ConditionTypes.cs new file mode 100644 index 00000000..0c96d4c3 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/ConditionTypes.cs @@ -0,0 +1,15 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark2 +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + internal enum ConditionTypes + { + Lyrics = 1, + Artist = 2, + ReleaseYear = 3 + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/ContentTypes.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/ContentTypes.cs new file mode 100644 index 00000000..628ae5b8 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/ContentTypes.cs @@ -0,0 +1,7 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark2 +{ + internal enum ContentTypes + { + Songs = 1 + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3.cs new file mode 100644 index 00000000..4477b06d --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3.cs @@ -0,0 +1,41 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 +{ + using BenchmarkDotNet.Attributes; + using Rules.Framework.Core; + using System.Linq; + using System.Threading.Tasks; + + public class Benchmark3 : IBenchmark + { + private readonly Benchmark3Data benchmarkData = new Benchmark3Data(); + private RulesEngine rulesEngine; + + [Benchmark] + public async Task RunAsync() + { + await this.rulesEngine.MatchOneAsync(ContentTypes.TexasHoldemPokerSingleCombinations, this.benchmarkData.MatchDate, this.benchmarkData.Conditions).ConfigureAwait(false); + } + + [GlobalSetup] + public async Task SetUpAsync() + { + this.rulesEngine = RulesEngineBuilder.CreateRulesEngine() + .WithContentType() + .WithConditionType() + .SetDataSource(new InMemoryRulesDataSource(Enumerable.Empty>())) + .Build(); + + foreach (var rule in this.benchmarkData.Rules) + { + await this.rulesEngine.AddRuleAsync(rule, RuleAddPriorityOption.AtTop).ConfigureAwait(false); + } + } + + [GlobalCleanup] + public async Task TearDownAsync() + { + this.rulesEngine = null; + await Task.CompletedTask.ConfigureAwait(false); + } + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Flush.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Flush.cs new file mode 100644 index 00000000..221ff221 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Flush.cs @@ -0,0 +1,68 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 +{ + using Rules.Framework.Builder; + using Rules.Framework.Core; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + internal partial class Benchmark3Data + { + private IEnumerable> GetFlushRules() + { + return new[] + { + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Flush of Clubs") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Flush" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(5) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Flush of Diamonds") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Flush" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(5) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Flush of Hearts") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Flush" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(5) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Flush of Spades") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Flush" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(5) + .Build() + ) + .Build().Rule, + }; + } + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.FourOfAKind.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.FourOfAKind.cs new file mode 100644 index 00000000..ddccb45f --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.FourOfAKind.cs @@ -0,0 +1,163 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 +{ + using Rules.Framework.Builder; + using Rules.Framework.Core; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + internal partial class Benchmark3Data + { + private IEnumerable> GetFourOfAKindRules() + { + return new[] + { + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Four Of A Kind Deuces") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfDeuces) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(4) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Four Of A Kind Treys") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfTreys) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(4) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Four Of A Kind Fours") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfFours) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(4) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Four Of A Kind Fives") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfFives) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(4) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Four Of A Kind Sixes") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfSixes) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(4) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Four Of A Kind Sevens") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfSevens) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(4) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Four Of A Kind Eights") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfEigths) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(4) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Four Of A Kind Nines") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfNines) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(4) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Four Of A Kind Tens") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfTens) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(4) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Four Of A Kind Jacks") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfJacks) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(4) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Four Of A Kind Queens") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfQueens) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(4) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Four Of A Kind Kings") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfKings) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(4) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Four Of A Kind Aces") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Four Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfAces) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(4) + .Build()) + .Build().Rule, + }; + } + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.HighCard.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.HighCard.cs new file mode 100644 index 00000000..49f141bf --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.HighCard.cs @@ -0,0 +1,163 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 +{ + using Rules.Framework.Builder; + using Rules.Framework.Core; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + internal partial class Benchmark3Data + { + private IEnumerable> GetHighCardsRules() + { + return new[] + { + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Deuces") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfDeuces) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Treys") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfTreys) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Fours") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfFours) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Fives") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfFives) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Sixes") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfSixes) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Sevens") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfSevens) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Eights") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfEigths) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Nines") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfNines) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Tens") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfTens) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Jacks") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfJacks) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Queens") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfQueens) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Kings") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfKings) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Aces") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfAces) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + }; + } + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Pair.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Pair.cs new file mode 100644 index 00000000..a68021b0 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Pair.cs @@ -0,0 +1,163 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 +{ + using Rules.Framework.Builder; + using Rules.Framework.Core; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + internal partial class Benchmark3Data + { + private IEnumerable> GetPairsRules() + { + return new[] + { + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Pair Deuces") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfDeuces) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(2) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Pair Treys") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfTreys) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(2) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Pair Fours") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfFours) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(2) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Pair Fives") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfFives) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(2) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Pair Sixes") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfSixes) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(2) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Pair Sevens") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfSevens) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(2) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Pair Eights") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfEigths) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(2) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Pair Nines") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfNines) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(2) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Pair Tens") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfTens) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(2) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Pair Jacks") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfJacks) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(2) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Pair Queens") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfQueens) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(2) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Pair Kings") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfKings) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(2) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Pair Aces") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Pair" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfAces) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(2) + .Build()) + .Build().Rule, + }; + } + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.RoyalFlush.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.RoyalFlush.cs new file mode 100644 index 00000000..ffb26b17 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.RoyalFlush.cs @@ -0,0 +1,200 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 +{ + using Rules.Framework.Builder; + using Rules.Framework.Core; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + internal partial class Benchmark3Data + { + private IEnumerable> GetRoyalFlushRules() + { + return new[] + { + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Royal flush of Clubs: Ace, King, Queen, Jack, 10") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Royal Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.TenOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.JackOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.QueenOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.KingOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.AceOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Royal flush of Diamonds: Ace, King, Queen, Jack, 10") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Royal Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.TenOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.JackOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.QueenOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.KingOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.AceOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Royal flush of Hearts: Ace, King, Queen, Jack, 10") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Royal Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.TenOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.JackOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.QueenOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.KingOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.AceOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Royal flush of Spades: Ace, King, Queen, Jack, 10") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Royal Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.TenOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.JackOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.QueenOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.KingOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.AceOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + }; + } + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Straight.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Straight.cs new file mode 100644 index 00000000..ff7ce278 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Straight.cs @@ -0,0 +1,380 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 +{ + using Rules.Framework.Builder; + using Rules.Framework.Core; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + internal partial class Benchmark3Data + { + private IEnumerable> GetStraightRules() + { + return new[] + { + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight 6, 5, 4, 3, 2") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfDeuces) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfTreys) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfFours) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfFives) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfSixes) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight 7, 6, 5, 4, 3") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfTreys) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfFours) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfFives) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfSixes) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfSevens) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight 8, 7, 6, 5, 4") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfFours) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfFives) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfSixes) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfSevens) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfEigths) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight 9, 8, 7, 6, 5") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfFives) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfSixes) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfSevens) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfEigths) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfNines) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight 10, 9, 8, 7, 6") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfSixes) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfSevens) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfEigths) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfNines) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfTens) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight Jack, 10, 9, 8, 7") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfSevens) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfEigths) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfNines) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfTens) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfJacks) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight Queen, Jack, 10, 9, 8") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfEigths) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfNines) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfTens) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfJacks) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfQueens) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight King, Queen, Jack, 10, 9") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfNines) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfTens) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfJacks) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfQueens) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NumberOfKings) + .OfDataType() + .WithComparisonOperator(Operators.GreaterThanOrEqual) + .SetOperand(1) + .Build() + ) + .Build() + ) + .Build().Rule, + }; + } + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.StraightFlush.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.StraightFlush.cs new file mode 100644 index 00000000..915ee745 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.StraightFlush.cs @@ -0,0 +1,1467 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 +{ + using Rules.Framework.Builder; + using Rules.Framework.Core; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + internal partial class Benchmark3Data + { + private IEnumerable> GetStraightFlushRules() + { + return new[] + { + // Straight flush of Clubs: + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Clubs: 6, 5, 4, 3, 2") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.DeuceOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.TreyOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.FourOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.FiveOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SixOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Clubs: 7, 6, 5, 4, 3") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.TreyOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.FourOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.FiveOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SixOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SevenOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Clubs: 8, 7, 6, 5, 4") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.FourOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.FiveOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SixOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SevenOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.EightOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Clubs: 9, 8, 7, 6, 5") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.FiveOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SixOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SevenOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.EightOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NineOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Clubs: 10, 9, 8, 7, 6") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.SixOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SevenOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.EightOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NineOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.TenOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Clubs: Jack, 10, 9, 8, 7") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.SevenOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.EightOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NineOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.TenOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.JackOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Clubs: Queen, Jack, 10, 9, 8") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.EightOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NineOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.TenOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.JackOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.QueenOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Clubs: King, Queen, Jack, 10, 9") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.NineOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.TenOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.JackOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.QueenOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.KingOfClubs) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + + // Straight flush of diamonds + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Diamonds: 6, 5, 4, 3, 2") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.DeuceOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.TreyOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.FourOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.FiveOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SixOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Diamonds: 7, 6, 5, 4, 3") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.TreyOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.FourOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.FiveOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SixOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SevenOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Diamonds: 8, 7, 6, 5, 4") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.FourOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.FiveOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SixOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SevenOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.EightOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Diamonds: 9, 8, 7, 6, 5") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.FiveOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SixOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SevenOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.EightOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NineOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Diamonds: 10, 9, 8, 7, 6") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.SixOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SevenOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.EightOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NineOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.TenOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Diamonds: Jack, 10, 9, 8, 7") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.SevenOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.EightOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NineOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.TenOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.JackOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Diamonds: Queen, Jack, 10, 9, 8") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.EightOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NineOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.TenOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.JackOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.QueenOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Diamonds: King, Queen, Jack, 10, 9") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.NineOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.TenOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.JackOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.QueenOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.KingOfDiamonds) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + + // Straight flush of hearts + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Hearts: 6, 5, 4, 3, 2") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.DeuceOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.TreyOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.FourOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.FiveOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SixOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Hearts: 7, 6, 5, 4, 3") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.TreyOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.FourOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.FiveOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SixOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SevenOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Hearts: 8, 7, 6, 5, 4") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.FourOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.FiveOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SixOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SevenOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.EightOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Hearts: 9, 8, 7, 6, 5") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.FiveOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SixOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SevenOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.EightOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NineOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Hearts: 10, 9, 8, 7, 6") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.SixOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SevenOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.EightOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NineOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.TenOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Hearts: Jack, 10, 9, 8, 7") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.SevenOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.EightOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NineOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.TenOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.JackOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Hearts: Queen, Jack, 10, 9, 8") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.EightOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NineOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.TenOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.JackOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.QueenOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Hearts: King, Queen, Jack, 10, 9") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.NineOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.TenOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.JackOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.QueenOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.KingOfHearts) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + + // Straight flush of spades + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Spades: 6, 5, 4, 3, 2") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.DeuceOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.TreyOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.FourOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.FiveOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SixOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Spades: 7, 6, 5, 4, 3") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.TreyOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.FourOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.FiveOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SixOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SevenOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Spades: 8, 7, 6, 5, 4") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.FourOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.FiveOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SixOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SevenOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.EightOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Spades: 9, 8, 7, 6, 5") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.FiveOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SixOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SevenOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.EightOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NineOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Spades: 10, 9, 8, 7, 6") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.SixOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.SevenOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.EightOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NineOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.TenOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Spades: Jack, 10, 9, 8, 7") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.SevenOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.EightOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NineOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.TenOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.JackOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Spades: Queen, Jack, 10, 9, 8") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.EightOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.NineOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.TenOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.JackOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.QueenOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Straight flush of Spades: King, Queen, Jack, 10, 9") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Straight Flush" }) + .WithCondition(x => + x.AsComposed() + .WithLogicalOperator(LogicalOperators.And) + .AddCondition(c => + c.AsValued(ConditionTypes.NineOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.TenOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.JackOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.QueenOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .AddCondition(c => + c.AsValued(ConditionTypes.KingOfSpades) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(true) + .Build() + ) + .Build() + ) + .Build().Rule, + }; + } + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.ThreeOfAKind.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.ThreeOfAKind.cs new file mode 100644 index 00000000..3e01090f --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.ThreeOfAKind.cs @@ -0,0 +1,163 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 +{ + using Rules.Framework.Builder; + using Rules.Framework.Core; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + internal partial class Benchmark3Data + { + private IEnumerable> GetThreeOfAKindRules() + { + return new[] + { + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Three Of A Kind Deuces") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfDeuces) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(3) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Three Of A Kind Treys") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfTreys) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(3) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Three Of A Kind Fours") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfFours) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(3) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Three Of A Kind Fives") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfFives) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(3) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Three Of A Kind Sixes") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfSixes) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(3) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Three Of A Kind Sevens") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfSevens) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(3) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Three Of A Kind Eights") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfEigths) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(3) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Three Of A Kind Nines") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfNines) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(3) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Three Of A Kind Tens") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfTens) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(3) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Three Of A Kind Jacks") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfJacks) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(3) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Three Of A Kind Queens") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfQueens) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(3) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Three Of A Kind Kings") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfKings) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(3) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - Three Of A Kind Aces") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "Three Of A Kind" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfAces) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(3) + .Build()) + .Build().Rule, + }; + } + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.cs new file mode 100644 index 00000000..bc09c2eb --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.cs @@ -0,0 +1,55 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 +{ + using Rules.Framework; + using Rules.Framework.Builder; + using Rules.Framework.Core; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + internal partial class Benchmark3Data : IBenchmarkData + { + public IEnumerable> Conditions => new[] + { + new Condition { Type = ConditionTypes.NumberOfKings, Value = 1 }, + new Condition { Type = ConditionTypes.NumberOfQueens, Value = 1 }, + new Condition { Type = ConditionTypes.NumberOfJacks, Value = 1 }, + new Condition { Type = ConditionTypes.NumberOfTens, Value = 1 }, + new Condition { Type = ConditionTypes.NumberOfNines, Value = 1 }, + new Condition { Type = ConditionTypes.KingOfClubs, Value = true }, + new Condition { Type = ConditionTypes.QueenOfDiamonds, Value = true }, + new Condition { Type = ConditionTypes.JackOfClubs, Value = true }, + new Condition { Type = ConditionTypes.TenOfHearts, Value = true }, + new Condition { Type = ConditionTypes.NineOfSpades, Value = true }, + }; + + public DateTime MatchDate => DateTime.Parse("2022-12-01"); + + public IEnumerable> Rules => this.GetRules(); + + private IEnumerable> GetRules() + { + // Does not consider the double pairs and full house combinations, as they would imply a combinatorial explosion. + // For the purpose of the benchmark, scenario already simulates a high number of rules. + var highCards = this.GetHighCardsRules(); + + var pairs = this.GetPairsRules(); + + var threeOfAKind = this.GetThreeOfAKindRules(); + + var straights = this.GetStraightRules(); + + var flushs = this.GetFlushRules(); + + var fourOfAKind = this.GetFourOfAKindRules(); + + var straightFlushs = this.GetStraightFlushRules(); + + var royalFlushs = this.GetRoyalFlushRules(); + + return highCards.Concat(pairs).Concat(threeOfAKind).Concat(straights).Concat(flushs).Concat(fourOfAKind).Concat(straightFlushs).Concat(royalFlushs); + } + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/CardPokerScore.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/CardPokerScore.cs new file mode 100644 index 00000000..1a747429 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/CardPokerScore.cs @@ -0,0 +1,15 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + internal class SingleCombinationPokerScore + { + public int BasePoints { get; set; } + + public string? Combination { get; set; } + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/ConditionTypes.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/ConditionTypes.cs new file mode 100644 index 00000000..c3ca263f --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/ConditionTypes.cs @@ -0,0 +1,75 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 +{ + internal enum ConditionTypes + { + DeuceOfClubs = 1, + TreyOfClubs = 2, + FourOfClubs = 3, + FiveOfClubs = 4, + SixOfClubs = 5, + SevenOfClubs = 6, + EightOfClubs = 7, + NineOfClubs = 8, + TenOfClubs = 9, + JackOfClubs = 10, + QueenOfClubs = 11, + KingOfClubs = 12, + AceOfClubs = 13, + DeuceOfDiamonds = 14, + TreyOfDiamonds = 15, + FourOfDiamonds = 16, + FiveOfDiamonds = 17, + SixOfDiamonds = 18, + SevenOfDiamonds = 19, + EightOfDiamonds = 20, + NineOfDiamonds = 21, + TenOfDiamonds = 22, + JackOfDiamonds = 23, + QueenOfDiamonds = 24, + KingOfDiamonds = 25, + AceOfDiamonds = 26, + DeuceOfHearts = 27, + TreyOfHearts = 28, + FourOfHearts = 29, + FiveOfHearts = 30, + SixOfHearts = 31, + SevenOfHearts = 32, + EightOfHearts = 33, + NineOfHearts = 34, + TenOfHearts = 35, + JackOfHearts = 36, + QueenOfHearts = 37, + KingOfHearts = 38, + AceOfHearts = 39, + DeuceOfSpades = 40, + TreyOfSpades = 41, + FourOfSpades = 42, + FiveOfSpades = 43, + SixOfSpades = 44, + SevenOfSpades = 45, + EightOfSpades = 46, + NineOfSpades = 47, + TenOfSpades = 48, + JackOfSpades = 49, + QueenOfSpades = 50, + KingOfSpades = 51, + AceOfSpades = 52, + NumberOfAces = 53, + NumberOfDeuces = 54, + NumberOfTreys = 55, + NumberOfFours = 56, + NumberOfFives = 57, + NumberOfSixes = 58, + NumberOfSevens = 59, + NumberOfEigths = 60, + NumberOfNines = 61, + NumberOfTens = 62, + NumberOfJacks = 63, + NumberOfQueens = 64, + NumberOfKings = 65, + NumberOfClubs = 66, + NumberOfDiamonds = 67, + NumberOfHearts = 68, + NumberOfSpades = 69 + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/ContentTypes.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/ContentTypes.cs new file mode 100644 index 00000000..b65baee3 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/ContentTypes.cs @@ -0,0 +1,7 @@ +namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 +{ + internal enum ContentTypes + { + TexasHoldemPokerSingleCombinations = 1 + } +} diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/IBenchmark.cs b/tests/Rules.Framework.BenchmarkTests/Tests/IBenchmark.cs new file mode 100644 index 00000000..12c6ea7d --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/IBenchmark.cs @@ -0,0 +1,11 @@ +namespace Rules.Framework.BenchmarkTests.Tests +{ + using System.Threading.Tasks; + + internal interface IBenchmark + { + Task RunAsync(); + Task SetUpAsync(); + Task TearDownAsync(); + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/IBenchmarkData.cs b/tests/Rules.Framework.BenchmarkTests/Tests/IBenchmarkData.cs new file mode 100644 index 00000000..9d42cbc7 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Tests/IBenchmarkData.cs @@ -0,0 +1,15 @@ +namespace Rules.Framework.BenchmarkTests.Tests +{ + using Rules.Framework; + using Rules.Framework.BenchmarkTests.Tests.Benchmark1; + using Rules.Framework.Core; + using System; + using System.Collections.Generic; + + internal interface IBenchmarkData + { + IEnumerable> Conditions { get; } + DateTime MatchDate { get; } + IEnumerable> Rules { get; } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Rules.Framework.Tests.csproj b/tests/Rules.Framework.Tests/Rules.Framework.Tests.csproj index 4b3fc947..17409342 100644 --- a/tests/Rules.Framework.Tests/Rules.Framework.Tests.csproj +++ b/tests/Rules.Framework.Tests/Rules.Framework.Tests.csproj @@ -8,19 +8,15 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive From 6cfe3b43da3355e77a733dea78114824e788e11e Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Wed, 19 Oct 2022 22:46:06 +0100 Subject: [PATCH 03/54] feat: add compilation functionality for value condition nodes --- .../Builder/ConfiguredRulesEngineBuilder.cs | 29 +++- .../Core/ConditionNodeProperties.cs | 21 +++ .../ConditionNodes/ComposedConditionNode.cs | 9 +- .../Core/ConditionNodes/ValueConditionNode.cs | 22 ++- .../ValueConditionNodeTemplate.cs | 7 + src/Rules.Framework/Core/IConditionNode.cs | 7 + .../{ => Classic}/ConditionsEvalEngine.cs | 3 +- .../DeferredEval.cs | 4 +- .../IDeferredEval.cs | 2 +- ...InsensitiveEndsWithOperatorEvalStrategy.cs | 2 +- ...sensitiveStartsWithOperatorEvalStrategy.cs | 2 +- .../ContainsOperatorEvalStrategy.cs | 2 +- .../ConditionEvalDispatchProvider.cs | 25 +++- .../ConditionEvalDispatcherBase.cs | 3 +- .../IConditionEvalDispatchProvider.cs | 2 +- .../Dispatchers/IConditionEvalDispatcher.cs | 2 +- .../ManyToManyConditionEvalDispatcher.cs | 3 +- .../ManyToOneConditionEvalDispatcher.cs | 3 +- .../OneToManyConditionEvalDispatcher.cs | 3 +- .../OneToOneConditionEvalDispatcher.cs | 3 +- .../EndsWithOperatorEvalStrategy.cs | 2 +- .../EqualOperatorEvalStrategy.cs | 2 +- .../GreaterThanOperatorEvalStrategy.cs | 2 +- .../GreaterThanOrEqualOperatorEvalStrategy.cs | 2 +- .../IManyToManyOperatorEvalStrategy.cs | 2 +- .../IManyToOneOperatorEvalStrategy.cs | 2 +- .../IOneToManyOperatorEvalStrategy.cs | 2 +- .../IOneToOneOperatorEvalStrategy.cs | 2 +- .../IOperatorEvalStrategyFactory.cs | 2 +- .../ValueEvaluation/InOperatorEvalStrategy.cs | 2 +- .../LesserThanOperatorEvalStrategy.cs | 2 +- .../LesserThanOrEqualOperatorEvalStrategy.cs | 2 +- .../NotContainsOperatorEvalStrategy.cs | 2 +- .../NotEndsWithOperatorEvalStrategy.cs | 2 +- .../NotEqualOperatorEvalStrategy.cs | 2 +- .../NotStartsWithOperatorEvalStrategy.cs | 2 +- .../OperatorEvalStrategyFactory.cs | 2 +- .../StartsWithOperatorEvalStrategy.cs | 2 +- .../Compiled/CompiledConditionsEvalEngine.cs | 140 ++++++++++++++++++ ...sWithOneToOneConditionExpressionBuilder.cs | 15 ++ ...sWithOneToOneConditionExpressionBuilder.cs | 15 ++ .../ConditionExpressionBuilderProvider.cs | 45 ++++++ ...tainsOneToOneConditionExpressionBuilder.cs | 21 +++ ...sWithOneToOneConditionExpressionBuilder.cs | 15 ++ ...EqualOneToOneConditionExpressionBuilder.cs | 19 +++ ...rThanOneToOneConditionExpressionBuilder.cs | 18 +++ ...EqualOneToOneConditionExpressionBuilder.cs | 18 +++ .../IConditionExpressionBuilder.cs | 14 ++ .../IConditionExpressionBuilderProvider.cs | 9 ++ .../InOneToManyConditionExpressionBuilder.cs | 22 +++ ...rThanOneToOneConditionExpressionBuilder.cs | 18 +++ ...EqualOneToOneConditionExpressionBuilder.cs | 18 +++ ...tainsOneToOneConditionExpressionBuilder.cs | 21 +++ ...sWithOneToOneConditionExpressionBuilder.cs | 38 +++++ ...EqualOneToOneConditionExpressionBuilder.cs | 18 +++ ...sWithOneToOneConditionExpressionBuilder.cs | 38 +++++ ...sWithOneToOneConditionExpressionBuilder.cs | 15 ++ .../Compiled/ConditionsTreeCompiler.cs | 63 ++++++++ .../Compiled/IConditionsTreeCompiler.cs | 12 ++ .../Compiled/IValueConditionNodeCompiler.cs | 15 ++ .../IValueConditionNodeCompilerProvider.cs | 12 ++ .../OneToManyValueConditionNodeCompiler.cs | 43 ++++++ .../OneToOneValueConditionNodeCompiler.cs | 39 +++++ .../ValueConditionNodeCompilerBase.cs | 102 +++++++++++++ .../ValueConditionNodeCompilerProvider.cs | 39 +++++ .../Dispatchers => }/DataTypeConfiguration.cs | 2 +- .../DataTypesConfigurationProvider.cs | 2 +- .../IDataTypesConfigurationProvider.cs | 2 +- .../Evaluation/MultiplicityEvaluator.cs | 1 + src/Rules.Framework/RulesEngineOptions.cs | 2 + .../Tests/Benchmark1/Benchmark1.cs | 8 + .../Tests/Benchmark2/Benchmark2.cs | 8 + .../Tests/Benchmark3/Benchmark3.cs | 8 + .../Tests/Scenario1/BodyMassIndexTests.cs | 40 ++++- .../Scenario2/CarInsuranceAdvisorTests.cs | 35 +++-- .../BuildingSecuritySystemControlTests.cs | 30 +++- .../Tests/Scenario4/DiscountCampaignTests.cs | 60 ++++++-- .../ConditionsEvalEngineTests.cs | 4 +- .../DeferredEvalTests.cs | 6 +- ...sitiveEndsWithOperatorEvalStrategyTests.cs | 4 +- ...tiveStartsWithOperatorEvalStrategyTests.cs | 4 +- .../ContainsOperatorEvalStrategyTests.cs | 4 +- .../ManyToManyConditionEvalDispatcherTests.cs | 5 +- .../ManyToOneConditionEvalDispatcherTests.cs | 5 +- .../OneToManyConditionEvalDispatcherTests.cs | 5 +- .../OneToOneConditionEvalDispatcherTests.cs | 5 +- .../EndsWithOperatorEvalStrategyTests.cs | 4 +- .../EqualOperatorEvalStrategyTests.cs | 4 +- .../GreaterThanOperatorEvalStrategyTests.cs | 4 +- ...terThanOrEqualOperatorEvalStrategyTests.cs | 4 +- .../InOperatorEvalStrategyTests.cs | 4 +- .../LesserThanOperatorEvalStrategyTests.cs | 4 +- ...serThanOrEqualOperatorEvalStrategyTests.cs | 4 +- .../NotEndsWithOperatorEvalStrategyTests.cs | 2 +- .../NotEqualOperatorEvalStrategyTests.cs | 4 +- .../NotStartsWithOperatorEvalStrategyTests.cs | 2 +- .../OperatorEvalStrategyFactoryTests.cs | 4 +- .../StartsWithOperatorEvalStrategyTests.cs | 4 +- .../Evaluation/StubConditionNode.cs | 3 + .../Extensions/GenericRuleExtensionsTests.cs | 3 +- 100 files changed, 1192 insertions(+), 130 deletions(-) create mode 100644 src/Rules.Framework/Core/ConditionNodeProperties.cs rename src/Rules.Framework/Evaluation/{ => Classic}/ConditionsEvalEngine.cs (97%) rename src/Rules.Framework/Evaluation/{ValueEvaluation => Classic}/DeferredEval.cs (97%) rename src/Rules.Framework/Evaluation/{ValueEvaluation => Classic}/IDeferredEval.cs (86%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategy.cs (93%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategy.cs (93%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/ContainsOperatorEvalStrategy.cs (91%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs (68%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs (93%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/Dispatchers/IConditionEvalDispatchProvider.cs (74%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/Dispatchers/IConditionEvalDispatcher.cs (73%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs (93%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs (92%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs (92%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs (91%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/EndsWithOperatorEvalStrategy.cs (92%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/EqualOperatorEvalStrategy.cs (90%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/GreaterThanOperatorEvalStrategy.cs (90%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategy.cs (90%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/IManyToManyOperatorEvalStrategy.cs (76%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/IManyToOneOperatorEvalStrategy.cs (74%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/IOneToManyOperatorEvalStrategy.cs (74%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/IOneToOneOperatorEvalStrategy.cs (67%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/IOperatorEvalStrategyFactory.cs (88%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/InOperatorEvalStrategy.cs (82%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/LesserThanOperatorEvalStrategy.cs (90%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategy.cs (90%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/NotContainsOperatorEvalStrategy.cs (91%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/NotEndsWithOperatorEvalStrategy.cs (91%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/NotEqualOperatorEvalStrategy.cs (90%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/NotStartsWithOperatorEvalStrategy.cs (91%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/OperatorEvalStrategyFactory.cs (98%) rename src/Rules.Framework/Evaluation/{ => Classic}/ValueEvaluation/StartsWithOperatorEvalStrategy.cs (92%) create mode 100644 src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProvider.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilderProvider.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionsTreeCompiler.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/IConditionsTreeCompiler.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeCompiler.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeCompilerProvider.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/OneToManyValueConditionNodeCompiler.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/OneToOneValueConditionNodeCompiler.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerBase.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerProvider.cs rename src/Rules.Framework/Evaluation/{ValueEvaluation/Dispatchers => }/DataTypeConfiguration.cs (91%) rename src/Rules.Framework/Evaluation/{ValueEvaluation/Dispatchers => }/DataTypesConfigurationProvider.cs (97%) rename src/Rules.Framework/Evaluation/{ValueEvaluation/Dispatchers => }/IDataTypesConfigurationProvider.cs (73%) rename tests/Rules.Framework.Tests/Evaluation/{ => Classic}/ConditionsEvalEngineTests.cs (99%) rename tests/Rules.Framework.Tests/Evaluation/{ValueEvaluation => Classic}/DeferredEvalTests.cs (98%) rename tests/Rules.Framework.Tests/Evaluation/{ => Classic}/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategyTests.cs (94%) rename tests/Rules.Framework.Tests/Evaluation/{ => Classic}/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategyTests.cs (94%) rename tests/Rules.Framework.Tests/Evaluation/{ => Classic}/ValueEvaluation/ContainsOperatorEvalStrategyTests.cs (93%) rename tests/Rules.Framework.Tests/Evaluation/{ => Classic}/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcherTests.cs (97%) rename tests/Rules.Framework.Tests/Evaluation/{ => Classic}/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcherTests.cs (95%) rename tests/Rules.Framework.Tests/Evaluation/{ => Classic}/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcherTests.cs (95%) rename tests/Rules.Framework.Tests/Evaluation/{ => Classic}/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcherTests.cs (96%) rename tests/Rules.Framework.Tests/Evaluation/{ => Classic}/ValueEvaluation/EndsWithOperatorEvalStrategyTests.cs (94%) rename tests/Rules.Framework.Tests/Evaluation/{ => Classic}/ValueEvaluation/EqualOperatorEvalStrategyTests.cs (93%) rename tests/Rules.Framework.Tests/Evaluation/{ => Classic}/ValueEvaluation/GreaterThanOperatorEvalStrategyTests.cs (94%) rename tests/Rules.Framework.Tests/Evaluation/{ => Classic}/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategyTests.cs (94%) rename tests/Rules.Framework.Tests/Evaluation/{ => Classic}/ValueEvaluation/InOperatorEvalStrategyTests.cs (90%) rename tests/Rules.Framework.Tests/Evaluation/{ => Classic}/ValueEvaluation/LesserThanOperatorEvalStrategyTests.cs (94%) rename tests/Rules.Framework.Tests/Evaluation/{ => Classic}/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategyTests.cs (94%) rename tests/Rules.Framework.Tests/Evaluation/{ => Classic}/ValueEvaluation/NotEndsWithOperatorEvalStrategyTests.cs (96%) rename tests/Rules.Framework.Tests/Evaluation/{ => Classic}/ValueEvaluation/NotEqualOperatorEvalStrategyTests.cs (93%) rename tests/Rules.Framework.Tests/Evaluation/{ => Classic}/ValueEvaluation/NotStartsWithOperatorEvalStrategyTests.cs (96%) rename tests/Rules.Framework.Tests/Evaluation/{ => Classic}/ValueEvaluation/OperatorEvalStrategyFactoryTests.cs (98%) rename tests/Rules.Framework.Tests/Evaluation/{ => Classic}/ValueEvaluation/StartsWithOperatorEvalStrategyTests.cs (94%) diff --git a/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs b/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs index 6d0b241d..de1487fa 100644 --- a/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs +++ b/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs @@ -4,8 +4,11 @@ namespace Rules.Framework.Builder using System.Collections.Generic; using System.Linq; using Rules.Framework.Evaluation; - using Rules.Framework.Evaluation.ValueEvaluation; - using Rules.Framework.Evaluation.ValueEvaluation.Dispatchers; + using Rules.Framework.Evaluation.Classic; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers; + using Rules.Framework.Evaluation.Compiled; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Source; using Rules.Framework.Validation; @@ -23,16 +26,26 @@ public ConfiguredRulesEngineBuilder(IRulesDataSource Build() { var rulesSourceMiddlewares = new List>(); - var operatorEvalStrategyFactory = new OperatorEvalStrategyFactory(); var dataTypesConfigurationProvider = new DataTypesConfigurationProvider(this.rulesEngineOptions); var multiplicityEvaluator = new MultiplicityEvaluator(); - var conditionsTreeAnalyzer = new ConditionsTreeAnalyzer(); - var conditionEvalDispatchProvider = new ConditionEvalDispatchProvider(operatorEvalStrategyFactory, multiplicityEvaluator, dataTypesConfigurationProvider); + IConditionsEvalEngine conditionsEvalEngine; - var deferredEval = new DeferredEval(conditionEvalDispatchProvider, this.rulesEngineOptions); - - var conditionsEvalEngine = new ConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); + if (this.rulesEngineOptions.EnableCompilation) + { + var conditionExpressionBuilderProvider = new ConditionExpressionBuilderProvider(); + var valueConditionNodeCompilerProvider = new ValueConditionNodeCompilerProvider(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); + var conditionsTreeCompiler = new ConditionsTreeCompiler(valueConditionNodeCompilerProvider); + conditionsEvalEngine = new CompiledConditionsEvalEngine(conditionsTreeCompiler, multiplicityEvaluator, this.rulesEngineOptions); + } + else + { + var operatorEvalStrategyFactory = new OperatorEvalStrategyFactory(); + var conditionEvalDispatchProvider = new ConditionEvalDispatchProvider(operatorEvalStrategyFactory, multiplicityEvaluator, dataTypesConfigurationProvider); + var deferredEval = new DeferredEval(conditionEvalDispatchProvider, this.rulesEngineOptions); + var conditionsTreeAnalyzer = new ConditionsTreeAnalyzer(); + conditionsEvalEngine = new ConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); + } var conditionTypeExtractor = new ConditionTypeExtractor(); diff --git a/src/Rules.Framework/Core/ConditionNodeProperties.cs b/src/Rules.Framework/Core/ConditionNodeProperties.cs new file mode 100644 index 00000000..10502b71 --- /dev/null +++ b/src/Rules.Framework/Core/ConditionNodeProperties.cs @@ -0,0 +1,21 @@ +namespace Rules.Framework.Core +{ + using System; + using System.Collections.Generic; + using System.Text; + + internal static class ConditionNodeProperties + { + public static string GetCompiledDelegateKey(string multiplicity) + { + if (string.IsNullOrWhiteSpace(multiplicity)) + { + throw new ArgumentException($"'{nameof(multiplicity)}' cannot be null or whitespace.", nameof(multiplicity)); + } + + return $"_compiled_{multiplicity}"; + } + + public static string CompiledFlagKey => "_compiled"; + } +} diff --git a/src/Rules.Framework/Core/ConditionNodes/ComposedConditionNode.cs b/src/Rules.Framework/Core/ConditionNodes/ComposedConditionNode.cs index b2898b42..b6fe89ec 100644 --- a/src/Rules.Framework/Core/ConditionNodes/ComposedConditionNode.cs +++ b/src/Rules.Framework/Core/ConditionNodes/ComposedConditionNode.cs @@ -1,6 +1,7 @@ namespace Rules.Framework.Core.ConditionNodes { using System.Collections.Generic; + using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; @@ -20,6 +21,7 @@ public ComposedConditionNode(LogicalOperators logicalOperator, IEnumerable(); } /// @@ -32,11 +34,16 @@ public ComposedConditionNode(LogicalOperators logicalOperator, IEnumerable public LogicalOperators LogicalOperator { get; } + /// + /// Gets the condition node properties. + /// + public IDictionary Properties { get; } + /// /// Clones the condition node into a different instance. /// /// public IConditionNode Clone() - => new ComposedConditionNode(this.LogicalOperator, this.ChildConditionNodes?.Select(cn => cn.Clone())); + => new ComposedConditionNode(this.LogicalOperator, this.ChildConditionNodes?.Select(cn => cn.Clone()).ToList().AsReadOnly()); } } \ No newline at end of file diff --git a/src/Rules.Framework/Core/ConditionNodes/ValueConditionNode.cs b/src/Rules.Framework/Core/ConditionNodes/ValueConditionNode.cs index caab4386..85ca4051 100644 --- a/src/Rules.Framework/Core/ConditionNodes/ValueConditionNode.cs +++ b/src/Rules.Framework/Core/ConditionNodes/ValueConditionNode.cs @@ -1,5 +1,6 @@ namespace Rules.Framework.Core.ConditionNodes { + using System.Collections.Generic; using System.Diagnostics; /// @@ -20,11 +21,25 @@ public class ValueConditionNode : IValueConditionNodeThe operator. /// The operand. public ValueConditionNode(DataTypes dataType, TConditionType conditionType, Operators @operator, object operand) + : this(dataType, conditionType, @operator, operand, new Dictionary()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Type of the data. + /// Type of the condition. + /// The operator. + /// The operand. + /// The properties. + public ValueConditionNode(DataTypes dataType, TConditionType conditionType, Operators @operator, object operand, IDictionary properties) { this.ConditionType = conditionType; this.DataType = dataType; this.Operand = operand; this.Operator = @operator; + this.Properties = properties; } /// @@ -53,11 +68,16 @@ public ValueConditionNode(DataTypes dataType, TConditionType conditionType, Oper /// public Operators Operator { get; } + /// + /// Gets the condition node properties. + /// + public IDictionary Properties { get; } + /// /// Clones the condition node into a different instance. /// /// public IConditionNode Clone() - => new ValueConditionNode(this.DataType, this.ConditionType, this.Operator, this.Operand); + => new ValueConditionNode(this.DataType, this.ConditionType, this.Operator, this.Operand, new Dictionary(this.Properties)); } } \ No newline at end of file diff --git a/src/Rules.Framework/Core/ConditionNodes/ValueConditionNodeTemplate.cs b/src/Rules.Framework/Core/ConditionNodes/ValueConditionNodeTemplate.cs index b7f8cfbb..0d6838ba 100644 --- a/src/Rules.Framework/Core/ConditionNodes/ValueConditionNodeTemplate.cs +++ b/src/Rules.Framework/Core/ConditionNodes/ValueConditionNodeTemplate.cs @@ -1,6 +1,7 @@ namespace Rules.Framework.Core.ConditionNodes { using System; + using System.Collections.Generic; /// /// Defines the template implementation for a condition node based on a value comparison. @@ -24,6 +25,7 @@ protected ValueConditionNodeTemplate(TConditionType conditionType, Operators @op this.ConditionType = conditionType; this.Operand = operand; this.Operator = @operator; + this.Properties = new Dictionary(); } /// @@ -51,6 +53,11 @@ protected ValueConditionNodeTemplate(TConditionType conditionType, Operators @op /// public Operators Operator { get; } + /// + /// Gets the condition node properties. + /// + public IDictionary Properties { get; } + /// /// Clones the condition node into a different instance. /// diff --git a/src/Rules.Framework/Core/IConditionNode.cs b/src/Rules.Framework/Core/IConditionNode.cs index 8303810c..d0c12762 100644 --- a/src/Rules.Framework/Core/IConditionNode.cs +++ b/src/Rules.Framework/Core/IConditionNode.cs @@ -1,5 +1,7 @@ namespace Rules.Framework.Core { + using System.Collections.Generic; + /// /// Defines the interface contract for a rule's condition node. /// @@ -11,6 +13,11 @@ public interface IConditionNode /// LogicalOperators LogicalOperator { get; } + /// + /// Gets the condition node properties. + /// + IDictionary Properties { get; } + /// /// Clones the condition node into a different instance. /// diff --git a/src/Rules.Framework/Evaluation/ConditionsEvalEngine.cs b/src/Rules.Framework/Evaluation/Classic/ConditionsEvalEngine.cs similarity index 97% rename from src/Rules.Framework/Evaluation/ConditionsEvalEngine.cs rename to src/Rules.Framework/Evaluation/Classic/ConditionsEvalEngine.cs index 819b1ea4..fa4897d6 100644 --- a/src/Rules.Framework/Evaluation/ConditionsEvalEngine.cs +++ b/src/Rules.Framework/Evaluation/Classic/ConditionsEvalEngine.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation +namespace Rules.Framework.Evaluation.Classic { using System; using System.Collections.Generic; @@ -6,7 +6,6 @@ namespace Rules.Framework.Evaluation using Rules.Framework.Core; using Rules.Framework.Core.ConditionNodes; using Rules.Framework.Evaluation.Specification; - using Rules.Framework.Evaluation.ValueEvaluation; internal sealed class ConditionsEvalEngine : IConditionsEvalEngine { diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/DeferredEval.cs b/src/Rules.Framework/Evaluation/Classic/DeferredEval.cs similarity index 97% rename from src/Rules.Framework/Evaluation/ValueEvaluation/DeferredEval.cs rename to src/Rules.Framework/Evaluation/Classic/DeferredEval.cs index 785cc88c..995c70f5 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/DeferredEval.cs +++ b/src/Rules.Framework/Evaluation/Classic/DeferredEval.cs @@ -1,9 +1,9 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic { using System; using System.Collections.Generic; using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Evaluation.ValueEvaluation.Dispatchers; + using Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers; internal sealed class DeferredEval : IDeferredEval { diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/IDeferredEval.cs b/src/Rules.Framework/Evaluation/Classic/IDeferredEval.cs similarity index 86% rename from src/Rules.Framework/Evaluation/ValueEvaluation/IDeferredEval.cs rename to src/Rules.Framework/Evaluation/Classic/IDeferredEval.cs index 47f08da0..5d2aaa58 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/IDeferredEval.cs +++ b/src/Rules.Framework/Evaluation/Classic/IDeferredEval.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic { using System; using System.Collections.Generic; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategy.cs similarity index 93% rename from src/Rules.Framework/Evaluation/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategy.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategy.cs index 47f2eabb..55180c63 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategy.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { using System; using System.Globalization; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategy.cs similarity index 93% rename from src/Rules.Framework/Evaluation/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategy.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategy.cs index 71321311..c2174e05 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategy.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { using System; using System.Globalization; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/ContainsOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/ContainsOperatorEvalStrategy.cs similarity index 91% rename from src/Rules.Framework/Evaluation/ValueEvaluation/ContainsOperatorEvalStrategy.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/ContainsOperatorEvalStrategy.cs index c3bf376f..7f920a9d 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/ContainsOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/ContainsOperatorEvalStrategy.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { using System; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs similarity index 68% rename from src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs index c9043706..675ff5b7 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs @@ -1,10 +1,13 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation.Dispatchers +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers { using System; + using System.Collections; using System.Collections.Generic; + using System.Linq; using Rules.Framework.Core; + using Rules.Framework.Evaluation; - internal sealed class ConditionEvalDispatchProvider : IConditionEvalDispatchProvider + internal class ConditionEvalDispatchProvider : IConditionEvalDispatchProvider { private readonly Dictionary dispatchers; private readonly IMultiplicityEvaluator multiplicityEvaluator; @@ -14,12 +17,12 @@ public ConditionEvalDispatchProvider( IMultiplicityEvaluator multiplicityEvaluator, IDataTypesConfigurationProvider dataTypesConfigurationProvider) { - this.dispatchers = new Dictionary(StringComparer.Ordinal) + this.dispatchers = new Dictionary { { Multiplicities.OneToOne, new OneToOneConditionEvalDispatcher(operatorEvalStrategyFactory, dataTypesConfigurationProvider) }, { Multiplicities.OneToMany, new OneToManyConditionEvalDispatcher(operatorEvalStrategyFactory, dataTypesConfigurationProvider) }, { Multiplicities.ManyToOne, new ManyToOneConditionEvalDispatcher(operatorEvalStrategyFactory, dataTypesConfigurationProvider) }, - { Multiplicities.ManyToMany, new ManyToManyConditionEvalDispatcher(operatorEvalStrategyFactory, dataTypesConfigurationProvider) }, + { Multiplicities.ManyToMany, new ManyToManyConditionEvalDispatcher(operatorEvalStrategyFactory, dataTypesConfigurationProvider) } }; this.multiplicityEvaluator = multiplicityEvaluator; } @@ -28,12 +31,22 @@ public IConditionEvalDispatcher GetEvalDispatcher(object leftOperand, Operators { string multiplicity = this.multiplicityEvaluator.EvaluateMultiplicity(leftOperand, @operator, rightOperand); - ThrowIfUnsupportedOperandsAndOperatorCombination($"{multiplicity}-{@operator}"); + this.ThrowIfUnsupportedOperandsAndOperatorCombination($"{multiplicity}-{@operator}"); return this.dispatchers[multiplicity]; } - private static void ThrowIfUnsupportedOperandsAndOperatorCombination(string combination) + private bool OperatorSupportsOneMultiplicityLeftOperand(Operators @operator) + { + if (OperatorsMetadata.AllByOperator.TryGetValue(@operator, out var operatorMetadata)) + { + return operatorMetadata.SupportedMultiplicities.Any(x => x.Contains("one-to")); + } + + return false; + } + + private void ThrowIfUnsupportedOperandsAndOperatorCombination(string combination) { if (!OperatorsMetadata.AllBySupportedCombination.ContainsKey(combination)) { diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs similarity index 93% rename from src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs index 38630ae1..9170acb1 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation.Dispatchers +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers { using System; using System.Collections; @@ -6,6 +6,7 @@ namespace Rules.Framework.Evaluation.ValueEvaluation.Dispatchers using System.Globalization; using System.Linq; using Rules.Framework.Core; + using Rules.Framework.Evaluation; internal abstract class ConditionEvalDispatcherBase { diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/IConditionEvalDispatchProvider.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/IConditionEvalDispatchProvider.cs similarity index 74% rename from src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/IConditionEvalDispatchProvider.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/IConditionEvalDispatchProvider.cs index bb579d7e..811738d2 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/IConditionEvalDispatchProvider.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/IConditionEvalDispatchProvider.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation.Dispatchers +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers { using Rules.Framework.Core; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/IConditionEvalDispatcher.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/IConditionEvalDispatcher.cs similarity index 73% rename from src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/IConditionEvalDispatcher.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/IConditionEvalDispatcher.cs index 0d36e02a..9d024b66 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/IConditionEvalDispatcher.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/IConditionEvalDispatcher.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation.Dispatchers +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers { using Rules.Framework.Core; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs similarity index 93% rename from src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs index 6f72a1cf..d20ac459 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs @@ -1,8 +1,9 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation.Dispatchers +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers { using System.Collections.Generic; using System.Linq; using Rules.Framework.Core; + using Rules.Framework.Evaluation; internal sealed class ManyToManyConditionEvalDispatcher : ConditionEvalDispatcherBase, IConditionEvalDispatcher { diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs similarity index 92% rename from src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs index df1aef0f..8370c6df 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs @@ -1,8 +1,9 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation.Dispatchers +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers { using System.Collections.Generic; using System.Linq; using Rules.Framework.Core; + using Rules.Framework.Evaluation; internal sealed class ManyToOneConditionEvalDispatcher : ConditionEvalDispatcherBase, IConditionEvalDispatcher { diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs similarity index 92% rename from src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs index 36ac177e..0c856426 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs @@ -1,8 +1,9 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation.Dispatchers +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers { using System.Collections.Generic; using System.Linq; using Rules.Framework.Core; + using Rules.Framework.Evaluation; internal sealed class OneToManyConditionEvalDispatcher : ConditionEvalDispatcherBase, IConditionEvalDispatcher { diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs similarity index 91% rename from src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs index b1607501..df48252a 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs @@ -1,6 +1,7 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation.Dispatchers +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers { using Rules.Framework.Core; + using Rules.Framework.Evaluation; internal sealed class OneToOneConditionEvalDispatcher : ConditionEvalDispatcherBase, IConditionEvalDispatcher { diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/EndsWithOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/EndsWithOperatorEvalStrategy.cs similarity index 92% rename from src/Rules.Framework/Evaluation/ValueEvaluation/EndsWithOperatorEvalStrategy.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/EndsWithOperatorEvalStrategy.cs index 4f4a13c5..3b98b9e6 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/EndsWithOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/EndsWithOperatorEvalStrategy.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { using System; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/EqualOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/EqualOperatorEvalStrategy.cs similarity index 90% rename from src/Rules.Framework/Evaluation/ValueEvaluation/EqualOperatorEvalStrategy.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/EqualOperatorEvalStrategy.cs index 692a1472..45739a77 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/EqualOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/EqualOperatorEvalStrategy.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { using System; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/GreaterThanOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/GreaterThanOperatorEvalStrategy.cs similarity index 90% rename from src/Rules.Framework/Evaluation/ValueEvaluation/GreaterThanOperatorEvalStrategy.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/GreaterThanOperatorEvalStrategy.cs index 73a071c8..b0441d94 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/GreaterThanOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/GreaterThanOperatorEvalStrategy.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { using System; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategy.cs similarity index 90% rename from src/Rules.Framework/Evaluation/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategy.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategy.cs index 84b50a0b..c8878964 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategy.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { using System; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/IManyToManyOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/IManyToManyOperatorEvalStrategy.cs similarity index 76% rename from src/Rules.Framework/Evaluation/ValueEvaluation/IManyToManyOperatorEvalStrategy.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/IManyToManyOperatorEvalStrategy.cs index d7affadc..57eb80d9 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/IManyToManyOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/IManyToManyOperatorEvalStrategy.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { using System.Collections.Generic; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/IManyToOneOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/IManyToOneOperatorEvalStrategy.cs similarity index 74% rename from src/Rules.Framework/Evaluation/ValueEvaluation/IManyToOneOperatorEvalStrategy.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/IManyToOneOperatorEvalStrategy.cs index 9a793515..214f357f 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/IManyToOneOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/IManyToOneOperatorEvalStrategy.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { using System.Collections.Generic; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/IOneToManyOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/IOneToManyOperatorEvalStrategy.cs similarity index 74% rename from src/Rules.Framework/Evaluation/ValueEvaluation/IOneToManyOperatorEvalStrategy.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/IOneToManyOperatorEvalStrategy.cs index 1ab9b433..3935ccb1 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/IOneToManyOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/IOneToManyOperatorEvalStrategy.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { using System.Collections.Generic; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/IOneToOneOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/IOneToOneOperatorEvalStrategy.cs similarity index 67% rename from src/Rules.Framework/Evaluation/ValueEvaluation/IOneToOneOperatorEvalStrategy.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/IOneToOneOperatorEvalStrategy.cs index f9304a3a..98f38624 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/IOneToOneOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/IOneToOneOperatorEvalStrategy.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { internal interface IOneToOneOperatorEvalStrategy { diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/IOperatorEvalStrategyFactory.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/IOperatorEvalStrategyFactory.cs similarity index 88% rename from src/Rules.Framework/Evaluation/ValueEvaluation/IOperatorEvalStrategyFactory.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/IOperatorEvalStrategyFactory.cs index 3361743f..3ee34ff6 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/IOperatorEvalStrategyFactory.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/IOperatorEvalStrategyFactory.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { using Rules.Framework.Core; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/InOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/InOperatorEvalStrategy.cs similarity index 82% rename from src/Rules.Framework/Evaluation/ValueEvaluation/InOperatorEvalStrategy.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/InOperatorEvalStrategy.cs index 4009184c..bb92cbe2 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/InOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/InOperatorEvalStrategy.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { using System.Collections.Generic; using System.Linq; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/LesserThanOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/LesserThanOperatorEvalStrategy.cs similarity index 90% rename from src/Rules.Framework/Evaluation/ValueEvaluation/LesserThanOperatorEvalStrategy.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/LesserThanOperatorEvalStrategy.cs index ae1dded8..3c952be9 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/LesserThanOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/LesserThanOperatorEvalStrategy.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { using System; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategy.cs similarity index 90% rename from src/Rules.Framework/Evaluation/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategy.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategy.cs index c8d52e8c..586851f2 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategy.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { using System; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/NotContainsOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/NotContainsOperatorEvalStrategy.cs similarity index 91% rename from src/Rules.Framework/Evaluation/ValueEvaluation/NotContainsOperatorEvalStrategy.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/NotContainsOperatorEvalStrategy.cs index 072a1bf2..e7e962e9 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/NotContainsOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/NotContainsOperatorEvalStrategy.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { using System; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/NotEndsWithOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/NotEndsWithOperatorEvalStrategy.cs similarity index 91% rename from src/Rules.Framework/Evaluation/ValueEvaluation/NotEndsWithOperatorEvalStrategy.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/NotEndsWithOperatorEvalStrategy.cs index 57f5f36e..93213d08 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/NotEndsWithOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/NotEndsWithOperatorEvalStrategy.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { using System; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/NotEqualOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/NotEqualOperatorEvalStrategy.cs similarity index 90% rename from src/Rules.Framework/Evaluation/ValueEvaluation/NotEqualOperatorEvalStrategy.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/NotEqualOperatorEvalStrategy.cs index f85be09c..a5254d79 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/NotEqualOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/NotEqualOperatorEvalStrategy.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { using System; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/NotStartsWithOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/NotStartsWithOperatorEvalStrategy.cs similarity index 91% rename from src/Rules.Framework/Evaluation/ValueEvaluation/NotStartsWithOperatorEvalStrategy.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/NotStartsWithOperatorEvalStrategy.cs index 3d825c3c..8df0aa01 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/NotStartsWithOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/NotStartsWithOperatorEvalStrategy.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { using System; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/OperatorEvalStrategyFactory.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/OperatorEvalStrategyFactory.cs similarity index 98% rename from src/Rules.Framework/Evaluation/ValueEvaluation/OperatorEvalStrategyFactory.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/OperatorEvalStrategyFactory.cs index a11d62e3..4178e3f1 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/OperatorEvalStrategyFactory.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/OperatorEvalStrategyFactory.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { using System; using System.Collections.Generic; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/StartsWithOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/StartsWithOperatorEvalStrategy.cs similarity index 92% rename from src/Rules.Framework/Evaluation/ValueEvaluation/StartsWithOperatorEvalStrategy.cs rename to src/Rules.Framework/Evaluation/Classic/ValueEvaluation/StartsWithOperatorEvalStrategy.cs index 182326e6..3890ad95 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/StartsWithOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/StartsWithOperatorEvalStrategy.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation +namespace Rules.Framework.Evaluation.Classic.ValueEvaluation { using System; diff --git a/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs b/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs new file mode 100644 index 00000000..b4e1eaf4 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs @@ -0,0 +1,140 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Rules.Framework.Core; + using Rules.Framework.Core.ConditionNodes; + using Rules.Framework.Evaluation.Specification; + + internal class CompiledConditionsEvalEngine : IConditionsEvalEngine + { + private readonly IConditionsTreeCompiler conditionsTreeCompiler; + private readonly IMultiplicityEvaluator multiplicityEvaluator; + private readonly RulesEngineOptions rulesEngineOptions; + + public CompiledConditionsEvalEngine( + IConditionsTreeCompiler conditionsTreeCompiler, + IMultiplicityEvaluator multiplicityEvaluator, + RulesEngineOptions rulesEngineOptions) + { + this.conditionsTreeCompiler = conditionsTreeCompiler; + this.multiplicityEvaluator = multiplicityEvaluator; + this.rulesEngineOptions = rulesEngineOptions; + } + + public bool Eval(IConditionNode conditionNode, IDictionary conditions, EvaluationOptions evaluationOptions) + { + if (evaluationOptions.ExcludeRulesWithoutSearchConditions && !AreAllSearchConditionsPresent(conditionNode, conditions)) + { + return false; + } + + if (!conditionNode.Properties.TryGetValue(ConditionNodeProperties.CompiledFlagKey, out var compiledFlag) || !(bool)compiledFlag) + { + this.conditionsTreeCompiler.Compile(conditionNode); + } + + ISpecification> specification = this.BuildSpecification(conditionNode, conditions, evaluationOptions.MatchMode); + + return specification.IsSatisfiedBy(conditions); + } + + private static bool AreAllSearchConditionsPresent(IConditionNode conditionNode, IDictionary conditions) + { + // Conditions checklist is a mere control construct to avoid a full sweep of the + // condition nodes tree when we already found all conditions. + IDictionary conditionsChecklist = new Dictionary(conditions.ToDictionary(ks => ks.Key, vs => false)); + + return VisitConditionNode(conditionNode, conditionsChecklist); + } + + private static bool VisitConditionNode(IConditionNode conditionNode, IDictionary conditionsChecklist) + { + switch (conditionNode) + { + case ValueConditionNode valueConditionNode: + if (conditionsChecklist.ContainsKey(valueConditionNode.ConditionType)) + { + conditionsChecklist[valueConditionNode.ConditionType] = true; + } + + return conditionsChecklist.All(kvp => kvp.Value); + + case ComposedConditionNode composedConditionNode: + foreach (IConditionNode childConditionNode in composedConditionNode.ChildConditionNodes) + { + bool allPresentAlready = VisitConditionNode(childConditionNode, conditionsChecklist); + if (allPresentAlready) + { + return true; + } + } + + return false; + + default: + throw new NotSupportedException($"Unsupported condition node: '{conditionNode.GetType().Name}'."); + } + } + + private ISpecification> BuildSpecification( + IConditionNode conditionNode, + IDictionary conditions, + MatchModes matchMode) + { + return conditionNode switch + { + ValueConditionNode valueConditionNode => this.BuildSpecificationForValueNode(valueConditionNode, conditions, matchMode), + ComposedConditionNode composedConditionNode => this.BuildSpecificationForComposedNode(composedConditionNode, conditions, matchMode), + _ => throw new NotSupportedException($"Unsupported condition node: '{conditionNode.GetType().Name}'."), + }; + } + + private ISpecification> BuildSpecificationForComposedNode( + ComposedConditionNode composedConditionNode, + IDictionary conditions, + MatchModes matchMode) + { + IEnumerable>> childConditionNodesSpecifications = composedConditionNode + .ChildConditionNodes + .Select(cn => this.BuildSpecification(cn, conditions, matchMode)); + + return composedConditionNode.LogicalOperator switch + { + LogicalOperators.And => childConditionNodesSpecifications.Aggregate((s1, s2) => s1.And(s2)), + LogicalOperators.Or => childConditionNodesSpecifications.Aggregate((s1, s2) => s1.Or(s2)), + _ => throw new NotSupportedException($"Unsupported logical operator: '{composedConditionNode.LogicalOperator}'."), + }; + } + + private ISpecification> BuildSpecificationForValueNode( + ValueConditionNode valueConditionNode, + IDictionary conditions, + MatchModes matchMode) + { + conditions.TryGetValue(valueConditionNode.ConditionType, out object leftOperand); + + if (leftOperand is null) + { + if (this.rulesEngineOptions.MissingConditionBehavior == MissingConditionBehaviors.Discard) + { + return new FuncSpecification>(conditions => false); + } + else if (matchMode == MatchModes.Search) + { + // When match mode is search, if condition is missing, it is not used as search + // criteria, so we don't filter out the rule. + return new FuncSpecification>(conditions => true); + } + } + + string multiplicity = this.multiplicityEvaluator.EvaluateMultiplicity(leftOperand, valueConditionNode.Operator, valueConditionNode.Operand); + + valueConditionNode.Properties.TryGetValue(ConditionNodeProperties.GetCompiledDelegateKey(multiplicity), out object conditionFuncAux); + Func, bool> conditionFunc = conditionFuncAux as Func, bool>; + + return new FuncSpecification>(conditionFunc); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs new file mode 100644 index 00000000..bcb12fe8 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs @@ -0,0 +1,15 @@ +namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using System.Text; + + internal class CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + { + public Expression BuildConditionExpression( + Expression leftHandOperandExpression, + Expression rightHandOperatorExpression, + DataTypeConfiguration dataTypeConfiguration) => throw new NotImplementedException(); + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs new file mode 100644 index 00000000..a96873d8 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs @@ -0,0 +1,15 @@ +namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using System.Text; + + internal class CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + { + public Expression BuildConditionExpression( + Expression leftHandOperandExpression, + Expression rightHandOperatorExpression, + DataTypeConfiguration dataTypeConfiguration) => throw new NotImplementedException(); + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProvider.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProvider.cs new file mode 100644 index 00000000..1a8d1efe --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProvider.cs @@ -0,0 +1,45 @@ +namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders +{ + using Rules.Framework.Core; + using System; + using System.Collections.Generic; + + internal sealed class ConditionExpressionBuilderProvider : IConditionExpressionBuilderProvider + { + private readonly IDictionary conditionExpressionBuilders; + + public ConditionExpressionBuilderProvider() + { + this.conditionExpressionBuilders = new Dictionary(StringComparer.Ordinal) + { + { Combine(Operators.Equal, Multiplicities.OneToOne), new EqualOneToOneConditionExpressionBuilder() }, + { Combine(Operators.NotEqual, Multiplicities.OneToOne), new NotEqualOneToOneConditionExpressionBuilder() }, + { Combine(Operators.GreaterThan, Multiplicities.OneToOne), new GreaterThanOneToOneConditionExpressionBuilder() }, + { Combine(Operators.GreaterThanOrEqual, Multiplicities.OneToOne), new GreaterThanOrEqualOneToOneConditionExpressionBuilder() }, + { Combine(Operators.LesserThan, Multiplicities.OneToOne), new LesserThanOneToOneConditionExpressionBuilder() }, + { Combine(Operators.LesserThanOrEqual, Multiplicities.OneToOne), new LesserThanOrEqualOneToOneConditionExpressionBuilder() }, + { Combine(Operators.Contains, Multiplicities.OneToOne), new ContainsOneToOneConditionExpressionBuilder() }, + { Combine(Operators.NotContains, Multiplicities.OneToOne), new NotContainsOneToOneConditionExpressionBuilder() }, + { Combine(Operators.In, Multiplicities.OneToMany), new InOneToManyConditionExpressionBuilder() }, + { Combine(Operators.StartsWith, Multiplicities.OneToOne), new StartsWithOneToOneConditionExpressionBuilder() }, + { Combine(Operators.EndsWith, Multiplicities.OneToOne), new EndsWithOneToOneConditionExpressionBuilder() }, + { Combine(Operators.CaseInsensitiveStartsWith, Multiplicities.OneToOne), new CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder() }, + { Combine(Operators.CaseInsensitiveEndsWith, Multiplicities.OneToOne), new CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder() }, + { Combine(Operators.NotEndsWith, Multiplicities.OneToOne), new NotEndsWithOneToOneConditionExpressionBuilder() }, + { Combine(Operators.NotStartsWith, Multiplicities.OneToOne), new NotStartsWithOneToOneConditionExpressionBuilder() }, + }; + } + + public IConditionExpressionBuilder GetConditionExpressionBuilderFor(Operators @operator, string multiplicity) + { + if (this.conditionExpressionBuilders.TryGetValue(Combine(@operator, multiplicity), out IConditionExpressionBuilder operatorEvalStrategy)) + { + return operatorEvalStrategy; + } + + throw new NotSupportedException($"Operator compilation is not supported for operator '{@operator}' with '{multiplicity}' multiplicity."); + } + + private static string Combine(Operators @operator, string multiplicity) => $"{multiplicity}-{@operator}"; + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs new file mode 100644 index 00000000..ce233367 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs @@ -0,0 +1,21 @@ +namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using System.Reflection; + using System.Text; + + internal class ContainsOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + { + private static readonly MethodInfo stringContainsMethodInfo = typeof(string).GetMethod("Contains", new[] { typeof(string) }); + + public Expression BuildConditionExpression( + Expression leftHandOperandExpression, + Expression rightHandOperatorExpression, + DataTypeConfiguration dataTypeConfiguration) + { + return Expression.Call(leftHandOperandExpression, stringContainsMethodInfo, rightHandOperatorExpression); + } + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs new file mode 100644 index 00000000..fb104360 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs @@ -0,0 +1,15 @@ +namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using System.Text; + + internal class EndsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + { + public Expression BuildConditionExpression( + Expression leftHandOperandExpression, + Expression rightHandOperatorExpression, + DataTypeConfiguration dataTypeConfiguration) => throw new NotImplementedException(); + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilder.cs new file mode 100644 index 00000000..1ef109bf --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilder.cs @@ -0,0 +1,19 @@ +namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders +{ + using Rules.Framework.Core; + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using System.Text; + + internal class EqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + { + public Expression BuildConditionExpression( + Expression leftHandOperandExpression, + Expression rightHandOperatorExpression, + DataTypeConfiguration dataTypeConfiguration) + { + return Expression.Equal(leftHandOperandExpression, rightHandOperatorExpression); + } + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs new file mode 100644 index 00000000..f7d1bef4 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs @@ -0,0 +1,18 @@ +namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using System.Text; + + internal class GreaterThanOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + { + public Expression BuildConditionExpression( + Expression leftHandOperandExpression, + Expression rightHandOperatorExpression, + DataTypeConfiguration dataTypeConfiguration) + { + return Expression.GreaterThan(leftHandOperandExpression, rightHandOperatorExpression); + } + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs new file mode 100644 index 00000000..051347d0 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs @@ -0,0 +1,18 @@ +namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using System.Text; + + internal class GreaterThanOrEqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + { + public Expression BuildConditionExpression( + Expression leftHandOperandExpression, + Expression rightHandOperatorExpression, + DataTypeConfiguration dataTypeConfiguration) + { + return Expression.GreaterThanOrEqual(leftHandOperandExpression, rightHandOperatorExpression); + } + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilder.cs new file mode 100644 index 00000000..37ad1a5f --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilder.cs @@ -0,0 +1,14 @@ +namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders +{ + using Rules.Framework.Core; + using Rules.Framework.Evaluation; + using System.Linq.Expressions; + + internal interface IConditionExpressionBuilder + { + Expression BuildConditionExpression( + Expression leftHandOperandExpression, + Expression rightHandOperatorExpression, + DataTypeConfiguration dataTypeConfiguration); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilderProvider.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilderProvider.cs new file mode 100644 index 00000000..159d62d0 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilderProvider.cs @@ -0,0 +1,9 @@ +namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders +{ + using Rules.Framework.Core; + + internal interface IConditionExpressionBuilderProvider + { + IConditionExpressionBuilder GetConditionExpressionBuilderFor(Operators @operator, string multiplicity); + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilder.cs new file mode 100644 index 00000000..9b8122c0 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilder.cs @@ -0,0 +1,22 @@ +namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using System.Text; + + internal class InOneToManyConditionExpressionBuilder : IConditionExpressionBuilder + { + private static readonly MethodInfo enumerableGenericContains = typeof(Enumerable).GetMethods().FirstOrDefault(m => m.Name == "Contains" && m.GetParameters().Length == 2); + + public Expression BuildConditionExpression( + Expression leftHandOperandExpression, + Expression rightHandOperatorExpression, + DataTypeConfiguration dataTypeConfiguration) + { + return Expression.Call(null, enumerableGenericContains.MakeGenericMethod(dataTypeConfiguration.Type), rightHandOperatorExpression, leftHandOperandExpression); + } + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs new file mode 100644 index 00000000..c4cc70dd --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs @@ -0,0 +1,18 @@ +namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using System.Text; + + internal class LesserThanOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + { + public Expression BuildConditionExpression( + Expression leftHandOperandExpression, + Expression rightHandOperatorExpression, + DataTypeConfiguration dataTypeConfiguration) + { + return Expression.LessThan(leftHandOperandExpression, rightHandOperatorExpression); + } + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs new file mode 100644 index 00000000..6408a26e --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs @@ -0,0 +1,18 @@ +namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using System.Text; + + internal class LesserThanOrEqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + { + public Expression BuildConditionExpression( + Expression leftHandOperandExpression, + Expression rightHandOperatorExpression, + DataTypeConfiguration dataTypeConfiguration) + { + return Expression.LessThanOrEqual(leftHandOperandExpression, rightHandOperatorExpression); + } + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs new file mode 100644 index 00000000..459b2bb9 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs @@ -0,0 +1,21 @@ +namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using System.Reflection; + using System.Text; + + internal class NotContainsOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + { + private static readonly MethodInfo stringContainsMethodInfo = typeof(string).GetMethod("Contains", new[] { typeof(string) }); + + public Expression BuildConditionExpression( + Expression leftHandOperandExpression, + Expression rightHandOperatorExpression, + DataTypeConfiguration dataTypeConfiguration) + { + return Expression.Not(Expression.Call(leftHandOperandExpression, stringContainsMethodInfo, rightHandOperatorExpression)); + } + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilder.cs new file mode 100644 index 00000000..bc01c933 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilder.cs @@ -0,0 +1,38 @@ +namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders +{ + using System; + using System.Linq.Expressions; + using System.Reflection; + using Rules.Framework.Core; + + internal class NotEndsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + { + private static readonly Type booleanType = typeof(bool); + + private static readonly MethodInfo endsWithMethodInfo = typeof(string) + .GetMethod(nameof(string.EndsWith), new[] { typeof(string), typeof(StringComparison) }); + + public Expression BuildConditionExpression( + Expression leftHandOperandExpression, + Expression rightHandOperatorExpression, + DataTypeConfiguration dataTypeConfiguration) + { + if (dataTypeConfiguration.DataType != DataTypes.String) + { + throw new NotSupportedException($"The operator '{Operators.NotEndsWith}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + } + + var returnLabelTarget = Expression.Label(booleanType); + Expression notNullScenario = Expression.Not(Expression.Call( + leftHandOperandExpression, + endsWithMethodInfo, + rightHandOperatorExpression, + Expression.Constant(StringComparison.InvariantCulture))); + Expression ifNotNullExpression = Expression.IfThen( + Expression.NotEqual(leftHandOperandExpression, Expression.Constant(value: null)), + Expression.Return(returnLabelTarget, notNullScenario)); + + return Expression.Block(new[] { ifNotNullExpression, Expression.Label(returnLabelTarget, Expression.Constant(value: false)) }); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilder.cs new file mode 100644 index 00000000..02e8118d --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilder.cs @@ -0,0 +1,18 @@ +namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using System.Text; + + internal class NotEqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + { + public Expression BuildConditionExpression( + Expression leftHandOperandExpression, + Expression rightHandOperatorExpression, + DataTypeConfiguration dataTypeConfiguration) + { + return Expression.NotEqual(leftHandOperandExpression, rightHandOperatorExpression); + } + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilder.cs new file mode 100644 index 00000000..ad356880 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilder.cs @@ -0,0 +1,38 @@ +namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders +{ + using System; + using System.Linq.Expressions; + using System.Reflection; + using Rules.Framework.Core; + + internal class NotStartsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + { + private static readonly Type booleanType = typeof(bool); + + private static readonly MethodInfo startsWithMethodInfo = typeof(string) + .GetMethod(nameof(string.StartsWith), new[] { typeof(string), typeof(StringComparison) }); + + public Expression BuildConditionExpression( + Expression leftHandOperandExpression, + Expression rightHandOperatorExpression, + DataTypeConfiguration dataTypeConfiguration) + { + if (dataTypeConfiguration.DataType != DataTypes.String) + { + throw new NotSupportedException($"The operator '{Operators.NotStartsWith}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + } + + var returnLabelTarget = Expression.Label(booleanType); + Expression notNullScenario = Expression.Not(Expression.Call( + leftHandOperandExpression, + startsWithMethodInfo, + rightHandOperatorExpression, + Expression.Constant(StringComparison.InvariantCulture))); + Expression ifNotNullExpression = Expression.IfThen( + Expression.NotEqual(leftHandOperandExpression, Expression.Constant(value: null)), + Expression.Return(returnLabelTarget, notNullScenario)); + + return Expression.Block(new[] { ifNotNullExpression, Expression.Label(returnLabelTarget, Expression.Constant(value: false)) }); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs new file mode 100644 index 00000000..e4eeb3a1 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs @@ -0,0 +1,15 @@ +namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using System.Text; + + internal class StartsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + { + public Expression BuildConditionExpression( + Expression leftHandOperandExpression, + Expression rightHandOperatorExpression, + DataTypeConfiguration dataTypeConfiguration) => throw new NotImplementedException(); + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionsTreeCompiler.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionsTreeCompiler.cs new file mode 100644 index 00000000..1042fb40 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionsTreeCompiler.cs @@ -0,0 +1,63 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using Rules.Framework.Core; + using Rules.Framework.Core.ConditionNodes; + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using System.Text; + + internal class ConditionsTreeCompiler : IConditionsTreeCompiler + { + private static readonly Type dictionaryOfConditionTypeAndObjectType = typeof(IDictionary); + private readonly IValueConditionNodeCompilerProvider valueConditionNodeCompilerProvider; + + public ConditionsTreeCompiler(IValueConditionNodeCompilerProvider valueConditionNodeCompilerProvider) + { + this.valueConditionNodeCompilerProvider = valueConditionNodeCompilerProvider; + } + + public void Compile(IConditionNode conditionNode) + { + ParameterExpression parameterExpression = Expression.Parameter(dictionaryOfConditionTypeAndObjectType, "conditions"); + this.CompileInternal(conditionNode, parameterExpression); + conditionNode.Properties[ConditionNodeProperties.CompiledFlagKey] = true; + } + + private void CompileInternal( + IConditionNode conditionNode, + ParameterExpression parameterExpression) + { + switch (conditionNode) + { + case ComposedConditionNode composedConditionNode: + foreach (var childConditionNode in composedConditionNode.ChildConditionNodes) + { + this.CompileInternal(childConditionNode, parameterExpression); + } + break; + case ValueConditionNode valueConditionNode: + this.CompileValueConditionNode(valueConditionNode, parameterExpression); + break; + default: + throw new NotSupportedException($"Unsupported condition node: '{conditionNode.GetType().Name}'."); + } + } + + private void CompileValueConditionNode( + ValueConditionNode valueConditionNode, + ParameterExpression parameterExpression) + { + var operatorMetadata = OperatorsMetadata.AllByOperator[valueConditionNode.Operator]; + + foreach (string multiplicity in operatorMetadata.SupportedMultiplicities) + { + var valueConditionNodeCompiler = this.valueConditionNodeCompilerProvider.GetValueConditionNodeCompiler(multiplicity); + + var compiledCondition = valueConditionNodeCompiler.Compile(valueConditionNode, parameterExpression); + + valueConditionNode.Properties[ConditionNodeProperties.GetCompiledDelegateKey(multiplicity)] = compiledCondition; + } + } + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/IConditionsTreeCompiler.cs b/src/Rules.Framework/Evaluation/Compiled/IConditionsTreeCompiler.cs new file mode 100644 index 00000000..86c7056d --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/IConditionsTreeCompiler.cs @@ -0,0 +1,12 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using Rules.Framework.Core; + using System; + using System.Collections.Generic; + using System.Text; + + internal interface IConditionsTreeCompiler + { + void Compile(IConditionNode conditionNode); + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeCompiler.cs b/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeCompiler.cs new file mode 100644 index 00000000..ecdbe806 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeCompiler.cs @@ -0,0 +1,15 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using Rules.Framework.Core.ConditionNodes; + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using System.Text; + + internal interface IValueConditionNodeCompiler + { + Func, bool> Compile( + ValueConditionNode valueConditionNode, + ParameterExpression parameterExpression); + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeCompilerProvider.cs b/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeCompilerProvider.cs new file mode 100644 index 00000000..80fbaa52 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeCompilerProvider.cs @@ -0,0 +1,12 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using Rules.Framework.Core; + using System; + using System.Collections.Generic; + using System.Text; + + internal interface IValueConditionNodeCompilerProvider + { + IValueConditionNodeCompiler GetValueConditionNodeCompiler(string multiplicity); + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/OneToManyValueConditionNodeCompiler.cs b/src/Rules.Framework/Evaluation/Compiled/OneToManyValueConditionNodeCompiler.cs new file mode 100644 index 00000000..e2fc49b3 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/OneToManyValueConditionNodeCompiler.cs @@ -0,0 +1,43 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using Rules.Framework.Core.ConditionNodes; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq.Expressions; + using System.Reflection; + using System.Text; + + internal class OneToManyValueConditionNodeCompiler : ValueConditionNodeCompilerBase, IValueConditionNodeCompiler + { + private readonly IConditionExpressionBuilderProvider conditionExpressionBuilderProvider; + + public OneToManyValueConditionNodeCompiler( + IConditionExpressionBuilderProvider conditionExpressionBuilderProvider, + IDataTypesConfigurationProvider dataTypesConfigurationProvider) + : base(dataTypesConfigurationProvider) + { + this.conditionExpressionBuilderProvider = conditionExpressionBuilderProvider; + } + + public Func, bool> Compile( + ValueConditionNode valueConditionNode, + ParameterExpression parameterExpression) + { + DataTypeConfiguration dataTypeConfiguration = this.GetDataTypeConfiguration(valueConditionNode.DataType); + + var getConditionExpression = CreateGetConditionExpression(valueConditionNode.ConditionType, parameterExpression, dataTypeConfiguration); + + var leftOperandExpression = CreateConvertedObjectExpression(getConditionExpression, dataTypeConfiguration.Type, dataTypeConfiguration.Default); + + var rightOperandExpression = CreateConvertedArrayExpression(valueConditionNode.Operand, dataTypeConfiguration.Type); + + var conditionExpressionBuilder = this.conditionExpressionBuilderProvider.GetConditionExpressionBuilderFor(valueConditionNode.Operator, Multiplicities.OneToMany); + var conditionExpression = conditionExpressionBuilder.BuildConditionExpression(leftOperandExpression, rightOperandExpression, dataTypeConfiguration); + + return Expression.Lambda, bool>>(conditionExpression, parameterExpression).Compile(); + } + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/OneToOneValueConditionNodeCompiler.cs b/src/Rules.Framework/Evaluation/Compiled/OneToOneValueConditionNodeCompiler.cs new file mode 100644 index 00000000..81505b39 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/OneToOneValueConditionNodeCompiler.cs @@ -0,0 +1,39 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using Rules.Framework.Core.ConditionNodes; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + + internal class OneToOneValueConditionNodeCompiler : ValueConditionNodeCompilerBase, IValueConditionNodeCompiler + { + private readonly IConditionExpressionBuilderProvider conditionExpressionBuilderProvider; + + public OneToOneValueConditionNodeCompiler( + IConditionExpressionBuilderProvider conditionExpressionBuilderProvider, + IDataTypesConfigurationProvider dataTypesConfigurationProvider) + : base(dataTypesConfigurationProvider) + { + this.conditionExpressionBuilderProvider = conditionExpressionBuilderProvider; + } + public Func, bool> Compile( + ValueConditionNode valueConditionNode, + ParameterExpression parameterExpression) + { + DataTypeConfiguration dataTypeConfiguration = this.GetDataTypeConfiguration(valueConditionNode.DataType); + + var getConditionExpression = CreateGetConditionExpression(valueConditionNode.ConditionType, parameterExpression, dataTypeConfiguration); + + var leftOperandExpression = CreateConvertedObjectExpression(getConditionExpression, dataTypeConfiguration.Type, dataTypeConfiguration.Default); + + var rightOperandExpression = CreateConvertedObjectExpression(valueConditionNode.Operand, dataTypeConfiguration.Type, dataTypeConfiguration.Default); + + var conditionExpressionBuilder = this.conditionExpressionBuilderProvider.GetConditionExpressionBuilderFor(valueConditionNode.Operator, Multiplicities.OneToOne); + var conditionExpression = conditionExpressionBuilder.BuildConditionExpression(leftOperandExpression, rightOperandExpression, dataTypeConfiguration); + + return Expression.Lambda, bool>>(conditionExpression, parameterExpression).Compile(); + } + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerBase.cs b/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerBase.cs new file mode 100644 index 00000000..6b5a6360 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerBase.cs @@ -0,0 +1,102 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using Rules.Framework.Core; + using Rules.Framework.Core.ConditionNodes; + using Rules.Framework.Evaluation; + using System; + using System.Collections; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using System.Text; + + internal class ValueConditionNodeCompilerBase + { + + protected static readonly MethodInfo changeTypeMethodInfo = typeof(Convert) + .GetMethod( + nameof(Convert.ChangeType), + new[] { typeof(object), typeof(Type), typeof(IFormatProvider) }); + protected static readonly Type systemType = typeof(Type); + protected static readonly Type formatProviderType = typeof(IFormatProvider); + protected static readonly Type objectType = typeof(object); + protected readonly IDataTypesConfigurationProvider dataTypesConfigurationProvider; + + protected ValueConditionNodeCompilerBase(IDataTypesConfigurationProvider dataTypesConfigurationProvider) + { + this.dataTypesConfigurationProvider = dataTypesConfigurationProvider; + } + + protected static object ConvertToDataType(object operand, string paramName, Type dataType, object dataDefault) + { + try + { + return Convert.ChangeType(operand + ?? dataDefault, dataType, CultureInfo.InvariantCulture); + } + catch (InvalidCastException ice) + { + throw new ArgumentException($"Parameter value or contained value is not convertible to {dataType.Name}.", paramName, ice); + } + } + + protected static Expression CreateGetConditionExpression(TConditionType conditionType, ParameterExpression parameterExpression, DataTypeConfiguration dataTypeConfiguration) + { + var returnTargetExpression = Expression.Label(typeof(object)); + var outVariableExpression = Expression.Variable(typeof(object), "conditionValue"); + var dictionaryTryGetValueMethodInfo = typeof(IDictionary).GetMethod("TryGetValue"); + var conditionTypeConstantExpression = Expression.Constant(conditionType, typeof(TConditionType)); + var testExpression = Expression.Call(parameterExpression, dictionaryTryGetValueMethodInfo, conditionTypeConstantExpression, outVariableExpression); + var defaultValueExpression = Expression.Constant(dataTypeConfiguration.Default, typeof(object)); + var ifThenExpression = Expression.IfThen(testExpression, Expression.Return(returnTargetExpression, outVariableExpression)); + return Expression.Block(new[] { outVariableExpression }, new Expression[] { ifThenExpression, Expression.Label(returnTargetExpression, defaultValueExpression) }); + } + + protected static Expression CreateConvertedArrayExpression(object operand, Type dataType) + { + IEnumerable operandAsTypedEnumerable = ConvertToTypedEnumerable(operand, nameof(operand), dataType); + IEnumerable arrayInitializerExpressions = operandAsTypedEnumerable.Select(o => Expression.Constant(o, dataType)); + return Expression.NewArrayInit(dataType, arrayInitializerExpressions); + } + + protected static Expression CreateConvertedObjectExpression(Expression operandExpression, Type dataType, object dataTypeDefault) + { + var testExpression = Expression.NotEqual(operandExpression, Expression.Constant(null)); + var returnTargetLabelExpression = Expression.Label(typeof(object)); + var defaultValueExpression = Expression.Constant(dataTypeDefault, objectType); + var ifThenElseExpression = Expression.IfThenElse( + testExpression, + Expression.Return(returnTargetLabelExpression, operandExpression), + Expression.Return(returnTargetLabelExpression, defaultValueExpression)); + var nullCoalesceBlock = Expression.Block(ifThenElseExpression, Expression.Label(returnTargetLabelExpression, defaultValueExpression)); + + var operandConvertExpression = Expression.Call( + changeTypeMethodInfo, + nullCoalesceBlock, + Expression.Constant(dataType, systemType), + Expression.Constant(CultureInfo.InvariantCulture, formatProviderType)); + return Expression.ConvertChecked(operandConvertExpression, dataType); + } + + protected static Expression CreateConvertedObjectExpression(object operand, Type dataType, object dataTypeDefault) + { + var rightOperandConverted = ConvertToDataType(operand, "operand", dataType, dataTypeDefault); + return Expression.ConvertChecked(Expression.Constant(rightOperandConverted), dataType); + } + + protected static IEnumerable ConvertToTypedEnumerable(object operand, string paramName, Type dataType) + { + if (operand is IEnumerable enumerable) + { + return enumerable.Cast().Select(o => ConvertToDataType(o, paramName, dataType, null)); + } + + throw new ArgumentException($"Parameter must be of type {nameof(IEnumerable)}.", paramName); + } + + protected DataTypeConfiguration GetDataTypeConfiguration(DataTypes dataType) + => this.dataTypesConfigurationProvider.GetDataTypeConfiguration(dataType); + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerProvider.cs b/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerProvider.cs new file mode 100644 index 00000000..b32db669 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerProvider.cs @@ -0,0 +1,39 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using Rules.Framework.Core; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + internal class ValueConditionNodeCompilerProvider : IValueConditionNodeCompilerProvider + { + private readonly Dictionary compilers; + + public ValueConditionNodeCompilerProvider( + IConditionExpressionBuilderProvider conditionExpressionBuilderProvider, + IDataTypesConfigurationProvider dataTypesConfigurationProvider) + { + this.compilers = new Dictionary + { + { Multiplicities.OneToOne, new OneToOneValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider) }, + { Multiplicities.OneToMany, new OneToManyValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider) }, + { Multiplicities.ManyToOne, null }, + { Multiplicities.ManyToMany, null } + }; + } + + public IValueConditionNodeCompiler GetValueConditionNodeCompiler(string multiplicity) + { + if (this.compilers.TryGetValue(multiplicity, out var compiler)) + { + return compiler; + } + + throw new NotSupportedException($"No compiler for multiplicity '{multiplicity}' defined."); + } + } +} diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/DataTypeConfiguration.cs b/src/Rules.Framework/Evaluation/DataTypeConfiguration.cs similarity index 91% rename from src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/DataTypeConfiguration.cs rename to src/Rules.Framework/Evaluation/DataTypeConfiguration.cs index d9d08539..a39de6a9 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/DataTypeConfiguration.cs +++ b/src/Rules.Framework/Evaluation/DataTypeConfiguration.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation.Dispatchers +namespace Rules.Framework.Evaluation { using System; using Rules.Framework.Core; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/DataTypesConfigurationProvider.cs b/src/Rules.Framework/Evaluation/DataTypesConfigurationProvider.cs similarity index 97% rename from src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/DataTypesConfigurationProvider.cs rename to src/Rules.Framework/Evaluation/DataTypesConfigurationProvider.cs index 75a8f324..71d7503f 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/DataTypesConfigurationProvider.cs +++ b/src/Rules.Framework/Evaluation/DataTypesConfigurationProvider.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation.Dispatchers +namespace Rules.Framework.Evaluation { using System; using System.Collections.Generic; diff --git a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/IDataTypesConfigurationProvider.cs b/src/Rules.Framework/Evaluation/IDataTypesConfigurationProvider.cs similarity index 73% rename from src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/IDataTypesConfigurationProvider.cs rename to src/Rules.Framework/Evaluation/IDataTypesConfigurationProvider.cs index 4bb1f7d7..fa842750 100644 --- a/src/Rules.Framework/Evaluation/ValueEvaluation/Dispatchers/IDataTypesConfigurationProvider.cs +++ b/src/Rules.Framework/Evaluation/IDataTypesConfigurationProvider.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.ValueEvaluation.Dispatchers +namespace Rules.Framework.Evaluation { using Rules.Framework.Core; diff --git a/src/Rules.Framework/Evaluation/MultiplicityEvaluator.cs b/src/Rules.Framework/Evaluation/MultiplicityEvaluator.cs index 3a9ace35..e2126333 100644 --- a/src/Rules.Framework/Evaluation/MultiplicityEvaluator.cs +++ b/src/Rules.Framework/Evaluation/MultiplicityEvaluator.cs @@ -1,5 +1,6 @@ namespace Rules.Framework.Evaluation { + using Rules.Framework.Core; using System; using System.Collections; using Rules.Framework.Core; diff --git a/src/Rules.Framework/RulesEngineOptions.cs b/src/Rules.Framework/RulesEngineOptions.cs index 9080f40f..e6f362bf 100644 --- a/src/Rules.Framework/RulesEngineOptions.cs +++ b/src/Rules.Framework/RulesEngineOptions.cs @@ -52,6 +52,8 @@ public static RulesEngineOptions NewWithDefaults() /// public IDictionary DataTypeDefaults { get; } + public bool EnableCompilation { get; set; } + /// /// Gets or sets the rules engine behavior when no condition with a specific type is provided to rules engine to match with a rule's condition with the same type. /// e.g. a rule with a condition of type "Age" is under evaluation but no condition of type "Age" was supplied. diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1.cs index 3fe424be..3d30e455 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1.cs @@ -8,11 +8,15 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark1 using System.Linq; using System.Threading.Tasks; + [SkewnessColumn, KurtosisColumn] public class Benchmark1 : IBenchmark { private readonly Benchmark1Data benchmarkData = new Benchmark1Data(); private RulesEngine rulesEngine; + [ParamsAllValues] + public bool EnableCompilation { get; set; } + [Benchmark] public async Task RunAsync() { @@ -26,6 +30,10 @@ public async Task SetUpAsync() .WithContentType() .WithConditionType() .SetDataSource(new InMemoryRulesDataSource(Enumerable.Empty>())) + .Configure(options => + { + options.EnableCompilation = this.EnableCompilation; + }) .Build(); foreach (var rule in this.benchmarkData.Rules) diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2.cs index c2d3196c..4903c989 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2.cs @@ -5,11 +5,15 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark2 using System.Linq; using System.Threading.Tasks; + [SkewnessColumn, KurtosisColumn] public class Benchmark2 : IBenchmark { private readonly Benchmark2Data benchmarkData = new Benchmark2Data(); private RulesEngine rulesEngine; + [ParamsAllValues] + public bool EnableCompilation { get; set; } + [Benchmark] public async Task RunAsync() { @@ -23,6 +27,10 @@ public async Task SetUpAsync() .WithContentType() .WithConditionType() .SetDataSource(new InMemoryRulesDataSource(Enumerable.Empty>())) + .Configure(options => + { + options.EnableCompilation = this.EnableCompilation; + }) .Build(); foreach (var rule in this.benchmarkData.Rules) diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3.cs index 4477b06d..d8590a19 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3.cs @@ -5,11 +5,15 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 using System.Linq; using System.Threading.Tasks; + [SkewnessColumn, KurtosisColumn] public class Benchmark3 : IBenchmark { private readonly Benchmark3Data benchmarkData = new Benchmark3Data(); private RulesEngine rulesEngine; + [ParamsAllValues] + public bool EnableCompilation { get; set; } + [Benchmark] public async Task RunAsync() { @@ -23,6 +27,10 @@ public async Task SetUpAsync() .WithContentType() .WithConditionType() .SetDataSource(new InMemoryRulesDataSource(Enumerable.Empty>())) + .Configure(options => + { + options.EnableCompilation = this.EnableCompilation; + }) .Build(); foreach (var rule in this.benchmarkData.Rules) diff --git a/tests/Rules.Framework.IntegrationTests/Tests/Scenario1/BodyMassIndexTests.cs b/tests/Rules.Framework.IntegrationTests/Tests/Scenario1/BodyMassIndexTests.cs index 95235b09..1a9ade3c 100644 --- a/tests/Rules.Framework.IntegrationTests/Tests/Scenario1/BodyMassIndexTests.cs +++ b/tests/Rules.Framework.IntegrationTests/Tests/Scenario1/BodyMassIndexTests.cs @@ -15,8 +15,10 @@ public class BodyMassIndexTests { private static string DataSourceFilePath => $@"{Environment.CurrentDirectory}/Scenarios/Scenario1/rules-framework-tests.body-mass-index.datasource.json"; - [Fact] - public async Task AddRule_AddingNewRuleFromScratchWithAgeConditionAtPriority1AndNewRuleAtPriority3_NewRuleIsInsertedAndExistentRulePriorityUpdatedAndNewRuleInsertedAfter() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task AddRule_AddingNewRuleFromScratchWithAgeConditionAtPriority1AndNewRuleAtPriority3_NewRuleIsInsertedAndExistentRulePriorityUpdatedAndNewRuleInsertedAfter(bool enableCompilation) { IRulesDataSource rulesDataSource = new InMemoryRulesDataSource(Enumerable.Empty>()); @@ -26,6 +28,10 @@ public async Task AddRule_AddingNewRuleFromScratchWithAgeConditionAtPriority1And .WithContentType() .WithConditionType() .SetDataSource(rulesDataSource) + .Configure(options => + { + options.EnableCompilation = enableCompilation; + }) .Build(); RuleBuilderResult newRuleResult1 = RuleBuilder.NewRule() @@ -78,8 +84,10 @@ public async Task AddRule_AddingNewRuleFromScratchWithAgeConditionAtPriority1And newRule2.Priority.Should().Be(2, "rule should have priority 2 if inserted at priority 2, given that last rule after insert was at priority 2."); } - [Fact] - public async Task AddRule_AddingNewRuleWithAgeConditionAtPriority1AndNewRuleAtPriority3_NewRuleIsInsertedAndExistentRulePriorityUpdatedAndNewRuleInsertedAfter() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task AddRule_AddingNewRuleWithAgeConditionAtPriority1AndNewRuleAtPriority3_NewRuleIsInsertedAndExistentRulePriorityUpdatedAndNewRuleInsertedAfter(bool enableCompilation) { // Arrange IRulesDataSource rulesDataSource = await RulesFromJsonFile.Load @@ -89,6 +97,10 @@ public async Task AddRule_AddingNewRuleWithAgeConditionAtPriority1AndNewRuleAtPr .WithContentType() .WithConditionType() .SetDataSource(rulesDataSource) + .Configure(options => + { + options.EnableCompilation = enableCompilation; + }) .Build(); RuleBuilderResult newRuleResult1 = RuleBuilder.NewRule() @@ -141,8 +153,10 @@ public async Task AddRule_AddingNewRuleWithAgeConditionAtPriority1AndNewRuleAtPr newRule2.Priority.Should().Be(3, "rule should have priority 3 if inserted at priority 3, given that last rule after insert was at priority 2."); } - [Fact] - public async Task AddRule_AddingNewRuleWithAgeConditionOnTop_NewRuleIsInsertedAndExistentRulePriorityUpdated() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task AddRule_AddingNewRuleWithAgeConditionOnTop_NewRuleIsInsertedAndExistentRulePriorityUpdated(bool enableCompilation) { // Arrange IRulesDataSource rulesDataSource = await RulesFromJsonFile.Load @@ -152,6 +166,10 @@ public async Task AddRule_AddingNewRuleWithAgeConditionOnTop_NewRuleIsInsertedAn .WithContentType() .WithConditionType() .SetDataSource(rulesDataSource) + .Configure(options => + { + options.EnableCompilation = enableCompilation; + }) .Build(); RuleBuilderResult newRuleResult = RuleBuilder.NewRule() @@ -189,8 +207,10 @@ public async Task AddRule_AddingNewRuleWithAgeConditionOnTop_NewRuleIsInsertedAn newRule.Priority.Should().Be(1, "rule should to priority 1 if inserted at top."); } - [Fact] - public async Task BodyMassIndex_NoConditions_ReturnsDefaultFormula() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task BodyMassIndex_NoConditions_ReturnsDefaultFormula(bool enableCompilation) { // Arrange string expectedFormulaDescription = "Body Mass Index default formula"; @@ -206,6 +226,10 @@ public async Task BodyMassIndex_NoConditions_ReturnsDefaultFormula() .WithContentType() .WithConditionType() .SetDataSource(rulesDataSource) + .Configure(options => + { + options.EnableCompilation = enableCompilation; + }) .Build(); // Act diff --git a/tests/Rules.Framework.IntegrationTests/Tests/Scenario2/CarInsuranceAdvisorTests.cs b/tests/Rules.Framework.IntegrationTests/Tests/Scenario2/CarInsuranceAdvisorTests.cs index e6d9f794..c22b074c 100644 --- a/tests/Rules.Framework.IntegrationTests/Tests/Scenario2/CarInsuranceAdvisorTests.cs +++ b/tests/Rules.Framework.IntegrationTests/Tests/Scenario2/CarInsuranceAdvisorTests.cs @@ -14,8 +14,10 @@ public class CarInsuranceAdvisorTests { private static string DataSourceFilePath => $@"{Environment.CurrentDirectory}/Scenarios/Scenario2/rules-framework-tests.car-insurance-advisor.json"; - [Fact] - public async Task GetCarInsuranceAdvice_ClaimDescriptionContionsAlcoholOrDrugs_ReturnsPerformInvestigation() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task GetCarInsuranceAdvice_ClaimDescriptionContionsAlcoholOrDrugs_ReturnsPerformInvestigation(bool enableCompilation) { // Arrange CarInsuranceAdvices expected = CarInsuranceAdvices.PerformInvestigation; @@ -50,6 +52,7 @@ public async Task GetCarInsuranceAdvice_ClaimDescriptionContionsAlcoholOrDrugs_R .Configure(reo => { reo.PriotityCriteria = PriorityCriterias.BottommostRuleWins; + reo.EnableCompilation = enableCompilation; }) .Build(); @@ -92,8 +95,10 @@ public async Task GetCarInsuranceAdvice_ClaimDescriptionContionsAlcoholOrDrugs_R actualContent.Should().Be(expected); } - [Fact] - public async Task GetCarInsuranceAdvice_RepairCostsNotWorthIt_ReturnsRefusePaymentPerFranchise() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task GetCarInsuranceAdvice_RepairCostsNotWorthIt_ReturnsRefusePaymentPerFranchise(bool enableCompilation) { // Arrange CarInsuranceAdvices expected = CarInsuranceAdvices.RefusePaymentPerFranchise; @@ -123,6 +128,7 @@ public async Task GetCarInsuranceAdvice_RepairCostsNotWorthIt_ReturnsRefusePayme .Configure(reo => { reo.PriotityCriteria = PriorityCriterias.BottommostRuleWins; + reo.EnableCompilation = enableCompilation; }) .Build(); @@ -135,8 +141,10 @@ public async Task GetCarInsuranceAdvice_RepairCostsNotWorthIt_ReturnsRefusePayme actualContent.Should().Be(expected); } - [Fact] - public async Task GetCarInsuranceAdvice_SearchForRulesExcludingRulesWithoutSearchConditions_ReturnsNoRules() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task GetCarInsuranceAdvice_SearchForRulesExcludingRulesWithoutSearchConditions_ReturnsNoRules(bool enableCompilation) { // Arrange const ContentTypes expectedContent = ContentTypes.CarInsuranceAdvice; @@ -169,6 +177,7 @@ public async Task GetCarInsuranceAdvice_SearchForRulesExcludingRulesWithoutSearc .Configure(reo => { reo.PriotityCriteria = PriorityCriterias.BottommostRuleWins; + reo.EnableCompilation = enableCompilation; }) .Build(); @@ -180,8 +189,10 @@ public async Task GetCarInsuranceAdvice_SearchForRulesExcludingRulesWithoutSearc actual.Should().HaveCount(0); } - [Fact] - public async Task GetCarInsuranceAdvice_SearchForRulesWithRepairCostsGreaterThan1000_Returns2Rules() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task GetCarInsuranceAdvice_SearchForRulesWithRepairCostsGreaterThan1000_Returns2Rules(bool enableCompilation) { // Arrange const ContentTypes expectedContent = ContentTypes.CarInsuranceAdvice; @@ -209,6 +220,7 @@ public async Task GetCarInsuranceAdvice_SearchForRulesWithRepairCostsGreaterThan .Configure(reo => { reo.PriotityCriteria = PriorityCriterias.BottommostRuleWins; + reo.EnableCompilation = enableCompilation; }) .Build(); @@ -222,8 +234,10 @@ public async Task GetCarInsuranceAdvice_SearchForRulesWithRepairCostsGreaterThan actual.Should().Contain(r => r.Name == "Car Insurance Advise on repair costs lesser than 80% of commercial value"); } - [Fact] - public async Task GetCarInsuranceAdvice_UpdatesRuleAndAddsNewOneAndEvaluates_ReturnsPay() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task GetCarInsuranceAdvice_UpdatesRuleAndAddsNewOneAndEvaluates_ReturnsPay(bool enableCompilation) { // Arrange const ContentTypes expectedContent = ContentTypes.CarInsuranceAdvice; @@ -252,6 +266,7 @@ public async Task GetCarInsuranceAdvice_UpdatesRuleAndAddsNewOneAndEvaluates_Ret .Configure(reo => { reo.PriotityCriteria = PriorityCriterias.BottommostRuleWins; + reo.EnableCompilation = enableCompilation; }) .Build(); diff --git a/tests/Rules.Framework.IntegrationTests/Tests/Scenario3/BuildingSecuritySystemControlTests.cs b/tests/Rules.Framework.IntegrationTests/Tests/Scenario3/BuildingSecuritySystemControlTests.cs index fad028d4..fe5f856e 100644 --- a/tests/Rules.Framework.IntegrationTests/Tests/Scenario3/BuildingSecuritySystemControlTests.cs +++ b/tests/Rules.Framework.IntegrationTests/Tests/Scenario3/BuildingSecuritySystemControlTests.cs @@ -13,8 +13,10 @@ public class BuildingSecuritySystemControlTests { private static string DataSourceFilePath => $@"{Environment.CurrentDirectory}/Scenarios/Scenario3/rules-framework-tests.security-system-actionables.json"; - [Fact] - public async Task BuildingSecuritySystem_FireScenario_ReturnsActionsToTrigger() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task BuildingSecuritySystem_FireScenario_ReturnsActionsToTrigger(bool enableCompilation) { // Assert const SecuritySystemActionables securitySystemActionable = SecuritySystemActionables.FireSystem; @@ -46,6 +48,10 @@ public async Task BuildingSecuritySystem_FireScenario_ReturnsActionsToTrigger() .WithContentType() .WithConditionType() .SetDataSource(rulesDataSource) + .Configure(options => + { + options.EnableCompilation = enableCompilation; + }) .Build(); // Act @@ -62,8 +68,10 @@ public async Task BuildingSecuritySystem_FireScenario_ReturnsActionsToTrigger() .And.HaveCount(3); } - [Fact] - public async Task BuildingSecuritySystem_PowerFailureScenario_ReturnsActionsToTrigger() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task BuildingSecuritySystem_PowerFailureScenario_ReturnsActionsToTrigger(bool enableCompilation) { // Assert const SecuritySystemActionables securitySystemActionable = SecuritySystemActionables.PowerSystem; @@ -95,6 +103,10 @@ public async Task BuildingSecuritySystem_PowerFailureScenario_ReturnsActionsToTr .WithContentType() .WithConditionType() .SetDataSource(rulesDataSource) + .Configure(options => + { + options.EnableCompilation = enableCompilation; + }) .Build(); // Act @@ -110,8 +122,10 @@ public async Task BuildingSecuritySystem_PowerFailureScenario_ReturnsActionsToTr .And.Contain(ssa => ssa.ActionName == "CallPowerGridPicket"); } - [Fact] - public async Task BuildingSecuritySystem_PowerShutdownScenario_ReturnsActionsToTrigger() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task BuildingSecuritySystem_PowerShutdownScenario_ReturnsActionsToTrigger(bool enableCompilation) { // Assert const SecuritySystemActionables securitySystemActionable = SecuritySystemActionables.PowerSystem; @@ -143,6 +157,10 @@ public async Task BuildingSecuritySystem_PowerShutdownScenario_ReturnsActionsToT .WithContentType() .WithConditionType() .SetDataSource(rulesDataSource) + .Configure(options => + { + options.EnableCompilation = enableCompilation; + }) .Build(); // Act diff --git a/tests/Rules.Framework.IntegrationTests/Tests/Scenario4/DiscountCampaignTests.cs b/tests/Rules.Framework.IntegrationTests/Tests/Scenario4/DiscountCampaignTests.cs index ac8fa7dd..576739cf 100644 --- a/tests/Rules.Framework.IntegrationTests/Tests/Scenario4/DiscountCampaignTests.cs +++ b/tests/Rules.Framework.IntegrationTests/Tests/Scenario4/DiscountCampaignTests.cs @@ -19,8 +19,10 @@ public enum ProductColor White = 2 } - [Fact] - public async Task DiscountsWeekend_Adding15PercentRulePerBrandAndEvaluatingOneOfTheBrands_Returns15PercentDiscountRate() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task DiscountsWeekend_Adding15PercentRulePerBrandAndEvaluatingOneOfTheBrands_Returns15PercentDiscountRate(bool enableCompilation) { // Arrange IRulesDataSource rulesDataSource @@ -30,6 +32,10 @@ IRulesDataSource rulesDataSource .WithContentType() .WithConditionType() .SetDataSource(rulesDataSource) + .Configure(options => + { + options.EnableCompilation = enableCompilation; + }) .Build(); // Act 1 - Create rule with "in" operator @@ -93,8 +99,10 @@ IRulesDataSource rulesDataSource actual.Should().BeEquivalentTo(rule); } - [Fact] - public async Task DiscountsWeekend_Adding20PercentRulePerProductTierAndEvaluatingOneOfTheTiers_Returns20PercentDiscountRate() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task DiscountsWeekend_Adding20PercentRulePerProductTierAndEvaluatingOneOfTheTiers_Returns20PercentDiscountRate(bool enableCompilation) { // Arrange IRulesDataSource rulesDataSource @@ -104,6 +112,10 @@ IRulesDataSource rulesDataSource .WithContentType() .WithConditionType() .SetDataSource(rulesDataSource) + .Configure(options => + { + options.EnableCompilation = enableCompilation; + }) .Build(); // Act 1 - Create rule with "in" operator @@ -172,8 +184,10 @@ IRulesDataSource rulesDataSource actual.Should().BeEquivalentTo(rule); } - [Fact] - public async Task DiscountsWeekend_Adding5PercentRuleWithNotContainsTestConditionAndInputWithMatchingConditions_Return5PercentDiscountRate() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task DiscountsWeekend_Adding5PercentRuleWithNotContainsTestConditionAndInputWithMatchingConditions_Return5PercentDiscountRate(bool enableCompilation) { // Arrange IRulesDataSource rulesDataSource @@ -183,6 +197,10 @@ IRulesDataSource rulesDataSource .WithContentType() .WithConditionType() .SetDataSource(rulesDataSource) + .Configure(options => + { + options.EnableCompilation = enableCompilation; + }) .Build(); // Act 1 - Create rule with "not contains" operator @@ -233,8 +251,10 @@ IRulesDataSource rulesDataSource actual.Should().BeEquivalentTo(rule); } - [Fact] - public async Task DiscountsWeekend_AddingRuleWithNullTestConditionAndInputWithMatchingConditions_ReturnNotNull() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task DiscountsWeekend_AddingRuleWithNullTestConditionAndInputWithMatchingConditions_ReturnNotNull(bool enableCompilation) { // Arrange IRulesDataSource rulesDataSource @@ -244,6 +264,10 @@ IRulesDataSource rulesDataSource .WithContentType() .WithConditionType() .SetDataSource(rulesDataSource) + .Configure(options => + { + options.EnableCompilation = enableCompilation; + }) .Build(); // Act 1 - Create rule with "equal" operator @@ -294,8 +318,10 @@ IRulesDataSource rulesDataSource actual.Should().BeEquivalentTo(rule); } - [Fact] - public async Task DiscountsWeekend_AddingRuleWithNullTestConditionAndInputWithoutConditions_ReturnNull() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task DiscountsWeekend_AddingRuleWithNullTestConditionAndInputWithoutConditions_ReturnNull(bool enableCompilation) { // Arrange IRulesDataSource rulesDataSource @@ -305,6 +331,10 @@ IRulesDataSource rulesDataSource .WithContentType() .WithConditionType() .SetDataSource(rulesDataSource) + .Configure(options => + { + options.EnableCompilation = enableCompilation; + }) .Build(); // Act 1 - Create rule with "equal" operator @@ -347,8 +377,10 @@ IRulesDataSource rulesDataSource actual.Should().BeNull(); } - [Fact] - public async Task DiscountsWeekend_AddingRuleWithNullTestConditionAndInputWithoutMatchingConditions_ReturnNull() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task DiscountsWeekend_AddingRuleWithNullTestConditionAndInputWithoutMatchingConditions_ReturnNull(bool enableCompilation) { // Arrange IRulesDataSource rulesDataSource @@ -358,6 +390,10 @@ IRulesDataSource rulesDataSource .WithContentType() .WithConditionType() .SetDataSource(rulesDataSource) + .Configure(options => + { + options.EnableCompilation = enableCompilation; + }) .Build(); // Act 1 - Create rule with "equal" operator diff --git a/tests/Rules.Framework.Tests/Evaluation/ConditionsEvalEngineTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ConditionsEvalEngineTests.cs similarity index 99% rename from tests/Rules.Framework.Tests/Evaluation/ConditionsEvalEngineTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ConditionsEvalEngineTests.cs index 19b7b49b..9d6dd13e 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ConditionsEvalEngineTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ConditionsEvalEngineTests.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Tests.Evaluation +namespace Rules.Framework.Tests.Evaluation.Classic { using System; using System.Collections.Generic; @@ -7,7 +7,7 @@ namespace Rules.Framework.Tests.Evaluation using Rules.Framework.Core; using Rules.Framework.Core.ConditionNodes; using Rules.Framework.Evaluation; - using Rules.Framework.Evaluation.ValueEvaluation; + using Rules.Framework.Evaluation.Classic; using Rules.Framework.Tests.TestStubs; using Xunit; diff --git a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/DeferredEvalTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/DeferredEvalTests.cs similarity index 98% rename from tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/DeferredEvalTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/DeferredEvalTests.cs index b5414cb7..9c0281fc 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/DeferredEvalTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/DeferredEvalTests.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Tests.Evaluation.ValueEvaluation +namespace Rules.Framework.Tests.Evaluation.Classic { using System; using System.Collections.Generic; @@ -7,8 +7,8 @@ namespace Rules.Framework.Tests.Evaluation.ValueEvaluation using Rules.Framework.Core; using Rules.Framework.Core.ConditionNodes; using Rules.Framework.Evaluation; - using Rules.Framework.Evaluation.ValueEvaluation; - using Rules.Framework.Evaluation.ValueEvaluation.Dispatchers; + using Rules.Framework.Evaluation.Classic; + using Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers; using Rules.Framework.Tests.TestStubs; using Xunit; diff --git a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategyTests.cs similarity index 94% rename from tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategyTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategyTests.cs index b5eacff3..2de092ae 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategyTests.cs @@ -1,8 +1,8 @@ -namespace Rules.Framework.Tests.Evaluation.ValueEvaluation +namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation { using System; using FluentAssertions; - using Rules.Framework.Evaluation.ValueEvaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; using Xunit; public class CaseInsensitiveEndsWithOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategyTests.cs similarity index 94% rename from tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategyTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategyTests.cs index e13acd9c..33011c92 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategyTests.cs @@ -1,8 +1,8 @@ -namespace Rules.Framework.Tests.Evaluation.ValueEvaluation +namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation { using System; using FluentAssertions; - using Rules.Framework.Evaluation.ValueEvaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; using Xunit; public class CaseInsensitiveStartsWithOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/ContainsOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/ContainsOperatorEvalStrategyTests.cs similarity index 93% rename from tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/ContainsOperatorEvalStrategyTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/ContainsOperatorEvalStrategyTests.cs index 5d294ce5..779efe83 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/ContainsOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/ContainsOperatorEvalStrategyTests.cs @@ -1,7 +1,7 @@ -namespace Rules.Framework.Tests.Evaluation.ValueEvaluation +namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation { using FluentAssertions; - using Rules.Framework.Evaluation.ValueEvaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; using System; using Xunit; diff --git a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcherTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcherTests.cs similarity index 97% rename from tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcherTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcherTests.cs index 3923bf79..e46d8b61 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcherTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcherTests.cs @@ -6,8 +6,9 @@ namespace Rules.Framework.Tests.Evaluation.ValueEvaluation.Dispatchers using FluentAssertions; using Moq; using Rules.Framework.Core; - using Rules.Framework.Evaluation.ValueEvaluation; - using Rules.Framework.Evaluation.ValueEvaluation.Dispatchers; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers; using Xunit; public class ManyToManyConditionEvalDispatcherTests diff --git a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcherTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcherTests.cs similarity index 95% rename from tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcherTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcherTests.cs index 2225b7b2..8adb8b37 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcherTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcherTests.cs @@ -6,8 +6,9 @@ namespace Rules.Framework.Tests.Evaluation.ValueEvaluation.Dispatchers using FluentAssertions; using Moq; using Rules.Framework.Core; - using Rules.Framework.Evaluation.ValueEvaluation; - using Rules.Framework.Evaluation.ValueEvaluation.Dispatchers; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers; using Xunit; public class ManyToOneConditionEvalDispatcherTests diff --git a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcherTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcherTests.cs similarity index 95% rename from tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcherTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcherTests.cs index de2cee96..b9b42182 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcherTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcherTests.cs @@ -5,8 +5,9 @@ namespace Rules.Framework.Tests.Evaluation.ValueEvaluation.Dispatchers using FluentAssertions; using Moq; using Rules.Framework.Core; - using Rules.Framework.Evaluation.ValueEvaluation; - using Rules.Framework.Evaluation.ValueEvaluation.Dispatchers; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers; using Xunit; public class OneToManyConditionEvalDispatcherTests diff --git a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcherTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcherTests.cs similarity index 96% rename from tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcherTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcherTests.cs index 080494b4..17f05016 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcherTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcherTests.cs @@ -4,8 +4,9 @@ namespace Rules.Framework.Tests.Evaluation.ValueEvaluation.Dispatchers using FluentAssertions; using Moq; using Rules.Framework.Core; - using Rules.Framework.Evaluation.ValueEvaluation; - using Rules.Framework.Evaluation.ValueEvaluation.Dispatchers; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers; using Xunit; public class OneToOneConditionEvalDispatcherTests diff --git a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/EndsWithOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/EndsWithOperatorEvalStrategyTests.cs similarity index 94% rename from tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/EndsWithOperatorEvalStrategyTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/EndsWithOperatorEvalStrategyTests.cs index 73627ba3..b54ec7c8 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/EndsWithOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/EndsWithOperatorEvalStrategyTests.cs @@ -1,7 +1,7 @@ -namespace Rules.Framework.Tests.Evaluation.ValueEvaluation +namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation { using FluentAssertions; - using Rules.Framework.Evaluation.ValueEvaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; using System; using Xunit; diff --git a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/EqualOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/EqualOperatorEvalStrategyTests.cs similarity index 93% rename from tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/EqualOperatorEvalStrategyTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/EqualOperatorEvalStrategyTests.cs index de397350..9391f199 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/EqualOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/EqualOperatorEvalStrategyTests.cs @@ -1,9 +1,9 @@ -namespace Rules.Framework.Tests.Evaluation.ValueEvaluation +namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation { using System; using System.Collections.Generic; using FluentAssertions; - using Rules.Framework.Evaluation.ValueEvaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; using Xunit; public class EqualOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/GreaterThanOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/GreaterThanOperatorEvalStrategyTests.cs similarity index 94% rename from tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/GreaterThanOperatorEvalStrategyTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/GreaterThanOperatorEvalStrategyTests.cs index ce9ea091..bc8c1d3e 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/GreaterThanOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/GreaterThanOperatorEvalStrategyTests.cs @@ -1,9 +1,9 @@ -namespace Rules.Framework.Tests.Evaluation.ValueEvaluation +namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation { using System; using System.Collections.Generic; using FluentAssertions; - using Rules.Framework.Evaluation.ValueEvaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; using Xunit; public class GreaterThanOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategyTests.cs similarity index 94% rename from tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategyTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategyTests.cs index 358ae545..a9237b5d 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategyTests.cs @@ -1,9 +1,9 @@ -namespace Rules.Framework.Tests.Evaluation.ValueEvaluation +namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation { using System; using System.Collections.Generic; using FluentAssertions; - using Rules.Framework.Evaluation.ValueEvaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; using Xunit; public class GreaterThanOrEqualOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/InOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/InOperatorEvalStrategyTests.cs similarity index 90% rename from tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/InOperatorEvalStrategyTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/InOperatorEvalStrategyTests.cs index c0c4b881..a613d781 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/InOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/InOperatorEvalStrategyTests.cs @@ -1,9 +1,9 @@ -namespace Rules.Framework.Tests.Evaluation.ValueEvaluation +namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation { using System.Collections.Generic; using System.Linq; using FluentAssertions; - using Rules.Framework.Evaluation.ValueEvaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; using Xunit; public class InOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/LesserThanOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/LesserThanOperatorEvalStrategyTests.cs similarity index 94% rename from tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/LesserThanOperatorEvalStrategyTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/LesserThanOperatorEvalStrategyTests.cs index e2068524..b0607de7 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/LesserThanOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/LesserThanOperatorEvalStrategyTests.cs @@ -1,9 +1,9 @@ -namespace Rules.Framework.Tests.Evaluation.ValueEvaluation +namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation { using System; using System.Collections.Generic; using FluentAssertions; - using Rules.Framework.Evaluation.ValueEvaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; using Xunit; public class LesserThanOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategyTests.cs similarity index 94% rename from tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategyTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategyTests.cs index e881ab8c..e97457e9 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategyTests.cs @@ -1,9 +1,9 @@ -namespace Rules.Framework.Tests.Evaluation.ValueEvaluation +namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation { using System; using System.Collections.Generic; using FluentAssertions; - using Rules.Framework.Evaluation.ValueEvaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; using Xunit; public class LesserThanOrEqualOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/NotEndsWithOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotEndsWithOperatorEvalStrategyTests.cs similarity index 96% rename from tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/NotEndsWithOperatorEvalStrategyTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotEndsWithOperatorEvalStrategyTests.cs index c20c4f89..50770c2a 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/NotEndsWithOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotEndsWithOperatorEvalStrategyTests.cs @@ -1,7 +1,7 @@ namespace Rules.Framework.Tests.Evaluation.ValueEvaluation { using System; - using Rules.Framework.Evaluation.ValueEvaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; using Xunit; public class NotEndsWithOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/NotEqualOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotEqualOperatorEvalStrategyTests.cs similarity index 93% rename from tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/NotEqualOperatorEvalStrategyTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotEqualOperatorEvalStrategyTests.cs index e7898684..d1685ee1 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/NotEqualOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotEqualOperatorEvalStrategyTests.cs @@ -1,9 +1,9 @@ -namespace Rules.Framework.Tests.Evaluation.ValueEvaluation +namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation { using System; using System.Collections.Generic; using FluentAssertions; - using Rules.Framework.Evaluation.ValueEvaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; using Xunit; public class NotEqualOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/NotStartsWithOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotStartsWithOperatorEvalStrategyTests.cs similarity index 96% rename from tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/NotStartsWithOperatorEvalStrategyTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotStartsWithOperatorEvalStrategyTests.cs index 56b8b409..5218920e 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/NotStartsWithOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotStartsWithOperatorEvalStrategyTests.cs @@ -1,7 +1,7 @@ namespace Rules.Framework.Tests.Evaluation.ValueEvaluation { using System; - using Rules.Framework.Evaluation.ValueEvaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; using Xunit; public class NotStartsWithOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/OperatorEvalStrategyFactoryTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/OperatorEvalStrategyFactoryTests.cs similarity index 98% rename from tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/OperatorEvalStrategyFactoryTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/OperatorEvalStrategyFactoryTests.cs index cc5f1861..6b895a2c 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/OperatorEvalStrategyFactoryTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/OperatorEvalStrategyFactoryTests.cs @@ -1,9 +1,9 @@ -namespace Rules.Framework.Tests.Evaluation.ValueEvaluation +namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation { using System; using FluentAssertions; using Rules.Framework.Core; - using Rules.Framework.Evaluation.ValueEvaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; using Xunit; public class OperatorEvalStrategyFactoryTests diff --git a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/StartsWithOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/StartsWithOperatorEvalStrategyTests.cs similarity index 94% rename from tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/StartsWithOperatorEvalStrategyTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/StartsWithOperatorEvalStrategyTests.cs index 351cc24a..299c978f 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ValueEvaluation/StartsWithOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/StartsWithOperatorEvalStrategyTests.cs @@ -1,7 +1,7 @@ -namespace Rules.Framework.Tests.Evaluation.ValueEvaluation +namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation { using FluentAssertions; - using Rules.Framework.Evaluation.ValueEvaluation; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; using System; using Xunit; diff --git a/tests/Rules.Framework.Tests/Evaluation/StubConditionNode.cs b/tests/Rules.Framework.Tests/Evaluation/StubConditionNode.cs index 9ec42909..967d6acc 100644 --- a/tests/Rules.Framework.Tests/Evaluation/StubConditionNode.cs +++ b/tests/Rules.Framework.Tests/Evaluation/StubConditionNode.cs @@ -1,12 +1,15 @@ namespace Rules.Framework.Tests.Evaluation { using System; + using System.Collections.Generic; using Rules.Framework.Core; internal class StubConditionNode : IConditionNode { public LogicalOperators LogicalOperator => throw new NotImplementedException(); + public IDictionary Properties => throw new NotImplementedException(); + public IConditionNode Clone() => throw new NotImplementedException(); } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Extensions/GenericRuleExtensionsTests.cs b/tests/Rules.Framework.Tests/Extensions/GenericRuleExtensionsTests.cs index 7958356a..181e29d2 100644 --- a/tests/Rules.Framework.Tests/Extensions/GenericRuleExtensionsTests.cs +++ b/tests/Rules.Framework.Tests/Extensions/GenericRuleExtensionsTests.cs @@ -152,7 +152,8 @@ public void GenericRuleExtensions_ToGenericRule_WithValueCondition_Success() var genericValueRootCondition = genericRule.RootCondition as GenericValueConditionNode; genericValueRootCondition.Should().BeEquivalentTo(expectedRootCondition, config => config .Excluding(r => r.ConditionType) - .Excluding(r => r.LogicalOperator)); + .Excluding(r => r.LogicalOperator) + .Excluding(r => r.Properties)); genericValueRootCondition.ConditionTypeName.Should().Be(expectedRootCondition.ConditionType.ToString()); } } From d254f78f05492a9706bed4cd2ac8f0ae132959e1 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sat, 29 Oct 2022 11:15:32 +0100 Subject: [PATCH 04/54] feat: add various improvements to favor overral rules evaluation performance --- .../Core/ConditionNodes/ComposedConditionNode.cs | 3 ++- .../Core/ConditionNodes/ValueConditionNode.cs | 5 +++-- .../ConditionNodes/ValueConditionNodeTemplate.cs | 2 +- .../Dispatchers/ConditionEvalDispatchProvider.cs | 14 ++------------ .../Compiled/CompiledConditionsEvalEngine.cs | 2 +- ...veEndsWithOneToOneConditionExpressionBuilder.cs | 2 +- ...StartsWithOneToOneConditionExpressionBuilder.cs | 2 +- .../ContainsOneToOneConditionExpressionBuilder.cs | 2 +- .../EndsWithOneToOneConditionExpressionBuilder.cs | 2 +- .../EqualOneToOneConditionExpressionBuilder.cs | 2 +- ...reaterThanOneToOneConditionExpressionBuilder.cs | 2 +- ...hanOrEqualOneToOneConditionExpressionBuilder.cs | 2 +- .../InOneToManyConditionExpressionBuilder.cs | 2 +- ...LesserThanOneToOneConditionExpressionBuilder.cs | 2 +- ...hanOrEqualOneToOneConditionExpressionBuilder.cs | 2 +- ...otContainsOneToOneConditionExpressionBuilder.cs | 2 +- .../NotEqualOneToOneConditionExpressionBuilder.cs | 2 +- ...StartsWithOneToOneConditionExpressionBuilder.cs | 2 +- .../Evaluation/Compiled/ConditionsTreeCompiler.cs | 2 +- .../OneToManyValueConditionNodeCompiler.cs | 2 +- .../Compiled/OneToOneValueConditionNodeCompiler.cs | 2 +- .../Compiled/ValueConditionNodeCompilerProvider.cs | 4 ++-- .../Evaluation/MultiplicityEvaluator.cs | 1 - 23 files changed, 27 insertions(+), 36 deletions(-) diff --git a/src/Rules.Framework/Core/ConditionNodes/ComposedConditionNode.cs b/src/Rules.Framework/Core/ConditionNodes/ComposedConditionNode.cs index b6fe89ec..4df8bf20 100644 --- a/src/Rules.Framework/Core/ConditionNodes/ComposedConditionNode.cs +++ b/src/Rules.Framework/Core/ConditionNodes/ComposedConditionNode.cs @@ -1,5 +1,6 @@ namespace Rules.Framework.Core.ConditionNodes { + using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; @@ -21,7 +22,7 @@ public ComposedConditionNode(LogicalOperators logicalOperator, IEnumerable(); + this.Properties = new Dictionary(StringComparer.Ordinal); } /// diff --git a/src/Rules.Framework/Core/ConditionNodes/ValueConditionNode.cs b/src/Rules.Framework/Core/ConditionNodes/ValueConditionNode.cs index 85ca4051..b526765e 100644 --- a/src/Rules.Framework/Core/ConditionNodes/ValueConditionNode.cs +++ b/src/Rules.Framework/Core/ConditionNodes/ValueConditionNode.cs @@ -1,5 +1,6 @@ namespace Rules.Framework.Core.ConditionNodes { + using System; using System.Collections.Generic; using System.Diagnostics; @@ -21,7 +22,7 @@ public class ValueConditionNode : IValueConditionNodeThe operator. /// The operand. public ValueConditionNode(DataTypes dataType, TConditionType conditionType, Operators @operator, object operand) - : this(dataType, conditionType, @operator, operand, new Dictionary()) + : this(dataType, conditionType, @operator, operand, new Dictionary(StringComparer.Ordinal)) { } @@ -78,6 +79,6 @@ public ValueConditionNode(DataTypes dataType, TConditionType conditionType, Oper /// /// public IConditionNode Clone() - => new ValueConditionNode(this.DataType, this.ConditionType, this.Operator, this.Operand, new Dictionary(this.Properties)); + => new ValueConditionNode(this.DataType, this.ConditionType, this.Operator, this.Operand, new Dictionary(this.Properties, StringComparer.Ordinal)); } } \ No newline at end of file diff --git a/src/Rules.Framework/Core/ConditionNodes/ValueConditionNodeTemplate.cs b/src/Rules.Framework/Core/ConditionNodes/ValueConditionNodeTemplate.cs index 0d6838ba..9387cc19 100644 --- a/src/Rules.Framework/Core/ConditionNodes/ValueConditionNodeTemplate.cs +++ b/src/Rules.Framework/Core/ConditionNodes/ValueConditionNodeTemplate.cs @@ -25,7 +25,7 @@ protected ValueConditionNodeTemplate(TConditionType conditionType, Operators @op this.ConditionType = conditionType; this.Operand = operand; this.Operator = @operator; - this.Properties = new Dictionary(); + this.Properties = new Dictionary(StringComparer.Ordinal); } /// diff --git a/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs index 675ff5b7..657b44d6 100644 --- a/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs @@ -7,7 +7,7 @@ namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers using Rules.Framework.Core; using Rules.Framework.Evaluation; - internal class ConditionEvalDispatchProvider : IConditionEvalDispatchProvider + internal sealed class ConditionEvalDispatchProvider : IConditionEvalDispatchProvider { private readonly Dictionary dispatchers; private readonly IMultiplicityEvaluator multiplicityEvaluator; @@ -17,7 +17,7 @@ public ConditionEvalDispatchProvider( IMultiplicityEvaluator multiplicityEvaluator, IDataTypesConfigurationProvider dataTypesConfigurationProvider) { - this.dispatchers = new Dictionary + this.dispatchers = new Dictionary(StringComparer.Ordinal) { { Multiplicities.OneToOne, new OneToOneConditionEvalDispatcher(operatorEvalStrategyFactory, dataTypesConfigurationProvider) }, { Multiplicities.OneToMany, new OneToManyConditionEvalDispatcher(operatorEvalStrategyFactory, dataTypesConfigurationProvider) }, @@ -36,16 +36,6 @@ public IConditionEvalDispatcher GetEvalDispatcher(object leftOperand, Operators return this.dispatchers[multiplicity]; } - private bool OperatorSupportsOneMultiplicityLeftOperand(Operators @operator) - { - if (OperatorsMetadata.AllByOperator.TryGetValue(@operator, out var operatorMetadata)) - { - return operatorMetadata.SupportedMultiplicities.Any(x => x.Contains("one-to")); - } - - return false; - } - private void ThrowIfUnsupportedOperandsAndOperatorCombination(string combination) { if (!OperatorsMetadata.AllBySupportedCombination.ContainsKey(combination)) diff --git a/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs b/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs index b4e1eaf4..155e804a 100644 --- a/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs +++ b/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs @@ -7,7 +7,7 @@ namespace Rules.Framework.Evaluation.Compiled using Rules.Framework.Core.ConditionNodes; using Rules.Framework.Evaluation.Specification; - internal class CompiledConditionsEvalEngine : IConditionsEvalEngine + internal sealed class CompiledConditionsEvalEngine : IConditionsEvalEngine { private readonly IConditionsTreeCompiler conditionsTreeCompiler; private readonly IMultiplicityEvaluator multiplicityEvaluator; diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs index bcb12fe8..5e91474b 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using System.Text; - internal class CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + internal sealed class CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { public Expression BuildConditionExpression( Expression leftHandOperandExpression, diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs index a96873d8..2f61581e 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using System.Text; - internal class CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + internal sealed class CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { public Expression BuildConditionExpression( Expression leftHandOperandExpression, diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs index ce233367..7dbb93c5 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs @@ -6,7 +6,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Reflection; using System.Text; - internal class ContainsOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + internal sealed class ContainsOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { private static readonly MethodInfo stringContainsMethodInfo = typeof(string).GetMethod("Contains", new[] { typeof(string) }); diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs index fb104360..b5fbb8f3 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using System.Text; - internal class EndsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + internal sealed class EndsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { public Expression BuildConditionExpression( Expression leftHandOperandExpression, diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilder.cs index 1ef109bf..b8028c87 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilder.cs @@ -6,7 +6,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using System.Text; - internal class EqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + internal sealed class EqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { public Expression BuildConditionExpression( Expression leftHandOperandExpression, diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs index f7d1bef4..f8763e9a 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using System.Text; - internal class GreaterThanOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + internal sealed class GreaterThanOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { public Expression BuildConditionExpression( Expression leftHandOperandExpression, diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs index 051347d0..4ccc13ba 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using System.Text; - internal class GreaterThanOrEqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + internal sealed class GreaterThanOrEqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { public Expression BuildConditionExpression( Expression leftHandOperandExpression, diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilder.cs index 9b8122c0..fdf6a932 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilder.cs @@ -7,7 +7,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Reflection; using System.Text; - internal class InOneToManyConditionExpressionBuilder : IConditionExpressionBuilder + internal sealed class InOneToManyConditionExpressionBuilder : IConditionExpressionBuilder { private static readonly MethodInfo enumerableGenericContains = typeof(Enumerable).GetMethods().FirstOrDefault(m => m.Name == "Contains" && m.GetParameters().Length == 2); diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs index c4cc70dd..39beb74f 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using System.Text; - internal class LesserThanOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + internal sealed class LesserThanOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { public Expression BuildConditionExpression( Expression leftHandOperandExpression, diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs index 6408a26e..20180872 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using System.Text; - internal class LesserThanOrEqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + internal sealed class LesserThanOrEqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { public Expression BuildConditionExpression( Expression leftHandOperandExpression, diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs index 459b2bb9..8e38ac7b 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs @@ -6,7 +6,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Reflection; using System.Text; - internal class NotContainsOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + internal sealed class NotContainsOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { private static readonly MethodInfo stringContainsMethodInfo = typeof(string).GetMethod("Contains", new[] { typeof(string) }); diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilder.cs index 02e8118d..dee0d617 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilder.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using System.Text; - internal class NotEqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + internal sealed class NotEqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { public Expression BuildConditionExpression( Expression leftHandOperandExpression, diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs index e4eeb3a1..25bb2df2 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using System.Text; - internal class StartsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder + internal sealed class StartsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { public Expression BuildConditionExpression( Expression leftHandOperandExpression, diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionsTreeCompiler.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionsTreeCompiler.cs index 1042fb40..1acd01d1 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionsTreeCompiler.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionsTreeCompiler.cs @@ -7,7 +7,7 @@ namespace Rules.Framework.Evaluation.Compiled using System.Linq.Expressions; using System.Text; - internal class ConditionsTreeCompiler : IConditionsTreeCompiler + internal sealed class ConditionsTreeCompiler : IConditionsTreeCompiler { private static readonly Type dictionaryOfConditionTypeAndObjectType = typeof(IDictionary); private readonly IValueConditionNodeCompilerProvider valueConditionNodeCompilerProvider; diff --git a/src/Rules.Framework/Evaluation/Compiled/OneToManyValueConditionNodeCompiler.cs b/src/Rules.Framework/Evaluation/Compiled/OneToManyValueConditionNodeCompiler.cs index e2fc49b3..cf3e906d 100644 --- a/src/Rules.Framework/Evaluation/Compiled/OneToManyValueConditionNodeCompiler.cs +++ b/src/Rules.Framework/Evaluation/Compiled/OneToManyValueConditionNodeCompiler.cs @@ -10,7 +10,7 @@ namespace Rules.Framework.Evaluation.Compiled using System.Reflection; using System.Text; - internal class OneToManyValueConditionNodeCompiler : ValueConditionNodeCompilerBase, IValueConditionNodeCompiler + internal sealed class OneToManyValueConditionNodeCompiler : ValueConditionNodeCompilerBase, IValueConditionNodeCompiler { private readonly IConditionExpressionBuilderProvider conditionExpressionBuilderProvider; diff --git a/src/Rules.Framework/Evaluation/Compiled/OneToOneValueConditionNodeCompiler.cs b/src/Rules.Framework/Evaluation/Compiled/OneToOneValueConditionNodeCompiler.cs index 81505b39..8610ac59 100644 --- a/src/Rules.Framework/Evaluation/Compiled/OneToOneValueConditionNodeCompiler.cs +++ b/src/Rules.Framework/Evaluation/Compiled/OneToOneValueConditionNodeCompiler.cs @@ -7,7 +7,7 @@ namespace Rules.Framework.Evaluation.Compiled using System.Collections.Generic; using System.Linq.Expressions; - internal class OneToOneValueConditionNodeCompiler : ValueConditionNodeCompilerBase, IValueConditionNodeCompiler + internal sealed class OneToOneValueConditionNodeCompiler : ValueConditionNodeCompilerBase, IValueConditionNodeCompiler { private readonly IConditionExpressionBuilderProvider conditionExpressionBuilderProvider; diff --git a/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerProvider.cs b/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerProvider.cs index b32db669..1f689fe3 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerProvider.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerProvider.cs @@ -9,7 +9,7 @@ namespace Rules.Framework.Evaluation.Compiled using System.Linq; using System.Text; - internal class ValueConditionNodeCompilerProvider : IValueConditionNodeCompilerProvider + internal sealed class ValueConditionNodeCompilerProvider : IValueConditionNodeCompilerProvider { private readonly Dictionary compilers; @@ -17,7 +17,7 @@ public ValueConditionNodeCompilerProvider( IConditionExpressionBuilderProvider conditionExpressionBuilderProvider, IDataTypesConfigurationProvider dataTypesConfigurationProvider) { - this.compilers = new Dictionary + this.compilers = new Dictionary(StringComparer.Ordinal) { { Multiplicities.OneToOne, new OneToOneValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider) }, { Multiplicities.OneToMany, new OneToManyValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider) }, diff --git a/src/Rules.Framework/Evaluation/MultiplicityEvaluator.cs b/src/Rules.Framework/Evaluation/MultiplicityEvaluator.cs index e2126333..7428801a 100644 --- a/src/Rules.Framework/Evaluation/MultiplicityEvaluator.cs +++ b/src/Rules.Framework/Evaluation/MultiplicityEvaluator.cs @@ -3,7 +3,6 @@ namespace Rules.Framework.Evaluation using Rules.Framework.Core; using System; using System.Collections; - using Rules.Framework.Core; internal sealed class MultiplicityEvaluator : IMultiplicityEvaluator { From df63339505c15d8ba93fde2dfcfd39a82e67318c Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sat, 29 Oct 2022 12:59:37 +0100 Subject: [PATCH 05/54] refactor: rename classic conditions eval engine --- .../Builder/ConfiguredRulesEngineBuilder.cs | 2 +- ...sEvalEngine.cs => ClassicConditionsEvalEngine.cs} | 4 ++-- ...eTests.cs => ClassicConditionsEvalEngineTests.cs} | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) rename src/Rules.Framework/Evaluation/Classic/{ConditionsEvalEngine.cs => ClassicConditionsEvalEngine.cs} (95%) rename tests/Rules.Framework.Tests/Evaluation/Classic/{ConditionsEvalEngineTests.cs => ClassicConditionsEvalEngineTests.cs} (93%) diff --git a/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs b/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs index de1487fa..08fa6325 100644 --- a/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs +++ b/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs @@ -44,7 +44,7 @@ public RulesEngine Build() var conditionEvalDispatchProvider = new ConditionEvalDispatchProvider(operatorEvalStrategyFactory, multiplicityEvaluator, dataTypesConfigurationProvider); var deferredEval = new DeferredEval(conditionEvalDispatchProvider, this.rulesEngineOptions); var conditionsTreeAnalyzer = new ConditionsTreeAnalyzer(); - conditionsEvalEngine = new ConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); + conditionsEvalEngine = new ClassicConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); } var conditionTypeExtractor = new ConditionTypeExtractor(); diff --git a/src/Rules.Framework/Evaluation/Classic/ConditionsEvalEngine.cs b/src/Rules.Framework/Evaluation/Classic/ClassicConditionsEvalEngine.cs similarity index 95% rename from src/Rules.Framework/Evaluation/Classic/ConditionsEvalEngine.cs rename to src/Rules.Framework/Evaluation/Classic/ClassicConditionsEvalEngine.cs index fa4897d6..23803d99 100644 --- a/src/Rules.Framework/Evaluation/Classic/ConditionsEvalEngine.cs +++ b/src/Rules.Framework/Evaluation/Classic/ClassicConditionsEvalEngine.cs @@ -7,12 +7,12 @@ namespace Rules.Framework.Evaluation.Classic using Rules.Framework.Core.ConditionNodes; using Rules.Framework.Evaluation.Specification; - internal sealed class ConditionsEvalEngine : IConditionsEvalEngine + internal sealed class ClassicConditionsEvalEngine : IConditionsEvalEngine { private readonly IConditionsTreeAnalyzer conditionsTreeAnalyzer; private readonly IDeferredEval deferredEval; - public ConditionsEvalEngine( + public ClassicConditionsEvalEngine( IDeferredEval deferredEval, IConditionsTreeAnalyzer conditionsTreeAnalyzer) { diff --git a/tests/Rules.Framework.Tests/Evaluation/Classic/ConditionsEvalEngineTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ClassicConditionsEvalEngineTests.cs similarity index 93% rename from tests/Rules.Framework.Tests/Evaluation/Classic/ConditionsEvalEngineTests.cs rename to tests/Rules.Framework.Tests/Evaluation/Classic/ClassicConditionsEvalEngineTests.cs index 9d6dd13e..b233e1fa 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Classic/ConditionsEvalEngineTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ClassicConditionsEvalEngineTests.cs @@ -11,7 +11,7 @@ namespace Rules.Framework.Tests.Evaluation.Classic using Rules.Framework.Tests.TestStubs; using Xunit; - public class ConditionsEvalEngineTests + public class ClassicConditionsEvalEngineTests { [Fact] public void Eval_GivenComposedConditionNodeWithAndOperatorAndMissingConditionWithSearchMode_EvalsAndReturnsResult() @@ -49,7 +49,7 @@ public void Eval_GivenComposedConditionNodeWithAndOperatorAndMissingConditionWit .Throws(new NotImplementedException("Shouldn't have gotten any more deferred evals.")); var conditionsTreeAnalyzer = Mock.Of>(); - var sut = new ConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); + var sut = new ClassicConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); // Act bool actual = sut.Eval(composedConditionNode, conditions, evaluationOptions); @@ -95,7 +95,7 @@ public void Eval_GivenComposedConditionNodeWithAndOperatorWithExactMatch_EvalsAn .Throws(new NotImplementedException("Shouldn't have gotten any more deferred evals.")); var conditionsTreeAnalyzer = Mock.Of>(); - var sut = new ConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); + var sut = new ClassicConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); // Act bool actual = sut.Eval(composedConditionNode, conditions, evaluationOptions); @@ -131,7 +131,7 @@ public void Eval_GivenComposedConditionNodeWithEvalOperator_ThrowsNotSupportedEx var mockDeferredEval = new Mock(); var conditionsTreeAnalyzer = Mock.Of>(); - var sut = new ConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); + var sut = new ClassicConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); // Act var notSupportedException = Assert.Throws(() => sut.Eval(composedConditionNode, conditions, evaluationOptions)); @@ -177,7 +177,7 @@ public void Eval_GivenComposedConditionNodeWithOrOperatorWithExactMatch_EvalsAnd .Throws(new NotImplementedException("Shouldn't have gotten any more deferred evals.")); var conditionsTreeAnalyzer = Mock.Of>(); - var sut = new ConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); + var sut = new ClassicConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); // Act bool actual = sut.Eval(composedConditionNode, conditions, evaluationOptions); @@ -208,7 +208,7 @@ public void Eval_GivenComposedConditionNodeWithUnknownConditionNode_ThrowsNotSup var mockDeferredEval = new Mock(); var conditionsTreeAnalyzer = Mock.Of>(); - var sut = new ConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); + var sut = new ClassicConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); // Act var notSupportedException = Assert.Throws(() => sut.Eval(mockConditionNode.Object, conditions, evaluationOptions)); From 852ba1c93113b36d73f369d17c5f08f9779f4f04 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sat, 5 Nov 2022 19:03:09 +0000 Subject: [PATCH 06/54] feat: implement remaining multiplicities and operators --- .../ConditionNodes/ComposedConditionNode.cs | 2 +- ...sWithOneToOneConditionExpressionBuilder.cs | 28 +++++++++-- ...sWithOneToOneConditionExpressionBuilder.cs | 26 +++++++++- ...tainsOneToOneConditionExpressionBuilder.cs | 18 ++++++- ...sWithOneToOneConditionExpressionBuilder.cs | 28 +++++++++-- ...EqualOneToOneConditionExpressionBuilder.cs | 4 -- ...rThanOneToOneConditionExpressionBuilder.cs | 8 ++- ...EqualOneToOneConditionExpressionBuilder.cs | 8 ++- .../IConditionExpressionBuilder.cs | 1 - .../InOneToManyConditionExpressionBuilder.cs | 7 +-- ...rThanOneToOneConditionExpressionBuilder.cs | 8 ++- ...EqualOneToOneConditionExpressionBuilder.cs | 8 ++- ...tainsOneToOneConditionExpressionBuilder.cs | 20 ++++++-- ...sWithOneToOneConditionExpressionBuilder.cs | 2 +- ...EqualOneToOneConditionExpressionBuilder.cs | 3 -- ...sWithOneToOneConditionExpressionBuilder.cs | 2 +- ...sWithOneToOneConditionExpressionBuilder.cs | 28 +++++++++-- .../ManyToManyValueConditionNodeCompiler.cs | 40 +++++++++++++++ .../ManyToOneValueConditionNodeCompiler.cs | 40 +++++++++++++++ .../ValueConditionNodeCompilerBase.cs | 44 ++++++++++++----- .../ValueConditionNodeCompilerProvider.cs | 4 +- .../Evaluation/DataTypeConfiguration.cs | 8 +-- .../Extensions/LanguageOperator.cs | 12 +++++ .../Extensions/TypeExtensions.cs | 49 +++++++++++++++++++ 24 files changed, 345 insertions(+), 53 deletions(-) create mode 100644 src/Rules.Framework/Evaluation/Compiled/ManyToManyValueConditionNodeCompiler.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ManyToOneValueConditionNodeCompiler.cs create mode 100644 src/Rules.Framework/Extensions/LanguageOperator.cs create mode 100644 src/Rules.Framework/Extensions/TypeExtensions.cs diff --git a/src/Rules.Framework/Core/ConditionNodes/ComposedConditionNode.cs b/src/Rules.Framework/Core/ConditionNodes/ComposedConditionNode.cs index 4df8bf20..d29c6429 100644 --- a/src/Rules.Framework/Core/ConditionNodes/ComposedConditionNode.cs +++ b/src/Rules.Framework/Core/ConditionNodes/ComposedConditionNode.cs @@ -45,6 +45,6 @@ public ComposedConditionNode(LogicalOperators logicalOperator, IEnumerable /// public IConditionNode Clone() - => new ComposedConditionNode(this.LogicalOperator, this.ChildConditionNodes?.Select(cn => cn.Clone()).ToList().AsReadOnly()); + => new ComposedConditionNode(this.LogicalOperator, this.ChildConditionNodes.Select(cn => cn.Clone()).ToList().AsReadOnly()); } } \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs index 5e91474b..8f20e49a 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs @@ -1,15 +1,37 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { + using Rules.Framework.Core; using System; - using System.Collections.Generic; using System.Linq.Expressions; - using System.Text; + using System.Reflection; internal sealed class CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { + private static readonly Type booleanType = typeof(bool); + private static readonly MethodInfo endsWithMethodInfo = typeof(string) + .GetMethod(nameof(string.EndsWith), new[] { typeof(string), typeof(StringComparison) }); + public Expression BuildConditionExpression( Expression leftHandOperandExpression, Expression rightHandOperatorExpression, - DataTypeConfiguration dataTypeConfiguration) => throw new NotImplementedException(); + DataTypeConfiguration dataTypeConfiguration) + { + if (dataTypeConfiguration.DataType != DataTypes.String) + { + throw new NotSupportedException($"The operator '{Operators.CaseInsensitiveEndsWith}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + } + + var returnLabelTarget = Expression.Label(booleanType); + Expression notNullScenario = Expression.Call( + leftHandOperandExpression, + endsWithMethodInfo, + rightHandOperatorExpression, + Expression.Constant(StringComparison.InvariantCultureIgnoreCase)); + Expression ifNotNullExpression = Expression.IfThen( + Expression.NotEqual(leftHandOperandExpression, Expression.Constant(value: null)), + Expression.Return(returnLabelTarget, notNullScenario)); + + return Expression.Block(new[] { ifNotNullExpression, Expression.Label(returnLabelTarget, Expression.Constant(value: false)) }); + } } } diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs index 2f61581e..6f0a8f1d 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs @@ -1,15 +1,39 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { + using Rules.Framework.Core; using System; using System.Collections.Generic; using System.Linq.Expressions; + using System.Reflection; using System.Text; internal sealed class CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { + private static readonly Type booleanType = typeof(bool); + private static readonly MethodInfo endsWithMethodInfo = typeof(string) + .GetMethod(nameof(string.StartsWith), new[] { typeof(string), typeof(StringComparison) }); + public Expression BuildConditionExpression( Expression leftHandOperandExpression, Expression rightHandOperatorExpression, - DataTypeConfiguration dataTypeConfiguration) => throw new NotImplementedException(); + DataTypeConfiguration dataTypeConfiguration) + { + if (dataTypeConfiguration.DataType != DataTypes.String) + { + throw new NotSupportedException($"The operator '{Operators.CaseInsensitiveStartsWith}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + } + + var returnLabelTarget = Expression.Label(booleanType); + Expression notNullScenario = Expression.Call( + leftHandOperandExpression, + endsWithMethodInfo, + rightHandOperatorExpression, + Expression.Constant(StringComparison.InvariantCultureIgnoreCase)); + Expression ifNotNullExpression = Expression.IfThen( + Expression.NotEqual(leftHandOperandExpression, Expression.Constant(null)), + Expression.Return(returnLabelTarget, notNullScenario)); + + return Expression.Block(new[] { ifNotNullExpression, Expression.Label(returnLabelTarget, Expression.Constant(false)) }); + } } } diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs index 7dbb93c5..43c8b2da 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs @@ -1,5 +1,6 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { + using Rules.Framework.Core; using System; using System.Collections.Generic; using System.Linq.Expressions; @@ -8,6 +9,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders internal sealed class ContainsOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { + private static readonly Type booleanType = typeof(bool); private static readonly MethodInfo stringContainsMethodInfo = typeof(string).GetMethod("Contains", new[] { typeof(string) }); public Expression BuildConditionExpression( @@ -15,7 +17,21 @@ public Expression BuildConditionExpression( Expression rightHandOperatorExpression, DataTypeConfiguration dataTypeConfiguration) { - return Expression.Call(leftHandOperandExpression, stringContainsMethodInfo, rightHandOperatorExpression); + if (dataTypeConfiguration.DataType != DataTypes.String) + { + throw new NotSupportedException($"The operator '{Operators.Contains}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + } + + var returnLabelTarget = Expression.Label(booleanType); + Expression notNullScenario = Expression.Call( + leftHandOperandExpression, + stringContainsMethodInfo, + rightHandOperatorExpression); + Expression ifNotNullExpression = Expression.IfThen( + Expression.NotEqual(leftHandOperandExpression, Expression.Constant(value: null)), + Expression.Return(returnLabelTarget, notNullScenario)); + + return Expression.Block(new[] { ifNotNullExpression, Expression.Label(returnLabelTarget, Expression.Constant(value: false)) }); } } } diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs index b5fbb8f3..afb3cd5b 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs @@ -1,15 +1,37 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { + using Rules.Framework.Core; using System; - using System.Collections.Generic; using System.Linq.Expressions; - using System.Text; + using System.Reflection; internal sealed class EndsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { + private static readonly Type booleanType = typeof(bool); + private static readonly MethodInfo endsWithMethodInfo = typeof(string) + .GetMethod(nameof(string.EndsWith), new[] { typeof(string), typeof(StringComparison) }); + public Expression BuildConditionExpression( Expression leftHandOperandExpression, Expression rightHandOperatorExpression, - DataTypeConfiguration dataTypeConfiguration) => throw new NotImplementedException(); + DataTypeConfiguration dataTypeConfiguration) + { + if (dataTypeConfiguration.DataType != DataTypes.String) + { + throw new NotSupportedException($"The operator '{Operators.EndsWith}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + } + + var returnLabelTarget = Expression.Label(booleanType); + Expression notNullScenario = Expression.Call( + leftHandOperandExpression, + endsWithMethodInfo, + rightHandOperatorExpression, + Expression.Constant(StringComparison.InvariantCulture)); + Expression ifNotNullExpression = Expression.IfThen( + Expression.NotEqual(leftHandOperandExpression, Expression.Constant(value: null)), + Expression.Return(returnLabelTarget, notNullScenario)); + + return Expression.Block(new[] { ifNotNullExpression, Expression.Label(returnLabelTarget, Expression.Constant(value: false)) }); + } } } diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilder.cs index b8028c87..5cecde31 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilder.cs @@ -1,10 +1,6 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { - using Rules.Framework.Core; - using System; - using System.Collections.Generic; using System.Linq.Expressions; - using System.Text; internal sealed class EqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs index f8763e9a..5198d813 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs @@ -1,9 +1,8 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { + using Rules.Framework.Core; using System; - using System.Collections.Generic; using System.Linq.Expressions; - using System.Text; internal sealed class GreaterThanOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { @@ -12,6 +11,11 @@ public Expression BuildConditionExpression( Expression rightHandOperatorExpression, DataTypeConfiguration dataTypeConfiguration) { + if (!dataTypeConfiguration.Type.HasLanguageOperator(LanguageOperator.GreaterThan)) + { + throw new NotSupportedException($"The operator '{Operators.GreaterThan}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + } + return Expression.GreaterThan(leftHandOperandExpression, rightHandOperatorExpression); } } diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs index 4ccc13ba..07c81818 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs @@ -1,9 +1,8 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { + using Rules.Framework.Core; using System; - using System.Collections.Generic; using System.Linq.Expressions; - using System.Text; internal sealed class GreaterThanOrEqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { @@ -12,6 +11,11 @@ public Expression BuildConditionExpression( Expression rightHandOperatorExpression, DataTypeConfiguration dataTypeConfiguration) { + if (!dataTypeConfiguration.Type.HasLanguageOperator(LanguageOperator.GreaterThanOrEqual)) + { + throw new NotSupportedException($"The operator '{Operators.GreaterThanOrEqual}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + } + return Expression.GreaterThanOrEqual(leftHandOperandExpression, rightHandOperatorExpression); } } diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilder.cs index 37ad1a5f..f598c881 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilder.cs @@ -1,6 +1,5 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { - using Rules.Framework.Core; using Rules.Framework.Evaluation; using System.Linq.Expressions; diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilder.cs index fdf6a932..ebdfd3b0 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilder.cs @@ -1,15 +1,16 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { using System; - using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; - using System.Text; internal sealed class InOneToManyConditionExpressionBuilder : IConditionExpressionBuilder { - private static readonly MethodInfo enumerableGenericContains = typeof(Enumerable).GetMethods().FirstOrDefault(m => m.Name == "Contains" && m.GetParameters().Length == 2); + private static readonly MethodInfo enumerableGenericContains + = typeof(Enumerable) + .GetMethods() + .FirstOrDefault(m => string.Equals(m.Name, "Contains", StringComparison.Ordinal) && m.GetParameters().Length == 2); public Expression BuildConditionExpression( Expression leftHandOperandExpression, diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs index 39beb74f..14e0c4fd 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs @@ -1,9 +1,8 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { + using Rules.Framework.Core; using System; - using System.Collections.Generic; using System.Linq.Expressions; - using System.Text; internal sealed class LesserThanOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { @@ -12,6 +11,11 @@ public Expression BuildConditionExpression( Expression rightHandOperatorExpression, DataTypeConfiguration dataTypeConfiguration) { + if (!dataTypeConfiguration.Type.HasLanguageOperator(LanguageOperator.LessThan)) + { + throw new NotSupportedException($"The operator '{Operators.LesserThan}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + } + return Expression.LessThan(leftHandOperandExpression, rightHandOperatorExpression); } } diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs index 20180872..673dcc6d 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs @@ -1,9 +1,8 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { + using Rules.Framework.Core; using System; - using System.Collections.Generic; using System.Linq.Expressions; - using System.Text; internal sealed class LesserThanOrEqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { @@ -12,6 +11,11 @@ public Expression BuildConditionExpression( Expression rightHandOperatorExpression, DataTypeConfiguration dataTypeConfiguration) { + if (!dataTypeConfiguration.Type.HasLanguageOperator(LanguageOperator.LessThanOrEqual)) + { + throw new NotSupportedException($"The operator '{Operators.LesserThanOrEqual}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + } + return Expression.LessThanOrEqual(leftHandOperandExpression, rightHandOperatorExpression); } } diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs index 8e38ac7b..03c9c473 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs @@ -1,13 +1,13 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { + using Rules.Framework.Core; using System; - using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; - using System.Text; internal sealed class NotContainsOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { + private static readonly Type booleanType = typeof(bool); private static readonly MethodInfo stringContainsMethodInfo = typeof(string).GetMethod("Contains", new[] { typeof(string) }); public Expression BuildConditionExpression( @@ -15,7 +15,21 @@ public Expression BuildConditionExpression( Expression rightHandOperatorExpression, DataTypeConfiguration dataTypeConfiguration) { - return Expression.Not(Expression.Call(leftHandOperandExpression, stringContainsMethodInfo, rightHandOperatorExpression)); + if (dataTypeConfiguration.DataType != DataTypes.String) + { + throw new NotSupportedException($"The operator '{Operators.NotContains}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + } + + var returnLabelTarget = Expression.Label(booleanType); + Expression notNullScenario = Expression.Not(Expression.Call( + leftHandOperandExpression, + stringContainsMethodInfo, + rightHandOperatorExpression)); + Expression ifNotNullExpression = Expression.IfThen( + Expression.NotEqual(leftHandOperandExpression, Expression.Constant(value: null)), + Expression.Return(returnLabelTarget, notNullScenario)); + + return Expression.Block(new[] { ifNotNullExpression, Expression.Label(returnLabelTarget, Expression.Constant(value: false)) }); } } } diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilder.cs index bc01c933..681cb037 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilder.cs @@ -1,9 +1,9 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { + using Rules.Framework.Core; using System; using System.Linq.Expressions; using System.Reflection; - using Rules.Framework.Core; internal class NotEndsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilder.cs index dee0d617..be5659da 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilder.cs @@ -1,9 +1,6 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { - using System; - using System.Collections.Generic; using System.Linq.Expressions; - using System.Text; internal sealed class NotEqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilder.cs index ad356880..3da4cec7 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilder.cs @@ -1,9 +1,9 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { + using Rules.Framework.Core; using System; using System.Linq.Expressions; using System.Reflection; - using Rules.Framework.Core; internal class NotStartsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs index 25bb2df2..a547be1f 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs @@ -1,15 +1,37 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { + using Rules.Framework.Core; using System; - using System.Collections.Generic; using System.Linq.Expressions; - using System.Text; + using System.Reflection; internal sealed class StartsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { + private static readonly Type booleanType = typeof(bool); + private static readonly MethodInfo startsWithMethodInfo = typeof(string) + .GetMethod(nameof(string.StartsWith), new[] { typeof(string), typeof(StringComparison) }); + public Expression BuildConditionExpression( Expression leftHandOperandExpression, Expression rightHandOperatorExpression, - DataTypeConfiguration dataTypeConfiguration) => throw new NotImplementedException(); + DataTypeConfiguration dataTypeConfiguration) + { + if (dataTypeConfiguration.DataType != DataTypes.String) + { + throw new NotSupportedException($"The operator '{Operators.StartsWith}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + } + + var returnLabelTarget = Expression.Label(booleanType); + Expression notNullScenario = Expression.Call( + leftHandOperandExpression, + startsWithMethodInfo, + rightHandOperatorExpression, + Expression.Constant(StringComparison.InvariantCulture)); + Expression ifNotNullExpression = Expression.IfThen( + Expression.NotEqual(leftHandOperandExpression, Expression.Constant(value: null)), + Expression.Return(returnLabelTarget, notNullScenario)); + + return Expression.Block(new[] { ifNotNullExpression, Expression.Label(returnLabelTarget, Expression.Constant(value: false)) }); + } } } diff --git a/src/Rules.Framework/Evaluation/Compiled/ManyToManyValueConditionNodeCompiler.cs b/src/Rules.Framework/Evaluation/Compiled/ManyToManyValueConditionNodeCompiler.cs new file mode 100644 index 00000000..d7af8ffc --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ManyToManyValueConditionNodeCompiler.cs @@ -0,0 +1,40 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using Rules.Framework.Core.ConditionNodes; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using System.Text; + + internal sealed class ManyToManyValueConditionNodeCompiler : ValueConditionNodeCompilerBase, IValueConditionNodeCompiler + { + private readonly IConditionExpressionBuilderProvider conditionExpressionBuilderProvider; + + public ManyToManyValueConditionNodeCompiler( + IConditionExpressionBuilderProvider conditionExpressionBuilderProvider, + IDataTypesConfigurationProvider dataTypesConfigurationProvider) + : base(dataTypesConfigurationProvider) + { + this.conditionExpressionBuilderProvider = conditionExpressionBuilderProvider; + } + + public Func, bool> Compile( + ValueConditionNode valueConditionNode, + ParameterExpression parameterExpression) + { + DataTypeConfiguration dataTypeConfiguration = this.GetDataTypeConfiguration(valueConditionNode.DataType); + + var getConditionExpression = CreateGetConditionExpression(valueConditionNode.ConditionType, parameterExpression, dataTypeConfiguration); + + var leftOperandExpression = CreateConvertedArrayExpression(getConditionExpression, dataTypeConfiguration.Type); + + var rightOperandExpression = CreateConvertedArrayExpression(valueConditionNode.Operand, dataTypeConfiguration.Type); + + var conditionExpressionBuilder = this.conditionExpressionBuilderProvider.GetConditionExpressionBuilderFor(valueConditionNode.Operator, Multiplicities.ManyToMany); + var conditionExpression = conditionExpressionBuilder.BuildConditionExpression(leftOperandExpression, rightOperandExpression, dataTypeConfiguration); + + return Expression.Lambda, bool>>(conditionExpression, parameterExpression).Compile(); + } + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/ManyToOneValueConditionNodeCompiler.cs b/src/Rules.Framework/Evaluation/Compiled/ManyToOneValueConditionNodeCompiler.cs new file mode 100644 index 00000000..1e82ef2a --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ManyToOneValueConditionNodeCompiler.cs @@ -0,0 +1,40 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using Rules.Framework.Core.ConditionNodes; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using System.Text; + + internal sealed class ManyToOneValueConditionNodeCompiler : ValueConditionNodeCompilerBase, IValueConditionNodeCompiler + { + private readonly IConditionExpressionBuilderProvider conditionExpressionBuilderProvider; + + public ManyToOneValueConditionNodeCompiler( + IConditionExpressionBuilderProvider conditionExpressionBuilderProvider, + IDataTypesConfigurationProvider dataTypesConfigurationProvider) + : base(dataTypesConfigurationProvider) + { + this.conditionExpressionBuilderProvider = conditionExpressionBuilderProvider; + } + + public Func, bool> Compile( + ValueConditionNode valueConditionNode, + ParameterExpression parameterExpression) + { + DataTypeConfiguration dataTypeConfiguration = this.GetDataTypeConfiguration(valueConditionNode.DataType); + + var getConditionExpression = CreateGetConditionExpression(valueConditionNode.ConditionType, parameterExpression, dataTypeConfiguration); + + var leftOperandExpression = CreateConvertedArrayExpression(getConditionExpression, dataTypeConfiguration.Type); + + var rightOperandExpression = CreateConvertedObjectExpression(valueConditionNode.Operand, dataTypeConfiguration.Type, dataTypeConfiguration.Default); + + var conditionExpressionBuilder = this.conditionExpressionBuilderProvider.GetConditionExpressionBuilderFor(valueConditionNode.Operator, Multiplicities.ManyToOne); + var conditionExpression = conditionExpressionBuilder.BuildConditionExpression(leftOperandExpression, rightOperandExpression, dataTypeConfiguration); + + return Expression.Lambda, bool>>(conditionExpression, parameterExpression).Compile(); + } + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerBase.cs b/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerBase.cs index 6b5a6360..7f6eaddb 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerBase.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerBase.cs @@ -14,11 +14,15 @@ namespace Rules.Framework.Evaluation.Compiled internal class ValueConditionNodeCompilerBase { - + protected static readonly MethodInfo arrayEmptyMethodInfo = typeof(Array) + .GetMethod( + nameof(Array.Empty), + Array.Empty()); protected static readonly MethodInfo changeTypeMethodInfo = typeof(Convert) - .GetMethod( - nameof(Convert.ChangeType), - new[] { typeof(object), typeof(Type), typeof(IFormatProvider) }); + .GetMethod( + nameof(Convert.ChangeType), + new[] { typeof(object), typeof(Type), typeof(IFormatProvider) }); + protected static readonly Type enumerableType = typeof(IEnumerable<>); protected static readonly Type systemType = typeof(Type); protected static readonly Type formatProviderType = typeof(IFormatProvider); protected static readonly Type objectType = typeof(object); @@ -36,24 +40,42 @@ protected static object ConvertToDataType(object operand, string paramName, Type return Convert.ChangeType(operand ?? dataDefault, dataType, CultureInfo.InvariantCulture); } - catch (InvalidCastException ice) + catch (Exception ex) { - throw new ArgumentException($"Parameter value or contained value is not convertible to {dataType.Name}.", paramName, ice); + throw new ArgumentException($"Parameter value or contained value is not convertible to {dataType.Name}.", paramName, ex); } } protected static Expression CreateGetConditionExpression(TConditionType conditionType, ParameterExpression parameterExpression, DataTypeConfiguration dataTypeConfiguration) { - var returnTargetExpression = Expression.Label(typeof(object)); - var outVariableExpression = Expression.Variable(typeof(object), "conditionValue"); + var returnTargetExpression = Expression.Label(objectType); + var outVariableExpression = Expression.Variable(objectType, "conditionValue"); var dictionaryTryGetValueMethodInfo = typeof(IDictionary).GetMethod("TryGetValue"); var conditionTypeConstantExpression = Expression.Constant(conditionType, typeof(TConditionType)); var testExpression = Expression.Call(parameterExpression, dictionaryTryGetValueMethodInfo, conditionTypeConstantExpression, outVariableExpression); - var defaultValueExpression = Expression.Constant(dataTypeConfiguration.Default, typeof(object)); + var defaultValueExpression = Expression.Constant(dataTypeConfiguration.Default, objectType); var ifThenExpression = Expression.IfThen(testExpression, Expression.Return(returnTargetExpression, outVariableExpression)); return Expression.Block(new[] { outVariableExpression }, new Expression[] { ifThenExpression, Expression.Label(returnTargetExpression, defaultValueExpression) }); } + protected static Expression CreateConvertedArrayExpression(Expression operandExpression, Type dataType) + { + var testExpression = Expression.NotEqual(operandExpression, Expression.Constant(null)); + var returnTargetLabelExpression = Expression.Label(objectType); + var defaultValueExpression = Expression.Constant( + arrayEmptyMethodInfo + .MakeGenericMethod(dataType) + .Invoke(obj: null, parameters: null), + objectType); + var ifThenElseExpression = Expression.IfThenElse( + testExpression, + Expression.Return(returnTargetLabelExpression, operandExpression), + Expression.Return(returnTargetLabelExpression, defaultValueExpression)); + var nullCoalesceBlock = Expression.Block(ifThenElseExpression, Expression.Label(returnTargetLabelExpression, defaultValueExpression)); + + return Expression.ConvertChecked(nullCoalesceBlock, enumerableType.MakeGenericType(dataType)); + } + protected static Expression CreateConvertedArrayExpression(object operand, Type dataType) { IEnumerable operandAsTypedEnumerable = ConvertToTypedEnumerable(operand, nameof(operand), dataType); @@ -64,7 +86,7 @@ protected static Expression CreateConvertedArrayExpression(object operand, Type protected static Expression CreateConvertedObjectExpression(Expression operandExpression, Type dataType, object dataTypeDefault) { var testExpression = Expression.NotEqual(operandExpression, Expression.Constant(null)); - var returnTargetLabelExpression = Expression.Label(typeof(object)); + var returnTargetLabelExpression = Expression.Label(objectType); var defaultValueExpression = Expression.Constant(dataTypeDefault, objectType); var ifThenElseExpression = Expression.IfThenElse( testExpression, @@ -88,7 +110,7 @@ protected static Expression CreateConvertedObjectExpression(object operand, Type protected static IEnumerable ConvertToTypedEnumerable(object operand, string paramName, Type dataType) { - if (operand is IEnumerable enumerable) + if (operand is not string && operand is IEnumerable enumerable) { return enumerable.Cast().Select(o => ConvertToDataType(o, paramName, dataType, null)); } diff --git a/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerProvider.cs b/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerProvider.cs index 1f689fe3..70611003 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerProvider.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerProvider.cs @@ -21,8 +21,8 @@ public ValueConditionNodeCompilerProvider( { { Multiplicities.OneToOne, new OneToOneValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider) }, { Multiplicities.OneToMany, new OneToManyValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider) }, - { Multiplicities.ManyToOne, null }, - { Multiplicities.ManyToMany, null } + { Multiplicities.ManyToOne, new ManyToOneValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider) }, + { Multiplicities.ManyToMany, new ManyToManyValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider) } }; } diff --git a/src/Rules.Framework/Evaluation/DataTypeConfiguration.cs b/src/Rules.Framework/Evaluation/DataTypeConfiguration.cs index a39de6a9..718f63ca 100644 --- a/src/Rules.Framework/Evaluation/DataTypeConfiguration.cs +++ b/src/Rules.Framework/Evaluation/DataTypeConfiguration.cs @@ -9,11 +9,11 @@ private DataTypeConfiguration() { } - public DataTypes DataType { get; set; } + public DataTypes DataType { get; private set; } - public object Default { get; set; } + public object Default { get; private set; } - public Type Type { get; set; } + public Type Type { get; private set; } public static DataTypeConfiguration Create(DataTypes dataType, Type type, object @default) { @@ -26,7 +26,7 @@ public static DataTypeConfiguration Create(DataTypes dataType, Type type, object { DataType = dataType, Default = @default, - Type = type + Type = type, }; } } diff --git a/src/Rules.Framework/Extensions/LanguageOperator.cs b/src/Rules.Framework/Extensions/LanguageOperator.cs new file mode 100644 index 00000000..5a4b9d57 --- /dev/null +++ b/src/Rules.Framework/Extensions/LanguageOperator.cs @@ -0,0 +1,12 @@ +namespace System +{ + internal enum LanguageOperator + { + Equal = 1, + NotEqual = 2, + GreaterThan = 3, + GreaterThanOrEqual = 4, + LessThan = 5, + LessThanOrEqual = 6 + } +} diff --git a/src/Rules.Framework/Extensions/TypeExtensions.cs b/src/Rules.Framework/Extensions/TypeExtensions.cs new file mode 100644 index 00000000..3bd1647c --- /dev/null +++ b/src/Rules.Framework/Extensions/TypeExtensions.cs @@ -0,0 +1,49 @@ +namespace System +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Text; + + internal static class TypeExtensions + { + private static readonly ConcurrentDictionary> languageOperatorsSupportByType + = new ConcurrentDictionary>(); + + public static bool HasLanguageOperator(this Type type, LanguageOperator languageOperator) + { + var languageOperatorsByType = languageOperatorsSupportByType.GetOrAdd(type, (t) => new Dictionary() + { + { LanguageOperator.Equal, t.HasLanguageOperatorInternal(LanguageOperator.Equal) }, + { LanguageOperator.NotEqual, t.HasLanguageOperatorInternal(LanguageOperator.NotEqual) }, + { LanguageOperator.GreaterThan, t.HasLanguageOperatorInternal(LanguageOperator.GreaterThan) }, + { LanguageOperator.GreaterThanOrEqual, t.HasLanguageOperatorInternal(LanguageOperator.GreaterThanOrEqual) }, + { LanguageOperator.LessThan, t.HasLanguageOperatorInternal(LanguageOperator.LessThan) }, + { LanguageOperator.LessThanOrEqual, t.HasLanguageOperatorInternal(LanguageOperator.LessThanOrEqual) }, + }); + + if (languageOperatorsByType.TryGetValue(languageOperator, out bool result)) + { + return result; + } + + return false; + } + + private static bool HasLanguageOperatorInternal(this Type type, LanguageOperator languageOperator) + => ((TypeInfo)type).GetRuntimeMethods().Any(m => m.Name.Contains(languageOperator.ToOperatorMethodName())); + + private static string ToOperatorMethodName(this LanguageOperator languageOperator) => languageOperator switch + { + LanguageOperator.Equal => "op_Equality", + LanguageOperator.NotEqual => "op_Inequality", + LanguageOperator.GreaterThan => "op_GreaterThan", + LanguageOperator.GreaterThanOrEqual => "op_GreaterThanOrEqual", + LanguageOperator.LessThan => "op_LessThan", + LanguageOperator.LessThanOrEqual => "op_LessThanOrEqual", + _ => throw new NotSupportedException() + }; + } +} From 452c044f863c4352b663cc6dd493cc12b1737864 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sat, 5 Nov 2022 19:03:57 +0000 Subject: [PATCH 07/54] test: add new unit tests covering compilation functionalities --- .../Core/ConditionNodePropertiesTests.cs | 38 +++++ .../BooleanConditionNodeTests.cs | 30 ++++ .../ComposedConditionNodeTests.cs | 33 ++++ .../DecimalConditionNodeTests.cs | 30 ++++ .../IntegerConditionNodeTests.cs | 30 ++++ .../StringConditionNodeTests.cs | 30 ++++ ...OneToOneConditionExpressionBuilderTests.cs | 72 ++++++++ ...OneToOneConditionExpressionBuilderTests.cs | 70 ++++++++ ...ConditionExpressionBuilderProviderTests.cs | 51 ++++++ ...OneToOneConditionExpressionBuilderTests.cs | 69 ++++++++ ...OneToOneConditionExpressionBuilderTests.cs | 66 ++++++++ ...OneToOneConditionExpressionBuilderTests.cs | 42 +++++ ...OneToOneConditionExpressionBuilderTests.cs | 66 ++++++++ ...OneToOneConditionExpressionBuilderTests.cs | 66 ++++++++ ...neToManyConditionExpressionBuilderTests.cs | 43 +++++ ...OneToOneConditionExpressionBuilderTests.cs | 66 ++++++++ ...OneToOneConditionExpressionBuilderTests.cs | 66 ++++++++ ...OneToOneConditionExpressionBuilderTests.cs | 69 ++++++++ ...OneToOneConditionExpressionBuilderTests.cs | 66 ++++++++ ...OneToOneConditionExpressionBuilderTests.cs | 42 +++++ ...OneToOneConditionExpressionBuilderTests.cs | 70 ++++++++ ...OneToOneConditionExpressionBuilderTests.cs | 70 ++++++++ .../Compiled/ConditionsTreeCompilerTests.cs | 85 ++++++++++ ...nyToManyValueConditionNodeCompilerTests.cs | 68 ++++++++ ...anyToOneValueConditionNodeCompilerTests.cs | 68 ++++++++ ...neToManyValueConditionNodeCompilerTests.cs | 109 ++++++++++++ ...OneToOneValueConditionNodeCompilerTests.cs | 159 ++++++++++++++++++ ...ValueConditionNodeCompilerProviderTests.cs | 59 +++++++ .../Evaluation/DataTypeConfigurationTests.cs | 49 ++++++ .../DataTypesConfigurationProviderTests.cs | 51 ++++++ .../Evaluation/EvaluationOptionsTests.cs | 106 ++++++++++++ .../Evaluation/MultiplicityEvaluatorTests.cs | 5 + .../Evaluation/OperatorMetadataTests.cs | 5 + .../Extensions/TypeExtensionsTests.cs | 43 +++++ 34 files changed, 1992 insertions(+) create mode 100644 tests/Rules.Framework.Tests/Core/ConditionNodePropertiesTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProviderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionsTreeCompilerTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeCompilerTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeCompilerTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeCompilerTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeCompilerTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ValueConditionNodeCompilerProviderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/DataTypeConfigurationTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/DataTypesConfigurationProviderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/EvaluationOptionsTests.cs create mode 100644 tests/Rules.Framework.Tests/Extensions/TypeExtensionsTests.cs diff --git a/tests/Rules.Framework.Tests/Core/ConditionNodePropertiesTests.cs b/tests/Rules.Framework.Tests/Core/ConditionNodePropertiesTests.cs new file mode 100644 index 00000000..5a77c2ef --- /dev/null +++ b/tests/Rules.Framework.Tests/Core/ConditionNodePropertiesTests.cs @@ -0,0 +1,38 @@ +namespace Rules.Framework.Tests.Core +{ + using FluentAssertions; + using Rules.Framework.Core; + using System; + using Xunit; + + public class ConditionNodePropertiesTests + { + [Fact] + public void GetCompiledDelegateKey_GivenMultiplicityString_ReturnsString() + { + // Arrange + string multiplicity = "test"; + string expected = "_compiled_test"; + + // Act + string actual = ConditionNodeProperties.GetCompiledDelegateKey(multiplicity); + + // Assert + actual.Should().Be(expected); + } + + [Fact] + public void GetCompiledDelegateKey_GivenEmptyMultiplicityString_ReturnsString() + { + // Arrange + string multiplicity = ""; + + // Act + ArgumentException argumentException = Assert.Throws(() => ConditionNodeProperties.GetCompiledDelegateKey(multiplicity)); + + // Assert + argumentException.Should().NotBeNull(); + argumentException.ParamName.Should().Be("multiplicity"); + } + } +} diff --git a/tests/Rules.Framework.Tests/Core/ConditionNodes/BooleanConditionNodeTests.cs b/tests/Rules.Framework.Tests/Core/ConditionNodes/BooleanConditionNodeTests.cs index 4226e8df..eb9a1434 100644 --- a/tests/Rules.Framework.Tests/Core/ConditionNodes/BooleanConditionNodeTests.cs +++ b/tests/Rules.Framework.Tests/Core/ConditionNodes/BooleanConditionNodeTests.cs @@ -34,5 +34,35 @@ public void Init_GivenSetupWithBooleanValue_ReturnsSettedValues() actualLogicalOperator.Should().Be(expectedLogicalOperator); actualDataType.Should().Be(expectedDataType); } + + [Fact] + public void Clone_NoConditions_ReturnsCloneInstance() + { + // Arrange + ConditionType expectedConditionType = ConditionType.IsoCountryCode; + Operators expectedOperator = Operators.NotEqual; + bool expectedOperand = false; + LogicalOperators expectedLogicalOperator = LogicalOperators.Eval; + DataTypes expectedDataType = DataTypes.Boolean; + + BooleanConditionNode sut = new BooleanConditionNode(expectedConditionType, expectedOperator, expectedOperand); + sut.Properties["test"] = "test"; + + // Act + IConditionNode actual = sut.Clone(); + + // Assert + actual.Should() + .NotBeNull() + .And + .BeOfType>(); + BooleanConditionNode valueConditionNode = actual.As>(); + valueConditionNode.ConditionType.Should().Be(expectedConditionType); + valueConditionNode.DataType.Should().Be(expectedDataType); + valueConditionNode.LogicalOperator.Should().Be(expectedLogicalOperator); + valueConditionNode.Operator.Should().Be(expectedOperator); + valueConditionNode.Operand.Should().Be(expectedOperand); + valueConditionNode.Properties.Should().BeEmpty(); + } } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Core/ConditionNodes/ComposedConditionNodeTests.cs b/tests/Rules.Framework.Tests/Core/ConditionNodes/ComposedConditionNodeTests.cs index 0a8a0a14..c7880288 100644 --- a/tests/Rules.Framework.Tests/Core/ConditionNodes/ComposedConditionNodeTests.cs +++ b/tests/Rules.Framework.Tests/Core/ConditionNodes/ComposedConditionNodeTests.cs @@ -31,5 +31,38 @@ public void ComposedConditionNode_Init_GivenSetupWithChildConditionsAndLogicalOp actualLogicalOperator.Should().Be(expectedLogicalOperator); actualChildConditionNodes.Should().NotBeNull().And.BeSameAs(expectedChildConditionNodes); } + + [Fact] + public void Clone_NoConditions_ReturnsCloneInstance() + { + // Arrange + LogicalOperators expectedLogicalOperator = LogicalOperators.Eval; + IConditionNode conditionNode1 = Mock.Of>(); + IConditionNode conditionNode2 = Mock.Of>(); + Mock.Get(conditionNode1) + .Setup(x => x.Clone()) + .Returns(conditionNode1); + Mock.Get(conditionNode2) + .Setup(x => x.Clone()) + .Returns(conditionNode2); + + IEnumerable > expectedChildConditionNodes = new IConditionNode[] { conditionNode1, conditionNode2 }; + + ComposedConditionNode sut = new ComposedConditionNode(expectedLogicalOperator, expectedChildConditionNodes); + sut.Properties["test"] = "test"; + + // Act + IConditionNode actual = sut.Clone(); + + // Assert + actual.Should() + .NotBeNull() + .And + .BeOfType>(); + ComposedConditionNode valueConditionNode = actual.As>(); + valueConditionNode.LogicalOperator.Should().Be(expectedLogicalOperator); + valueConditionNode.ChildConditionNodes.Should().BeEquivalentTo(expectedChildConditionNodes); + valueConditionNode.Properties.Should().BeEmpty(); + } } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Core/ConditionNodes/DecimalConditionNodeTests.cs b/tests/Rules.Framework.Tests/Core/ConditionNodes/DecimalConditionNodeTests.cs index 9711560f..29a37289 100644 --- a/tests/Rules.Framework.Tests/Core/ConditionNodes/DecimalConditionNodeTests.cs +++ b/tests/Rules.Framework.Tests/Core/ConditionNodes/DecimalConditionNodeTests.cs @@ -34,5 +34,35 @@ public void Init_GivenSetupWithDecimalValue_ReturnsSettedValues() actualLogicalOperator.Should().Be(expectedLogicalOperator); actualDataType.Should().Be(expectedDataType); } + + [Fact] + public void Clone_NoConditions_ReturnsCloneInstance() + { + // Arrange + ConditionType expectedConditionType = ConditionType.PluviosityRate; + Operators expectedOperator = Operators.NotEqual; + decimal expectedOperand = 5682.2654m; + LogicalOperators expectedLogicalOperator = LogicalOperators.Eval; + DataTypes expectedDataType = DataTypes.Decimal; + + DecimalConditionNode sut = new DecimalConditionNode(expectedConditionType, expectedOperator, expectedOperand); + sut.Properties["test"] = "test"; + + // Act + IConditionNode actual = sut.Clone(); + + // Assert + actual.Should() + .NotBeNull() + .And + .BeOfType>(); + DecimalConditionNode valueConditionNode = actual.As>(); + valueConditionNode.ConditionType.Should().Be(expectedConditionType); + valueConditionNode.DataType.Should().Be(expectedDataType); + valueConditionNode.LogicalOperator.Should().Be(expectedLogicalOperator); + valueConditionNode.Operator.Should().Be(expectedOperator); + valueConditionNode.Operand.Should().Be(expectedOperand); + valueConditionNode.Properties.Should().BeEmpty(); + } } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Core/ConditionNodes/IntegerConditionNodeTests.cs b/tests/Rules.Framework.Tests/Core/ConditionNodes/IntegerConditionNodeTests.cs index e9006b37..48a4dfe1 100644 --- a/tests/Rules.Framework.Tests/Core/ConditionNodes/IntegerConditionNodeTests.cs +++ b/tests/Rules.Framework.Tests/Core/ConditionNodes/IntegerConditionNodeTests.cs @@ -34,5 +34,35 @@ public void Init_GivenSetupWithIntegerValue_ReturnsSettedValues() actualLogicalOperator.Should().Be(expectedLogicalOperator); actualDataType.Should().Be(expectedDataType); } + + [Fact] + public void Clone_NoConditions_ReturnsCloneInstance() + { + // Arrange + ConditionType expectedConditionType = ConditionType.IsoCountryCode; + Operators expectedOperator = Operators.NotEqual; + int expectedOperand = 1616; + LogicalOperators expectedLogicalOperator = LogicalOperators.Eval; + DataTypes expectedDataType = DataTypes.Integer; + + IntegerConditionNode sut = new IntegerConditionNode(expectedConditionType, expectedOperator, expectedOperand); + sut.Properties["test"] = "test"; + + // Act + IConditionNode actual = sut.Clone(); + + // Assert + actual.Should() + .NotBeNull() + .And + .BeOfType>(); + IntegerConditionNode valueConditionNode = actual.As>(); + valueConditionNode.ConditionType.Should().Be(expectedConditionType); + valueConditionNode.DataType.Should().Be(expectedDataType); + valueConditionNode.LogicalOperator.Should().Be(expectedLogicalOperator); + valueConditionNode.Operator.Should().Be(expectedOperator); + valueConditionNode.Operand.Should().Be(expectedOperand); + valueConditionNode.Properties.Should().BeEmpty(); + } } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Core/ConditionNodes/StringConditionNodeTests.cs b/tests/Rules.Framework.Tests/Core/ConditionNodes/StringConditionNodeTests.cs index 9546631a..45a75645 100644 --- a/tests/Rules.Framework.Tests/Core/ConditionNodes/StringConditionNodeTests.cs +++ b/tests/Rules.Framework.Tests/Core/ConditionNodes/StringConditionNodeTests.cs @@ -34,5 +34,35 @@ public void Init_GivenSetupWithStringValue_ReturnsSettedValues() actualLogicalOperator.Should().Be(expectedLogicalOperator); actualDataType.Should().Be(expectedDataType); } + + [Fact] + public void Clone_NoConditions_ReturnsCloneInstance() + { + // Arrange + ConditionType expectedConditionType = ConditionType.IsoCountryCode; + Operators expectedOperator = Operators.NotEqual; + string expectedOperand = "Such operand, much wow."; + LogicalOperators expectedLogicalOperator = LogicalOperators.Eval; + DataTypes expectedDataType = DataTypes.String; + + StringConditionNode sut = new StringConditionNode(expectedConditionType, expectedOperator, expectedOperand); + sut.Properties["test"] = "test"; + + // Act + IConditionNode actual = sut.Clone(); + + // Assert + actual.Should() + .NotBeNull() + .And + .BeOfType>(); + StringConditionNode valueConditionNode = actual.As>(); + valueConditionNode.ConditionType.Should().Be(expectedConditionType); + valueConditionNode.DataType.Should().Be(expectedDataType); + valueConditionNode.LogicalOperator.Should().Be(expectedLogicalOperator); + valueConditionNode.Operator.Should().Be(expectedOperator); + valueConditionNode.Operand.Should().Be(expectedOperand); + valueConditionNode.Properties.Should().BeEmpty(); + } } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilderTests.cs new file mode 100644 index 00000000..e4504056 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilderTests.cs @@ -0,0 +1,72 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders +{ + using FluentAssertions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using System.Runtime.CompilerServices; + using System.Text; + using System.Threading.Tasks; + using Xunit; + + public class CaseInsensitiveEndsWithOneToOneConditionExpressionBuilderTests + { + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() + { + // Arrange + + ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); + Expression rightHandExpression = Expression.Constant("fox", typeof(string)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + + CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder caseInsensitiveEndsWithOneToOneConditionExpressionBuilder + = new CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder(); + + // Act + Expression actualExpression = caseInsensitiveEndsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + + // Assert + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); + var notNullLeftHandValueResult2 = compiledExpression.Invoke("The quick brown Fox"); + var nullLeftHandValueResult = compiledExpression.Invoke(null); + + notNullLeftHandValueResult1.Should().BeTrue(); + notNullLeftHandValueResult2.Should().BeTrue(); + nullLeftHandValueResult.Should().BeFalse(); + } + + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + { + // Arrange + Expression leftHandExpression = Expression.Constant(1, typeof(int)); + Expression rightHandExpression = Expression.Constant(2, typeof(int)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null); + + CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder caseInsensitiveEndsWithOneToOneConditionExpressionBuilder + = new CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder(); + + // Act + NotSupportedException notSupportedException = Assert.Throws(() => caseInsensitiveEndsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + + // Assert + notSupportedException.Should().NotBeNull(); + notSupportedException.Message + .Should() + .Contain(Operators.CaseInsensitiveEndsWith.ToString()) + .And + .Contain(DataTypes.Integer.ToString()); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilderTests.cs new file mode 100644 index 00000000..1bf81ad4 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilderTests.cs @@ -0,0 +1,70 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders +{ + using FluentAssertions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Text; + using System.Threading.Tasks; + using Xunit; + + public class CaseInsensitiveStartsWithOneToOneConditionExpressionBuilderTests + { + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() + { + // Arrange + + ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); + Expression rightHandExpression = Expression.Constant("the", typeof(string)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + + CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder caseInsensitiveEndsWithOneToOneConditionExpressionBuilder + = new CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder(); + + // Act + Expression actualExpression = caseInsensitiveEndsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + + // Assert + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); + var notNullLeftHandValueResult2 = compiledExpression.Invoke("the quick brown fox"); + var nullLeftHandValueResult = compiledExpression.Invoke(null); + + notNullLeftHandValueResult1.Should().BeTrue(); + notNullLeftHandValueResult2.Should().BeTrue(); + nullLeftHandValueResult.Should().BeFalse(); + } + + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + { + // Arrange + Expression leftHandExpression = Expression.Constant(1, typeof(int)); + Expression rightHandExpression = Expression.Constant(2, typeof(int)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null); + + CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder caseInsensitiveEndsWithOneToOneConditionExpressionBuilder + = new CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder(); + + // Act + NotSupportedException notSupportedException = Assert.Throws(() => caseInsensitiveEndsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + + // Assert + notSupportedException.Should().NotBeNull(); + notSupportedException.Message + .Should() + .Contain(Operators.CaseInsensitiveStartsWith.ToString()) + .And + .Contain(DataTypes.Integer.ToString()); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProviderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProviderTests.cs new file mode 100644 index 00000000..de69ca2e --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProviderTests.cs @@ -0,0 +1,51 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders +{ + using FluentAssertions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using Xunit; + + public class ConditionExpressionBuilderProviderTests + { + public static IEnumerable Scenarios => OperatorsMetadata.All + .SelectMany(c => c.SupportedMultiplicities.Select(m => new object[] { c.Operator, m }) ) + .ToList(); + + [Theory] + [MemberData(nameof(Scenarios))] + public void GetConditionExpressionBuilderFor_GivenOperatorAndSupportedMultiplicity_ReturnsConditionExpressionBuilder(Operators @operator, string multiplicity) + { + // Arrange + ConditionExpressionBuilderProvider conditionExpressionBuilderProvider = new ConditionExpressionBuilderProvider(); + + // Act + IConditionExpressionBuilder conditionExpressionBuilder = conditionExpressionBuilderProvider.GetConditionExpressionBuilderFor(@operator, multiplicity); + + // Assert + conditionExpressionBuilder.Should().NotBeNull(); + } + + [Fact] + public void GetConditionExpressionBuilderFor_GivenOperatorAndNotSupportedMultiplicity_ThrowsNotSupportedException() + { + // Arrange + Operators @operator = Operators.In; + string multiplicity = Multiplicities.OneToOne; + + ConditionExpressionBuilderProvider conditionExpressionBuilderProvider = new ConditionExpressionBuilderProvider(); + + // Act + NotSupportedException notSupportedException = Assert.Throws(() => conditionExpressionBuilderProvider.GetConditionExpressionBuilderFor(@operator, multiplicity)); + + // Assert + notSupportedException.Should().NotBeNull(); + notSupportedException.Message.Should().Contain(@operator.ToString()).And.Contain(multiplicity); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilderTests.cs new file mode 100644 index 00000000..21f30a8c --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilderTests.cs @@ -0,0 +1,69 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders +{ + using FluentAssertions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Text; + using System.Threading.Tasks; + using Xunit; + + public class ContainsOneToOneConditionExpressionBuilderTests + { + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() + { + // Arrange + ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); + Expression rightHandExpression = Expression.Constant("quick", typeof(string)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + + ContainsOneToOneConditionExpressionBuilder containsOneToOneConditionExpressionBuilder + = new ContainsOneToOneConditionExpressionBuilder(); + + // Act + Expression actualExpression = containsOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + + // Assert + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); + var notNullLeftHandValueResult2 = compiledExpression.Invoke("The Quick brown fox"); + var nullLeftHandValueResult = compiledExpression.Invoke(null); + + notNullLeftHandValueResult1.Should().BeTrue(); + notNullLeftHandValueResult2.Should().BeFalse(); + nullLeftHandValueResult.Should().BeFalse(); + } + + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + { + // Arrange + Expression leftHandExpression = Expression.Constant(1, typeof(int)); + Expression rightHandExpression = Expression.Constant(2, typeof(int)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null); + + ContainsOneToOneConditionExpressionBuilder containsOneToOneConditionExpressionBuilder + = new ContainsOneToOneConditionExpressionBuilder(); + + // Act + NotSupportedException notSupportedException = Assert.Throws(() => containsOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + + // Assert + notSupportedException.Should().NotBeNull(); + notSupportedException.Message + .Should() + .Contain(Operators.Contains.ToString()) + .And + .Contain(DataTypes.Integer.ToString()); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilderTests.cs new file mode 100644 index 00000000..a5a81bfe --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilderTests.cs @@ -0,0 +1,66 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders +{ + using FluentAssertions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation; + using System; + using System.Linq.Expressions; + using Xunit; + + public class EndsWithOneToOneConditionExpressionBuilderTests + { + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() + { + // Arrange + + ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); + Expression rightHandExpression = Expression.Constant("fox", typeof(string)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + + EndsWithOneToOneConditionExpressionBuilder endsWithOneToOneConditionExpressionBuilder + = new EndsWithOneToOneConditionExpressionBuilder(); + + // Act + Expression actualExpression = endsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + + // Assert + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); + var notNullLeftHandValueResult2 = compiledExpression.Invoke("The quick brown Fox"); + var nullLeftHandValueResult = compiledExpression.Invoke(null); + + notNullLeftHandValueResult1.Should().BeTrue(); + notNullLeftHandValueResult2.Should().BeFalse(); + nullLeftHandValueResult.Should().BeFalse(); + } + + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + { + // Arrange + Expression leftHandExpression = Expression.Constant(1, typeof(int)); + Expression rightHandExpression = Expression.Constant(2, typeof(int)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null); + + EndsWithOneToOneConditionExpressionBuilder endsWithOneToOneConditionExpressionBuilder + = new EndsWithOneToOneConditionExpressionBuilder(); + + // Act + NotSupportedException notSupportedException = Assert.Throws(() => endsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + + // Assert + notSupportedException.Should().NotBeNull(); + notSupportedException.Message + .Should() + .Contain(Operators.EndsWith.ToString()) + .And + .Contain(DataTypes.Integer.ToString()); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilderTests.cs new file mode 100644 index 00000000..d761812f --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilderTests.cs @@ -0,0 +1,42 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders +{ + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation; + using System; + using System.Linq.Expressions; + using Xunit; + using FluentAssertions; + + public class EqualOneToOneConditionExpressionBuilderTests + { + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() + { + // Arrange + + ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); + Expression rightHandExpression = Expression.Constant("The quick brown fox", typeof(string)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + + EqualOneToOneConditionExpressionBuilder equalOneToOneConditionExpressionBuilder + = new EqualOneToOneConditionExpressionBuilder(); + + // Act + Expression actualExpression = equalOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + + // Assert + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); + var notNullLeftHandValueResult2 = compiledExpression.Invoke("The quick brown Fox"); + var nullLeftHandValueResult = compiledExpression.Invoke(null); + + notNullLeftHandValueResult1.Should().BeTrue(); + notNullLeftHandValueResult2.Should().BeFalse(); + nullLeftHandValueResult.Should().BeFalse(); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilderTests.cs new file mode 100644 index 00000000..3e095796 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilderTests.cs @@ -0,0 +1,66 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders +{ + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Text; + using System.Threading.Tasks; + using Xunit; + using FluentAssertions; + + public class GreaterThanOneToOneConditionExpressionBuilderTests + { + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + { + // Arrange + ParameterExpression leftHandExpression = Expression.Parameter(typeof(int), "leftHand"); + Expression rightHandExpression = Expression.Constant(1, typeof(int)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0); + + GreaterThanOneToOneConditionExpressionBuilder greaterThanOneToOneConditionExpressionBuilder + = new GreaterThanOneToOneConditionExpressionBuilder(); + + // Act + Expression actualExpression = greaterThanOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + + // Assert + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke(2); + var notNullLeftHandValueResult2 = compiledExpression.Invoke(0); + + notNullLeftHandValueResult1.Should().BeTrue(); + notNullLeftHandValueResult2.Should().BeFalse(); + } + + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ThrowsNotSupportedException() + { + // Arrange + ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); + Expression rightHandExpression = Expression.Constant("test", typeof(string)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + + GreaterThanOneToOneConditionExpressionBuilder greaterThanOneToOneConditionExpressionBuilder + = new GreaterThanOneToOneConditionExpressionBuilder(); + + // Act + NotSupportedException notSupportedException = Assert.Throws(() => greaterThanOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + + // Assert + notSupportedException.Should().NotBeNull(); + notSupportedException.Message.Should() + .Contain(DataTypes.String.ToString()) + .And + .Contain(Operators.GreaterThan.ToString()); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilderTests.cs new file mode 100644 index 00000000..43b95260 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilderTests.cs @@ -0,0 +1,66 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders +{ + using FluentAssertions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Text; + using System.Threading.Tasks; + using Xunit; + + public class GreaterThanOrEqualOneToOneConditionExpressionBuilderTests + { + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + { + // Arrange + ParameterExpression leftHandExpression = Expression.Parameter(typeof(int), "leftHand"); + Expression rightHandExpression = Expression.Constant(1, typeof(int)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0); + + GreaterThanOrEqualOneToOneConditionExpressionBuilder greaterThanOrEqualOneToOneConditionExpressionBuilder + = new GreaterThanOrEqualOneToOneConditionExpressionBuilder(); + + // Act + Expression actualExpression = greaterThanOrEqualOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + + // Assert + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke(2); + var notNullLeftHandValueResult2 = compiledExpression.Invoke(0); + + notNullLeftHandValueResult1.Should().BeTrue(); + notNullLeftHandValueResult2.Should().BeFalse(); + } + + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ThrowsNotSupportedException() + { + // Arrange + ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); + Expression rightHandExpression = Expression.Constant("test", typeof(string)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + + GreaterThanOrEqualOneToOneConditionExpressionBuilder greaterThanOrEqualOneToOneConditionExpressionBuilder + = new GreaterThanOrEqualOneToOneConditionExpressionBuilder(); + + // Act + NotSupportedException notSupportedException = Assert.Throws(() => greaterThanOrEqualOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + + // Assert + notSupportedException.Should().NotBeNull(); + notSupportedException.Message.Should() + .Contain(DataTypes.String.ToString()) + .And + .Contain(Operators.GreaterThan.ToString()); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilderTests.cs new file mode 100644 index 00000000..158bc791 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilderTests.cs @@ -0,0 +1,43 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders +{ + using FluentAssertions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Text; + using System.Threading.Tasks; + using Xunit; + + public class InOneToManyConditionExpressionBuilderTests + { + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + { + // Arrange + ParameterExpression leftHandExpression = Expression.Parameter(typeof(int), "leftHand"); + Expression rightHandExpression = Expression.Constant(new int[] { 1, 2, 3 }, typeof(IEnumerable)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0); + + InOneToManyConditionExpressionBuilder inOneToManyConditionExpressionBuilder + = new InOneToManyConditionExpressionBuilder(); + + // Act + Expression actualExpression = inOneToManyConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + + // Assert + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke(2); + var notNullLeftHandValueResult2 = compiledExpression.Invoke(0); + + notNullLeftHandValueResult1.Should().BeTrue(); + notNullLeftHandValueResult2.Should().BeFalse(); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilderTests.cs new file mode 100644 index 00000000..05a67683 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilderTests.cs @@ -0,0 +1,66 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders +{ + using FluentAssertions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Text; + using System.Threading.Tasks; + using Xunit; + + public class LesserThanOneToOneConditionExpressionBuilderTests + { + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + { + // Arrange + ParameterExpression leftHandExpression = Expression.Parameter(typeof(int), "leftHand"); + Expression rightHandExpression = Expression.Constant(1, typeof(int)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0); + + LesserThanOneToOneConditionExpressionBuilder lesserThanOneToOneConditionExpressionBuilder + = new LesserThanOneToOneConditionExpressionBuilder(); + + // Act + Expression actualExpression = lesserThanOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + + // Assert + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke(2); + var notNullLeftHandValueResult2 = compiledExpression.Invoke(0); + + notNullLeftHandValueResult1.Should().BeFalse(); + notNullLeftHandValueResult2.Should().BeTrue(); + } + + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ThrowsNotSupportedException() + { + // Arrange + ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); + Expression rightHandExpression = Expression.Constant("test", typeof(string)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + + LesserThanOneToOneConditionExpressionBuilder lesserThanOneToOneConditionExpressionBuilder + = new LesserThanOneToOneConditionExpressionBuilder(); + + // Act + NotSupportedException notSupportedException = Assert.Throws(() => lesserThanOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + + // Assert + notSupportedException.Should().NotBeNull(); + notSupportedException.Message.Should() + .Contain(DataTypes.String.ToString()) + .And + .Contain(Operators.LesserThan.ToString()); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilderTests.cs new file mode 100644 index 00000000..9286ea7b --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilderTests.cs @@ -0,0 +1,66 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders +{ + using FluentAssertions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Text; + using System.Threading.Tasks; + using Xunit; + + public class LesserThanOrEqualOneToOneConditionExpressionBuilderTests + { + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + { + // Arrange + ParameterExpression leftHandExpression = Expression.Parameter(typeof(int), "leftHand"); + Expression rightHandExpression = Expression.Constant(1, typeof(int)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0); + + LesserThanOrEqualOneToOneConditionExpressionBuilder lesserThanOrEqualOneToOneConditionExpressionBuilder + = new LesserThanOrEqualOneToOneConditionExpressionBuilder(); + + // Act + Expression actualExpression = lesserThanOrEqualOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + + // Assert + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke(2); + var notNullLeftHandValueResult2 = compiledExpression.Invoke(0); + + notNullLeftHandValueResult1.Should().BeFalse(); + notNullLeftHandValueResult2.Should().BeTrue(); + } + + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ThrowsNotSupportedException() + { + // Arrange + ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); + Expression rightHandExpression = Expression.Constant("test", typeof(string)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + + LesserThanOrEqualOneToOneConditionExpressionBuilder lesserThanOrEqualOneToOneConditionExpressionBuilder + = new LesserThanOrEqualOneToOneConditionExpressionBuilder(); + + // Act + NotSupportedException notSupportedException = Assert.Throws(() => lesserThanOrEqualOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + + // Assert + notSupportedException.Should().NotBeNull(); + notSupportedException.Message.Should() + .Contain(DataTypes.String.ToString()) + .And + .Contain(Operators.LesserThanOrEqual.ToString()); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilderTests.cs new file mode 100644 index 00000000..32e1e4bf --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilderTests.cs @@ -0,0 +1,69 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders +{ + using FluentAssertions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Text; + using System.Threading.Tasks; + using Xunit; + + public class NotContainsOneToOneConditionExpressionBuilderTests + { + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() + { + // Arrange + ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); + Expression rightHandExpression = Expression.Constant("quick", typeof(string)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + + NotContainsOneToOneConditionExpressionBuilder containsOneToOneConditionExpressionBuilder + = new NotContainsOneToOneConditionExpressionBuilder(); + + // Act + Expression actualExpression = containsOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + + // Assert + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); + var notNullLeftHandValueResult2 = compiledExpression.Invoke("The Quick brown fox"); + var nullLeftHandValueResult = compiledExpression.Invoke(null); + + notNullLeftHandValueResult1.Should().BeFalse(); + notNullLeftHandValueResult2.Should().BeTrue(); + nullLeftHandValueResult.Should().BeFalse(); + } + + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + { + // Arrange + Expression leftHandExpression = Expression.Constant(1, typeof(int)); + Expression rightHandExpression = Expression.Constant(2, typeof(int)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null); + + NotContainsOneToOneConditionExpressionBuilder containsOneToOneConditionExpressionBuilder + = new NotContainsOneToOneConditionExpressionBuilder(); + + // Act + NotSupportedException notSupportedException = Assert.Throws(() => containsOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + + // Assert + notSupportedException.Should().NotBeNull(); + notSupportedException.Message + .Should() + .Contain(Operators.NotContains.ToString()) + .And + .Contain(DataTypes.Integer.ToString()); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilderTests.cs new file mode 100644 index 00000000..54b9a992 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilderTests.cs @@ -0,0 +1,66 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders +{ + using FluentAssertions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation; + using System; + using System.Linq.Expressions; + using Xunit; + + public class NotEndsWithOneToOneConditionExpressionBuilderTests + { + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() + { + // Arrange + + ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); + Expression rightHandExpression = Expression.Constant("fox", typeof(string)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + + NotEndsWithOneToOneConditionExpressionBuilder notEndsWithOneToOneConditionExpressionBuilder + = new NotEndsWithOneToOneConditionExpressionBuilder(); + + // Act + Expression actualExpression = notEndsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + + // Assert + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); + var notNullLeftHandValueResult2 = compiledExpression.Invoke("The quick brown Fox"); + var nullLeftHandValueResult = compiledExpression.Invoke(null); + + notNullLeftHandValueResult1.Should().BeFalse(); + notNullLeftHandValueResult2.Should().BeTrue(); + nullLeftHandValueResult.Should().BeFalse(); + } + + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + { + // Arrange + Expression leftHandExpression = Expression.Constant(1, typeof(int)); + Expression rightHandExpression = Expression.Constant(2, typeof(int)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null); + + NotEndsWithOneToOneConditionExpressionBuilder notEndsWithOneToOneConditionExpressionBuilder + = new NotEndsWithOneToOneConditionExpressionBuilder(); + + // Act + NotSupportedException notSupportedException = Assert.Throws(() => notEndsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + + // Assert + notSupportedException.Should().NotBeNull(); + notSupportedException.Message + .Should() + .Contain(Operators.NotEndsWith.ToString()) + .And + .Contain(DataTypes.Integer.ToString()); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilderTests.cs new file mode 100644 index 00000000..bd39a813 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilderTests.cs @@ -0,0 +1,42 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders +{ + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation; + using System; + using System.Linq.Expressions; + using Xunit; + using FluentAssertions; + + public class NotEqualOneToOneConditionExpressionBuilderTests + { + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() + { + // Arrange + + ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); + Expression rightHandExpression = Expression.Constant("The quick brown fox", typeof(string)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + + NotEqualOneToOneConditionExpressionBuilder notEqualOneToOneConditionExpressionBuilder + = new NotEqualOneToOneConditionExpressionBuilder(); + + // Act + Expression actualExpression = notEqualOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + + // Assert + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); + var notNullLeftHandValueResult2 = compiledExpression.Invoke("The quick brown Fox"); + var nullLeftHandValueResult = compiledExpression.Invoke(null); + + notNullLeftHandValueResult1.Should().BeFalse(); + notNullLeftHandValueResult2.Should().BeTrue(); + nullLeftHandValueResult.Should().BeTrue(); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilderTests.cs new file mode 100644 index 00000000..6565cba0 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilderTests.cs @@ -0,0 +1,70 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders +{ + using FluentAssertions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Text; + using System.Threading.Tasks; + using Xunit; + + public class NotStartsWithOneToOneConditionExpressionBuilderTests + { + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() + { + // Arrange + + ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); + Expression rightHandExpression = Expression.Constant("The", typeof(string)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + + NotStartsWithOneToOneConditionExpressionBuilder notStartsWithOneToOneConditionExpressionBuilder + = new NotStartsWithOneToOneConditionExpressionBuilder(); + + // Act + Expression actualExpression = notStartsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + + // Assert + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); + var notNullLeftHandValueResult2 = compiledExpression.Invoke("the quick brown fox"); + var nullLeftHandValueResult = compiledExpression.Invoke(null); + + notNullLeftHandValueResult1.Should().BeFalse(); + notNullLeftHandValueResult2.Should().BeTrue(); + nullLeftHandValueResult.Should().BeFalse(); + } + + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + { + // Arrange + Expression leftHandExpression = Expression.Constant(1, typeof(int)); + Expression rightHandExpression = Expression.Constant(2, typeof(int)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null); + + NotStartsWithOneToOneConditionExpressionBuilder notStartsWithOneToOneConditionExpressionBuilder + = new NotStartsWithOneToOneConditionExpressionBuilder(); + + // Act + NotSupportedException notSupportedException = Assert.Throws(() => notStartsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + + // Assert + notSupportedException.Should().NotBeNull(); + notSupportedException.Message + .Should() + .Contain(Operators.NotStartsWith.ToString()) + .And + .Contain(DataTypes.Integer.ToString()); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilderTests.cs new file mode 100644 index 00000000..f1d8a290 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilderTests.cs @@ -0,0 +1,70 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders +{ + using FluentAssertions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Text; + using System.Threading.Tasks; + using Xunit; + + public class StartsWithOneToOneConditionExpressionBuilderTests + { + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() + { + // Arrange + + ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); + Expression rightHandExpression = Expression.Constant("The", typeof(string)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + + StartsWithOneToOneConditionExpressionBuilder startsWithOneToOneConditionExpressionBuilder + = new StartsWithOneToOneConditionExpressionBuilder(); + + // Act + Expression actualExpression = startsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + + // Assert + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); + var notNullLeftHandValueResult2 = compiledExpression.Invoke("the quick brown fox"); + var nullLeftHandValueResult = compiledExpression.Invoke(null); + + notNullLeftHandValueResult1.Should().BeTrue(); + notNullLeftHandValueResult2.Should().BeFalse(); + nullLeftHandValueResult.Should().BeFalse(); + } + + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + { + // Arrange + Expression leftHandExpression = Expression.Constant(1, typeof(int)); + Expression rightHandExpression = Expression.Constant(2, typeof(int)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null); + + StartsWithOneToOneConditionExpressionBuilder endsWithOneToOneConditionExpressionBuilder + = new StartsWithOneToOneConditionExpressionBuilder(); + + // Act + NotSupportedException notSupportedException = Assert.Throws(() => endsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + + // Assert + notSupportedException.Should().NotBeNull(); + notSupportedException.Message + .Should() + .Contain(Operators.StartsWith.ToString()) + .And + .Contain(DataTypes.Integer.ToString()); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionsTreeCompilerTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionsTreeCompilerTests.cs new file mode 100644 index 00000000..f1e408a4 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionsTreeCompilerTests.cs @@ -0,0 +1,85 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled +{ + using FluentAssertions; + using Moq; + using Rules.Framework.Core; + using Rules.Framework.Core.ConditionNodes; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled; + using Rules.Framework.Tests.TestStubs; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Text; + using System.Threading.Tasks; + using Xunit; + + public class ConditionsTreeCompilerTests + { + [Fact] + public void Compile_GivenComposedConditionNodeWith2ChildValueConditionNodes_CompilesConditionAndStoresExpressionAsProperty() + { + // Arrange + ValueConditionNode valueConditionNode1 + = new ValueConditionNode(DataTypes.Integer, ConditionType.NumberOfSales, Operators.Equal, 100); + ValueConditionNode valueConditionNode2 + = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "GB"); + + ComposedConditionNode composedConditionNode + = new ComposedConditionNode(LogicalOperators.And, new[] { valueConditionNode1, valueConditionNode2 }); + Func, bool> expectedCompiledFunc = (c) => true; + + IValueConditionNodeCompiler valueConditionNodeCompiler = Mock.Of(); + Mock.Get(valueConditionNodeCompiler) + .Setup(x => x.Compile(It.IsAny>(), It.IsAny())) + .Returns(expectedCompiledFunc); + + IValueConditionNodeCompilerProvider valueConditionNodeCompilerProvider = Mock.Of(); + Mock.Get(valueConditionNodeCompilerProvider) + .Setup(x => x.GetValueConditionNodeCompiler(Multiplicities.OneToOne)) + .Returns(valueConditionNodeCompiler); + + ConditionsTreeCompiler conditionsTreeCompiler = new ConditionsTreeCompiler(valueConditionNodeCompilerProvider); + + // Act + conditionsTreeCompiler.Compile(composedConditionNode); + + // Assert + valueConditionNode1.Properties.TryGetValue(ConditionNodeProperties.GetCompiledDelegateKey(Multiplicities.OneToOne), out var compiledExpression1); + valueConditionNode2.Properties.TryGetValue(ConditionNodeProperties.GetCompiledDelegateKey(Multiplicities.OneToOne), out var compiledExpression2); + compiledExpression1.Should() + .NotBeNull() + .And + .BeOfType, bool>>() + .And + .BeSameAs(expectedCompiledFunc); + compiledExpression2.Should() + .NotBeNull() + .And + .BeOfType, bool>>() + .And + .BeSameAs(expectedCompiledFunc); + composedConditionNode.Properties.TryGetValue(ConditionNodeProperties.CompiledFlagKey, out object compiled); + compiled.Should().Be(true); + } + + [Fact] + public void Compile_GivenUnknownConditionNode_ThrowsNotSupportedException() + { + // Arrange + StubConditionNode stubConditionNode = new StubConditionNode(); + + IValueConditionNodeCompilerProvider valueConditionNodeCompilerProvider = Mock.Of(); + + ConditionsTreeCompiler conditionsTreeCompiler = new ConditionsTreeCompiler(valueConditionNodeCompilerProvider); + + // Act + NotSupportedException notSupportedException = Assert.Throws(() => conditionsTreeCompiler.Compile(stubConditionNode)); + + // Assert + notSupportedException.Should().NotBeNull(); + notSupportedException.Message.Should().Contain(nameof(StubConditionNode)); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeCompilerTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeCompilerTests.cs new file mode 100644 index 00000000..dd753d22 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeCompilerTests.cs @@ -0,0 +1,68 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled +{ + using FluentAssertions; + using Moq; + using Rules.Framework.Core; + using Rules.Framework.Core.ConditionNodes; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Tests.TestStubs; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Text; + using System.Threading.Tasks; + using Xunit; + + public class ManyToManyValueConditionNodeCompilerTests + { + [Fact] + public void Compile_GivenValueConditionNodeAndParameterExpression_ReturnsCompiledLambda() + { + // Arrange + ValueConditionNode valueConditionNode = new ValueConditionNode( + DataTypes.String, + ConditionType.IsoCountryCode, + Operators.Contains, + new[] { "PT", "ES" }); + ParameterExpression parameterExpression = Expression.Parameter(typeof(IDictionary)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + Expression actualLeftExpression = null; + Expression actualRightExpression = null; + + Expression conditionExpression = Expression.Constant(true, typeof(bool)); // For testing purposes, does not need to stay true to the scenario + IConditionExpressionBuilder conditionExpressionBuilder = Mock.Of(); + Mock.Get(conditionExpressionBuilder) + .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((e1, e2, dtc) => + { + actualLeftExpression = e1; + actualRightExpression = e2; + }) + .Returns(conditionExpression); + IConditionExpressionBuilderProvider conditionExpressionBuilderProvider = Mock.Of(); + Mock.Get(conditionExpressionBuilderProvider) + .Setup(x => x.GetConditionExpressionBuilderFor(Operators.Contains, Multiplicities.ManyToMany)) + .Returns(conditionExpressionBuilder); + IDataTypesConfigurationProvider dataTypesConfigurationProvider = Mock.Of(); + Mock.Get(dataTypesConfigurationProvider) + .Setup(x => x.GetDataTypeConfiguration(DataTypes.String)) + .Returns(dataTypeConfiguration); + + ManyToManyValueConditionNodeCompiler manyToManyValueConditionNodeCompiler = + new ManyToManyValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); + + // Act + var compiledValueConditionNode = manyToManyValueConditionNodeCompiler.Compile(valueConditionNode, parameterExpression); + + // Assert + compiledValueConditionNode.Should().NotBeNull(); + actualLeftExpression.Should().NotBeNull(); + actualLeftExpression.Type.Should().BeAssignableTo>(); + actualRightExpression.Should().NotBeNull(); + actualRightExpression.Type.Should().BeAssignableTo>(); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeCompilerTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeCompilerTests.cs new file mode 100644 index 00000000..6723ffb8 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeCompilerTests.cs @@ -0,0 +1,68 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled +{ + using Moq; + using Rules.Framework.Core.ConditionNodes; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled; + using Rules.Framework.Evaluation; + using Rules.Framework.Tests.TestStubs; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Text; + using System.Threading.Tasks; + using Xunit; + using FluentAssertions; + + public class ManyToOneValueConditionNodeCompilerTests + { + [Fact] + public void Compile_GivenValueConditionNodeAndParameterExpression_ReturnsCompiledLambda() + { + // Arrange + ValueConditionNode valueConditionNode = new ValueConditionNode( + DataTypes.String, + ConditionType.IsoCountryCode, + Operators.Contains, + "ES"); + ParameterExpression parameterExpression = Expression.Parameter(typeof(IDictionary)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + Expression actualLeftExpression = null; + Expression actualRightExpression = null; + + Expression conditionExpression = Expression.Constant(true, typeof(bool)); // For testing purposes, does not need to stay true to the scenario + IConditionExpressionBuilder conditionExpressionBuilder = Mock.Of(); + Mock.Get(conditionExpressionBuilder) + .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((e1, e2, dtc) => + { + actualLeftExpression = e1; + actualRightExpression = e2; + }) + .Returns(conditionExpression); + IConditionExpressionBuilderProvider conditionExpressionBuilderProvider = Mock.Of(); + Mock.Get(conditionExpressionBuilderProvider) + .Setup(x => x.GetConditionExpressionBuilderFor(Operators.Contains, Multiplicities.ManyToOne)) + .Returns(conditionExpressionBuilder); + IDataTypesConfigurationProvider dataTypesConfigurationProvider = Mock.Of(); + Mock.Get(dataTypesConfigurationProvider) + .Setup(x => x.GetDataTypeConfiguration(DataTypes.String)) + .Returns(dataTypeConfiguration); + + ManyToOneValueConditionNodeCompiler manyToManyValueConditionNodeCompiler = + new ManyToOneValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); + + // Act + var compiledValueConditionNode = manyToManyValueConditionNodeCompiler.Compile(valueConditionNode, parameterExpression); + + // Assert + compiledValueConditionNode.Should().NotBeNull(); + actualLeftExpression.Should().NotBeNull(); + actualLeftExpression.Type.Should().BeAssignableTo>(); + actualRightExpression.Should().NotBeNull(); + actualRightExpression.Type.Should().Be(); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeCompilerTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeCompilerTests.cs new file mode 100644 index 00000000..9e301181 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeCompilerTests.cs @@ -0,0 +1,109 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled +{ + using Moq; + using Rules.Framework.Core.ConditionNodes; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled; + using Rules.Framework.Evaluation; + using Rules.Framework.Tests.TestStubs; + using System.Collections.Generic; + using System.Linq.Expressions; + using Xunit; + using FluentAssertions; + using System; + + public class OneToManyValueConditionNodeCompilerTests + { + [Fact] + public void Compile_GivenValueConditionNodeAndParameterExpression_ReturnsCompiledLambda() + { + // Arrange + ValueConditionNode valueConditionNode = new ValueConditionNode( + DataTypes.String, + ConditionType.IsoCountryCode, + Operators.In, + new[] { "PT", "ES" }); + ParameterExpression parameterExpression = Expression.Parameter(typeof(IDictionary)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + Expression actualLeftExpression = null; + Expression actualRightExpression = null; + + Expression conditionExpression = Expression.Constant(true, typeof(bool)); // For testing purposes, does not need to stay true to the scenario + IConditionExpressionBuilder conditionExpressionBuilder = Mock.Of(); + Mock.Get(conditionExpressionBuilder) + .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((e1, e2, dtc) => + { + actualLeftExpression = e1; + actualRightExpression = e2; + }) + .Returns(conditionExpression); + IConditionExpressionBuilderProvider conditionExpressionBuilderProvider = Mock.Of(); + Mock.Get(conditionExpressionBuilderProvider) + .Setup(x => x.GetConditionExpressionBuilderFor(Operators.In, Multiplicities.OneToMany)) + .Returns(conditionExpressionBuilder); + IDataTypesConfigurationProvider dataTypesConfigurationProvider = Mock.Of(); + Mock.Get(dataTypesConfigurationProvider) + .Setup(x => x.GetDataTypeConfiguration(DataTypes.String)) + .Returns(dataTypeConfiguration); + + OneToManyValueConditionNodeCompiler manyToManyValueConditionNodeCompiler = + new OneToManyValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); + + // Act + var compiledValueConditionNode = manyToManyValueConditionNodeCompiler.Compile(valueConditionNode, parameterExpression); + + // Assert + compiledValueConditionNode.Should().NotBeNull(); + actualLeftExpression.Should().NotBeNull(); + actualLeftExpression.Type.Should().Be(); + actualRightExpression.Should().NotBeNull(); + actualRightExpression.Type.Should().BeAssignableTo>(); + } + + [Fact] + public void Compile_GivenValueConditionNodeWithValueNonArrayTypeAndParameterExpression_ThrowsArgumentException() + { + // Arrange + ValueConditionNode valueConditionNode = new ValueConditionNode( + DataTypes.String, + ConditionType.IsoCountryCode, + Operators.In, + "PT"); + ParameterExpression parameterExpression = Expression.Parameter(typeof(IDictionary)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + Expression actualLeftExpression = null; + Expression actualRightExpression = null; + + Expression conditionExpression = Expression.Constant(true, typeof(bool)); // For testing purposes, does not need to stay true to the scenario + IConditionExpressionBuilder conditionExpressionBuilder = Mock.Of(); + Mock.Get(conditionExpressionBuilder) + .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((e1, e2, dtc) => + { + actualLeftExpression = e1; + actualRightExpression = e2; + }) + .Returns(conditionExpression); + IConditionExpressionBuilderProvider conditionExpressionBuilderProvider = Mock.Of(); + Mock.Get(conditionExpressionBuilderProvider) + .Setup(x => x.GetConditionExpressionBuilderFor(Operators.In, Multiplicities.OneToMany)) + .Returns(conditionExpressionBuilder); + IDataTypesConfigurationProvider dataTypesConfigurationProvider = Mock.Of(); + Mock.Get(dataTypesConfigurationProvider) + .Setup(x => x.GetDataTypeConfiguration(DataTypes.String)) + .Returns(dataTypeConfiguration); + + OneToManyValueConditionNodeCompiler manyToManyValueConditionNodeCompiler = + new OneToManyValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); + + // Act + ArgumentException argumentException = Assert.Throws(() => manyToManyValueConditionNodeCompiler.Compile(valueConditionNode, parameterExpression)); + + // Assert + argumentException.Should().NotBeNull(); + argumentException.ParamName.Should().Be("operand"); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeCompilerTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeCompilerTests.cs new file mode 100644 index 00000000..c7f9adef --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeCompilerTests.cs @@ -0,0 +1,159 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled +{ + using Moq; + using Rules.Framework.Core.ConditionNodes; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled; + using Rules.Framework.Evaluation; + using Rules.Framework.Tests.TestStubs; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Text; + using System.Threading.Tasks; + using Xunit; + using FluentAssertions; + + public class OneToOneValueConditionNodeCompilerTests + { + [Fact] + public void Compile_GivenValueConditionNodeAndParameterExpression_ReturnsCompiledLambda() + { + // Arrange + ValueConditionNode valueConditionNode = new ValueConditionNode( + DataTypes.String, + ConditionType.IsoCountryCode, + Operators.Equal, + "ES"); + ParameterExpression parameterExpression = Expression.Parameter(typeof(IDictionary)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + Expression actualLeftExpression = null; + Expression actualRightExpression = null; + + Expression conditionExpression = Expression.Constant(true, typeof(bool)); // For testing purposes, does not need to stay true to the scenario + IConditionExpressionBuilder conditionExpressionBuilder = Mock.Of(); + Mock.Get(conditionExpressionBuilder) + .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((e1, e2, dtc) => + { + actualLeftExpression = e1; + actualRightExpression = e2; + }) + .Returns(conditionExpression); + IConditionExpressionBuilderProvider conditionExpressionBuilderProvider = Mock.Of(); + Mock.Get(conditionExpressionBuilderProvider) + .Setup(x => x.GetConditionExpressionBuilderFor(Operators.Equal, Multiplicities.OneToOne)) + .Returns(conditionExpressionBuilder); + IDataTypesConfigurationProvider dataTypesConfigurationProvider = Mock.Of(); + Mock.Get(dataTypesConfigurationProvider) + .Setup(x => x.GetDataTypeConfiguration(DataTypes.String)) + .Returns(dataTypeConfiguration); + + OneToOneValueConditionNodeCompiler manyToManyValueConditionNodeCompiler = + new OneToOneValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); + + // Act + var compiledValueConditionNode = manyToManyValueConditionNodeCompiler.Compile(valueConditionNode, parameterExpression); + + // Assert + compiledValueConditionNode.Should().NotBeNull(); + actualLeftExpression.Should().NotBeNull(); + actualLeftExpression.Type.Should().Be(); + actualRightExpression.Should().NotBeNull(); + actualRightExpression.Type.Should().Be(); + } + + [Fact] + public void Compile_GivenValueConditionNodeWithNullOperandAndParameterExpression_ReturnsCompiledLambdaUsingDataTypeDefault() + { + // Arrange + ValueConditionNode valueConditionNode = new ValueConditionNode( + DataTypes.Integer, + ConditionType.NumberOfSales, + Operators.Equal, + null); + ParameterExpression parameterExpression = Expression.Parameter(typeof(IDictionary)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0); + Expression actualLeftExpression = null; + Expression actualRightExpression = null; + + Expression conditionExpression = Expression.Constant(true, typeof(bool)); // For testing purposes, does not need to stay true to the scenario + IConditionExpressionBuilder conditionExpressionBuilder = Mock.Of(); + Mock.Get(conditionExpressionBuilder) + .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((e1, e2, dtc) => + { + actualLeftExpression = e1; + actualRightExpression = e2; + }) + .Returns(conditionExpression); + IConditionExpressionBuilderProvider conditionExpressionBuilderProvider = Mock.Of(); + Mock.Get(conditionExpressionBuilderProvider) + .Setup(x => x.GetConditionExpressionBuilderFor(Operators.Equal, Multiplicities.OneToOne)) + .Returns(conditionExpressionBuilder); + IDataTypesConfigurationProvider dataTypesConfigurationProvider = Mock.Of(); + Mock.Get(dataTypesConfigurationProvider) + .Setup(x => x.GetDataTypeConfiguration(DataTypes.Integer)) + .Returns(dataTypeConfiguration); + + OneToOneValueConditionNodeCompiler oneToOneValueConditionNodeCompiler = + new OneToOneValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); + + // Act + var compiledValueConditionNode = oneToOneValueConditionNodeCompiler.Compile(valueConditionNode, parameterExpression); + + // Assert + compiledValueConditionNode.Should().NotBeNull(); + actualLeftExpression.Should().NotBeNull(); + actualLeftExpression.Type.Should().Be(); + actualRightExpression.Should().NotBeNull(); + actualRightExpression.Type.Should().Be(); + } + + [Fact] + public void Compile_GivenValueConditionNodeWithWrongTypeOperandAndParameterExpression_ThrowsArgumentException() + { + // Arrange + ValueConditionNode valueConditionNode = new ValueConditionNode( + DataTypes.Integer, + ConditionType.NumberOfSales, + Operators.Equal, + "abc"); + ParameterExpression parameterExpression = Expression.Parameter(typeof(IDictionary)); + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0); + Expression actualLeftExpression = null; + Expression actualRightExpression = null; + + Expression conditionExpression = Expression.Constant(true, typeof(bool)); // For testing purposes, does not need to stay true to the scenario + IConditionExpressionBuilder conditionExpressionBuilder = Mock.Of(); + Mock.Get(conditionExpressionBuilder) + .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((e1, e2, dtc) => + { + actualLeftExpression = e1; + actualRightExpression = e2; + }) + .Returns(conditionExpression); + IConditionExpressionBuilderProvider conditionExpressionBuilderProvider = Mock.Of(); + Mock.Get(conditionExpressionBuilderProvider) + .Setup(x => x.GetConditionExpressionBuilderFor(Operators.Equal, Multiplicities.OneToOne)) + .Returns(conditionExpressionBuilder); + IDataTypesConfigurationProvider dataTypesConfigurationProvider = Mock.Of(); + Mock.Get(dataTypesConfigurationProvider) + .Setup(x => x.GetDataTypeConfiguration(DataTypes.Integer)) + .Returns(dataTypeConfiguration); + + OneToOneValueConditionNodeCompiler oneToOneValueConditionNodeCompiler = + new OneToOneValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); + + // Act + ArgumentException argumentException = Assert.Throws(() => oneToOneValueConditionNodeCompiler.Compile(valueConditionNode, parameterExpression)); + + // Assert + argumentException.Should().NotBeNull(); + argumentException.ParamName.Should().Be("operand"); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ValueConditionNodeCompilerProviderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ValueConditionNodeCompilerProviderTests.cs new file mode 100644 index 00000000..b91a19d8 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ValueConditionNodeCompilerProviderTests.cs @@ -0,0 +1,59 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled +{ + using FluentAssertions; + using Moq; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using System; + using System.Collections.Generic; + using Xunit; + + public class ValueConditionNodeCompilerProviderTests + { + public static IEnumerable SuccessScenarios => new[] + { + new object[] { Multiplicities.OneToOne, typeof(OneToOneValueConditionNodeCompiler) }, + new object[] { Multiplicities.OneToMany, typeof(OneToManyValueConditionNodeCompiler) }, + new object[] { Multiplicities.ManyToOne, typeof(ManyToOneValueConditionNodeCompiler) }, + new object[] { Multiplicities.ManyToMany, typeof(ManyToManyValueConditionNodeCompiler) }, + }; + + [Theory] + [MemberData(nameof(SuccessScenarios))] + public void GetValueConditionNodeCompiler_GivenValidMultiplicity_ReturnsMatchingCompiler(string multiplicity, Type compilerType) + { + // Arrange + IConditionExpressionBuilderProvider conditionExpressionBuilderProvider = Mock.Of(); + IDataTypesConfigurationProvider dataTypesConfigurationProvider = Mock.Of(); + + ValueConditionNodeCompilerProvider valueConditionNodeCompilerProvider + = new ValueConditionNodeCompilerProvider(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); + + // Act + var valueConditionNodeCompiler = valueConditionNodeCompilerProvider.GetValueConditionNodeCompiler(multiplicity); + + // Assert + valueConditionNodeCompiler.Should().NotBeNull().And.BeOfType(compilerType); + } + + [Fact] + public void GetValueConditionNodeCompiler_GivenUnknownMultiplicity_ThrowsInvalidOperationException() + { + // Arrange + string multiplicity = "unknown-multiplicity"; + IConditionExpressionBuilderProvider conditionExpressionBuilderProvider = Mock.Of(); + IDataTypesConfigurationProvider dataTypesConfigurationProvider = Mock.Of(); + + ValueConditionNodeCompilerProvider valueConditionNodeCompilerProvider + = new ValueConditionNodeCompilerProvider(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); + + // Act + NotSupportedException notSupportedException = Assert.Throws(() => valueConditionNodeCompilerProvider.GetValueConditionNodeCompiler(multiplicity)); + + // Assert + notSupportedException.Should().NotBeNull(); + notSupportedException.Message.Should().Contain(multiplicity); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/DataTypeConfigurationTests.cs b/tests/Rules.Framework.Tests/Evaluation/DataTypeConfigurationTests.cs new file mode 100644 index 00000000..de973b0d --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/DataTypeConfigurationTests.cs @@ -0,0 +1,49 @@ +namespace Rules.Framework.Tests.Evaluation +{ + using FluentAssertions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using Xunit; + + public class DataTypeConfigurationTests + { + [Fact] + public void Create_GivenDataTypeWithNonNullSystemTypeAndADefault_ReturnsNewInstance() + { + // Arrange + DataTypes dataType = DataTypes.Integer; + Type type = typeof(int); + object @default = 0; + + // Act + DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(dataType, type, @default); + + // Assert + dataTypeConfiguration.Should().NotBeNull(); + dataTypeConfiguration.Default.Should().Be(@default); + dataTypeConfiguration.DataType.Should().Be(dataType); + dataTypeConfiguration.Type.Should().Be(type); + } + + [Fact] + public void Create_GivenDataTypeWithNullSystemTypeAndADefault_ThrowsArgumentNullExcetion() + { + // Assert + DataTypes dataType = DataTypes.Integer; + Type type = null; + object @default = 0; + + // Act + ArgumentNullException argumentNullException = Assert.Throws(() => DataTypeConfiguration.Create(dataType, type, @default)); + + // Assert + argumentNullException.Should().NotBeNull(); + argumentNullException.ParamName.Should().Be(nameof(type)); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/DataTypesConfigurationProviderTests.cs b/tests/Rules.Framework.Tests/Evaluation/DataTypesConfigurationProviderTests.cs new file mode 100644 index 00000000..11e64ad7 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/DataTypesConfigurationProviderTests.cs @@ -0,0 +1,51 @@ +namespace Rules.Framework.Tests.Evaluation +{ + using FluentAssertions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation; + using System; + using System.Collections.Generic; + using System.Linq; + using Xunit; + + public class DataTypesConfigurationProviderTests + { + public static IEnumerable DataTypes => Enum.GetValues() + .Select(dt => new object[] { dt }) + .ToArray(); + + [Theory] + [MemberData(nameof(DataTypes))] + public void GetDataTypeConfiguration_GivenMappedDataType_ReturnsDataTypeConfiguration(DataTypes dataType) + { + // Arrange + RulesEngineOptions rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); + + DataTypesConfigurationProvider dataTypesConfigurationProvider = new DataTypesConfigurationProvider(rulesEngineOptions); + + // Act + DataTypeConfiguration dataTypeConfiguration = dataTypesConfigurationProvider.GetDataTypeConfiguration(dataType); + + // Assert + dataTypeConfiguration.Should().NotBeNull(); + dataTypeConfiguration.DataType.Should().Be(dataType); + } + + [Fact] + public void GetDataTypeConfiguration_GivenUnkownDataType_ThrowsNotSupportedException() + { + // Arrange + DataTypes dataType = (DataTypes)0; + RulesEngineOptions rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); + + DataTypesConfigurationProvider dataTypesConfigurationProvider = new DataTypesConfigurationProvider(rulesEngineOptions); + + // Act + NotSupportedException notSupportedException = Assert.Throws(() => dataTypesConfigurationProvider.GetDataTypeConfiguration(dataType)); + + // Assert + notSupportedException.Should().NotBeNull(); + notSupportedException.Message.Should().Contain($"{dataType}"); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/EvaluationOptionsTests.cs b/tests/Rules.Framework.Tests/Evaluation/EvaluationOptionsTests.cs new file mode 100644 index 00000000..43c0adf9 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/EvaluationOptionsTests.cs @@ -0,0 +1,106 @@ +namespace Rules.Framework.Tests.Evaluation +{ + using FluentAssertions; + using Rules.Framework.Evaluation; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using Xunit; + + public class EvaluationOptionsTests + { + public static IEnumerable EqualsScenarios => OperatorEqualityScenarios.Concat( + new[] + { + new object[] { new object(), false } + }) + .ToList(); + + public static IEnumerable OperatorEqualityScenarios => new[] + { + new object[] { new EvaluationOptions { ExcludeRulesWithoutSearchConditions = true, MatchMode = MatchModes.Search}, false }, + new object[] { new EvaluationOptions { ExcludeRulesWithoutSearchConditions = true, MatchMode = MatchModes.Exact}, false }, + new object[] { new EvaluationOptions { ExcludeRulesWithoutSearchConditions = false, MatchMode = MatchModes.Search}, false }, + new object[] { new EvaluationOptions { ExcludeRulesWithoutSearchConditions = false, MatchMode = MatchModes.Exact}, true } + }; + + public static IEnumerable OperatorInequalityScenarios => OperatorEqualityScenarios + .Select(a => new object[] { a[0], !((bool)a[1]) }) + .ToList(); + + [Theory] + [MemberData(nameof(EqualsScenarios))] + public void Equals_GivenObject_ReturnsComparisonResult(object compared, bool expetedResult) + { + // Arrange + EvaluationOptions evaluationOptions = new EvaluationOptions + { + ExcludeRulesWithoutSearchConditions = false, + MatchMode = MatchModes.Exact + }; + + // Act + bool actualResult = evaluationOptions.Equals(compared); + + // Assert + actualResult.Should().Be(expetedResult); + } + + [Fact] + public void GetHashCode_NoConditions_ReturnsHashCode() + { + // Arrange + EvaluationOptions evaluationOptions = new EvaluationOptions + { + ExcludeRulesWithoutSearchConditions = false, + MatchMode = MatchModes.Exact + }; + + // Act + int hashCode = evaluationOptions.GetHashCode(); + + // Assert + hashCode.Should().NotBe(0); + } + + [Theory] + [MemberData(nameof(OperatorEqualityScenarios))] + public void OperatorEquality_GivenOtherInstance_ReturnsComparisonResult(object compared, bool expetedResult) + { + // Arrange + EvaluationOptions evaluationOptions = new EvaluationOptions + { + ExcludeRulesWithoutSearchConditions = false, + MatchMode = MatchModes.Exact + }; + EvaluationOptions comparedEvaluationOptions = (EvaluationOptions)compared; + + // Act + bool actualResult = evaluationOptions == comparedEvaluationOptions; + + // Assert + actualResult.Should().Be(expetedResult); + } + + [Theory] + [MemberData(nameof(OperatorInequalityScenarios))] + public void OperatorInequality_GivenOtherInstance_ReturnsComparisonResult(object compared, bool expetedResult) + { + // Arrange + EvaluationOptions evaluationOptions = new EvaluationOptions + { + ExcludeRulesWithoutSearchConditions = false, + MatchMode = MatchModes.Exact + }; + EvaluationOptions comparedEvaluationOptions = (EvaluationOptions)compared; + + // Act + bool actualResult = evaluationOptions != comparedEvaluationOptions; + + // Assert + actualResult.Should().Be(expetedResult); + } + } +} diff --git a/tests/Rules.Framework.Tests/Evaluation/MultiplicityEvaluatorTests.cs b/tests/Rules.Framework.Tests/Evaluation/MultiplicityEvaluatorTests.cs index f257bc22..5a1dcacc 100644 --- a/tests/Rules.Framework.Tests/Evaluation/MultiplicityEvaluatorTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/MultiplicityEvaluatorTests.cs @@ -5,6 +5,11 @@ namespace Rules.Framework.Tests.Evaluation using FluentAssertions; using Rules.Framework.Core; using Rules.Framework.Evaluation; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; using Xunit; public class MultiplicityEvaluatorTests diff --git a/tests/Rules.Framework.Tests/Evaluation/OperatorMetadataTests.cs b/tests/Rules.Framework.Tests/Evaluation/OperatorMetadataTests.cs index 75fc98d0..30c0bbb8 100644 --- a/tests/Rules.Framework.Tests/Evaluation/OperatorMetadataTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/OperatorMetadataTests.cs @@ -4,6 +4,11 @@ namespace Rules.Framework.Tests.Evaluation using FluentAssertions; using Rules.Framework.Core; using Rules.Framework.Evaluation; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; using Xunit; public class OperatorMetadataTests diff --git a/tests/Rules.Framework.Tests/Extensions/TypeExtensionsTests.cs b/tests/Rules.Framework.Tests/Extensions/TypeExtensionsTests.cs new file mode 100644 index 00000000..09b35faa --- /dev/null +++ b/tests/Rules.Framework.Tests/Extensions/TypeExtensionsTests.cs @@ -0,0 +1,43 @@ +namespace Rules.Framework.Tests.Extensions +{ + using FluentAssertions; + using System; + using System.Collections.Generic; + using System.Linq; + using Xunit; + + public class TypeExtensionsTests + { + public static IEnumerable SuccessCases + => Enum.GetValues().Select(lo => new object[] { lo }); + + [Theory] + [MemberData(nameof(SuccessCases))] + public void HasLanguageOperator_GivenMappedLanguageOperator_ReturnsTrue(object langOperator) + { + // Arrange + LanguageOperator languageOperator = (LanguageOperator)langOperator; + Type type = typeof(int); + + // Act + bool result = type.HasLanguageOperator(languageOperator); + + // Assert + result.Should().BeTrue(); + } + + [Fact] + public void HasLanguageOperator_GivenUnmappedLanguageOperator_ReturnsFalse() + { + // Arrange + LanguageOperator languageOperator = (LanguageOperator)0; + Type type = typeof(int); + + // Act + bool result = type.HasLanguageOperator(languageOperator); + + // Assert + result.Should().BeFalse(); + } + } +} From 545e031340c52d6d72b015d03659bac6d35e7b13 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Tue, 15 Nov 2022 23:47:45 +0000 Subject: [PATCH 08/54] refactor: implement additional performance improvements --- .../Extensions/LanguageOperator.cs | 1 + .../Extensions/TypeExtensions.cs | 60 ++++++++++--------- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/Rules.Framework/Extensions/LanguageOperator.cs b/src/Rules.Framework/Extensions/LanguageOperator.cs index 5a4b9d57..8afc6d7e 100644 --- a/src/Rules.Framework/Extensions/LanguageOperator.cs +++ b/src/Rules.Framework/Extensions/LanguageOperator.cs @@ -2,6 +2,7 @@ namespace System { internal enum LanguageOperator { + None = 0, Equal = 1, NotEqual = 2, GreaterThan = 3, diff --git a/src/Rules.Framework/Extensions/TypeExtensions.cs b/src/Rules.Framework/Extensions/TypeExtensions.cs index 3bd1647c..f5b30555 100644 --- a/src/Rules.Framework/Extensions/TypeExtensions.cs +++ b/src/Rules.Framework/Extensions/TypeExtensions.cs @@ -1,49 +1,53 @@ namespace System { - using System; using System.Collections.Concurrent; using System.Collections.Generic; + using System.Collections.Specialized; using System.Linq; using System.Reflection; - using System.Text; internal static class TypeExtensions { - private static readonly ConcurrentDictionary> languageOperatorsSupportByType - = new ConcurrentDictionary>(); + private static readonly LanguageOperator[] languageOperators = new[] + { + LanguageOperator.Equal, + LanguageOperator.NotEqual, + LanguageOperator.GreaterThan, + LanguageOperator.GreaterThanOrEqual, + LanguageOperator.LessThan, + LanguageOperator.LessThanOrEqual, + }; + + private static readonly ConcurrentDictionary> languageOperatorsSupportByType = new(); public static bool HasLanguageOperator(this Type type, LanguageOperator languageOperator) { - var languageOperatorsByType = languageOperatorsSupportByType.GetOrAdd(type, (t) => new Dictionary() + var languageOperatorsByType = languageOperatorsSupportByType.GetOrAdd(type, (t) => { - { LanguageOperator.Equal, t.HasLanguageOperatorInternal(LanguageOperator.Equal) }, - { LanguageOperator.NotEqual, t.HasLanguageOperatorInternal(LanguageOperator.NotEqual) }, - { LanguageOperator.GreaterThan, t.HasLanguageOperatorInternal(LanguageOperator.GreaterThan) }, - { LanguageOperator.GreaterThanOrEqual, t.HasLanguageOperatorInternal(LanguageOperator.GreaterThanOrEqual) }, - { LanguageOperator.LessThan, t.HasLanguageOperatorInternal(LanguageOperator.LessThan) }, - { LanguageOperator.LessThanOrEqual, t.HasLanguageOperatorInternal(LanguageOperator.LessThanOrEqual) }, + return ((TypeInfo)type).GetRuntimeMethods() + .Select(m => + { + var splittedName = m.Name.Split('.'); + return splittedName[splittedName.Length - 1]; + }) + .Distinct(StringComparer.Ordinal) + .Select(m => new { LanguageOperator = m.ToLanguageOperator(), Name = m }) + .Where(x => x.LanguageOperator != LanguageOperator.None) + .ToDictionary(x => x.LanguageOperator, x => x.Name); }); - if (languageOperatorsByType.TryGetValue(languageOperator, out bool result)) - { - return result; - } - - return false; + return languageOperatorsByType.ContainsKey(languageOperator); } - private static bool HasLanguageOperatorInternal(this Type type, LanguageOperator languageOperator) - => ((TypeInfo)type).GetRuntimeMethods().Any(m => m.Name.Contains(languageOperator.ToOperatorMethodName())); - - private static string ToOperatorMethodName(this LanguageOperator languageOperator) => languageOperator switch + private static LanguageOperator ToLanguageOperator(this string operatorMethodName) => operatorMethodName switch { - LanguageOperator.Equal => "op_Equality", - LanguageOperator.NotEqual => "op_Inequality", - LanguageOperator.GreaterThan => "op_GreaterThan", - LanguageOperator.GreaterThanOrEqual => "op_GreaterThanOrEqual", - LanguageOperator.LessThan => "op_LessThan", - LanguageOperator.LessThanOrEqual => "op_LessThanOrEqual", - _ => throw new NotSupportedException() + "op_Equality" => LanguageOperator.Equal, + "op_Inequality" => LanguageOperator.NotEqual, + "op_GreaterThan" => LanguageOperator.GreaterThan, + "op_GreaterThanOrEqual" => LanguageOperator.GreaterThanOrEqual, + "op_LessThan" => LanguageOperator.LessThan, + "op_LessThanOrEqual" => LanguageOperator.LessThanOrEqual, + _ => LanguageOperator.None }; } } From ff9bd5ca7dd6e35c5ad5f13c79bbe2a6a99de040 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Tue, 15 Nov 2022 23:48:15 +0000 Subject: [PATCH 09/54] test: test improvements --- tests/Rules.Framework.BenchmarkTests/Program.cs | 2 -- .../Tests/Scenario2/CarInsuranceAdvisorTests.cs | 2 +- tests/Rules.Framework.Tests/Extensions/TypeExtensionsTests.cs | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/Rules.Framework.BenchmarkTests/Program.cs b/tests/Rules.Framework.BenchmarkTests/Program.cs index cc7e477d..01ed5c90 100644 --- a/tests/Rules.Framework.BenchmarkTests/Program.cs +++ b/tests/Rules.Framework.BenchmarkTests/Program.cs @@ -1,8 +1,6 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Diagnostics.Windows; -using BenchmarkDotNet.Engines; using BenchmarkDotNet.Exporters; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Running; diff --git a/tests/Rules.Framework.IntegrationTests/Tests/Scenario2/CarInsuranceAdvisorTests.cs b/tests/Rules.Framework.IntegrationTests/Tests/Scenario2/CarInsuranceAdvisorTests.cs index c22b074c..364f1c59 100644 --- a/tests/Rules.Framework.IntegrationTests/Tests/Scenario2/CarInsuranceAdvisorTests.cs +++ b/tests/Rules.Framework.IntegrationTests/Tests/Scenario2/CarInsuranceAdvisorTests.cs @@ -87,7 +87,7 @@ public async Task GetCarInsuranceAdvice_ClaimDescriptionContionsAlcoholOrDrugs_R // Act await rulesEngine.AddRuleAsync(ruleBuilderResult.Rule, RuleAddPriorityOption.AtBottom).ConfigureAwait(false); - Rule actual = await rulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions); + Rule actual = await rulesEngine.MatchOneAsync(expectedContent, expectedMatchDate, expectedConditions).ConfigureAwait(false); // Assert actual.Should().NotBeNull(); diff --git a/tests/Rules.Framework.Tests/Extensions/TypeExtensionsTests.cs b/tests/Rules.Framework.Tests/Extensions/TypeExtensionsTests.cs index 09b35faa..7def857e 100644 --- a/tests/Rules.Framework.Tests/Extensions/TypeExtensionsTests.cs +++ b/tests/Rules.Framework.Tests/Extensions/TypeExtensionsTests.cs @@ -9,7 +9,7 @@ namespace Rules.Framework.Tests.Extensions public class TypeExtensionsTests { public static IEnumerable SuccessCases - => Enum.GetValues().Select(lo => new object[] { lo }); + => Enum.GetValues().Where(lo => lo != LanguageOperator.None).Select(lo => new object[] { lo }); [Theory] [MemberData(nameof(SuccessCases))] From 2adf807d337429e59efc1914cd62237390bffe72 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Thu, 17 Nov 2022 22:58:22 +0000 Subject: [PATCH 10/54] refactor: optimize conditions convertion and extract conditions tree analysis logic to separate class --- .../Builder/ConfiguredRulesEngineBuilder.cs | 4 +- .../Compiled/CompiledConditionsEvalEngine.cs | 43 +----- .../Evaluation/ConditionsTreeAnalyzer.cs | 2 + .../ClassicConditionsEvalEngineTests.cs | 127 ++++++++---------- .../Evaluation/ConditionsTreeAnalyzerTests.cs | 3 + 5 files changed, 66 insertions(+), 113 deletions(-) diff --git a/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs b/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs index 08fa6325..c7666786 100644 --- a/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs +++ b/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs @@ -28,6 +28,7 @@ public RulesEngine Build() var rulesSourceMiddlewares = new List>(); var dataTypesConfigurationProvider = new DataTypesConfigurationProvider(this.rulesEngineOptions); var multiplicityEvaluator = new MultiplicityEvaluator(); + var conditionsTreeAnalyzer = new ConditionsTreeAnalyzer(); IConditionsEvalEngine conditionsEvalEngine; @@ -36,14 +37,13 @@ public RulesEngine Build() var conditionExpressionBuilderProvider = new ConditionExpressionBuilderProvider(); var valueConditionNodeCompilerProvider = new ValueConditionNodeCompilerProvider(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); var conditionsTreeCompiler = new ConditionsTreeCompiler(valueConditionNodeCompilerProvider); - conditionsEvalEngine = new CompiledConditionsEvalEngine(conditionsTreeCompiler, multiplicityEvaluator, this.rulesEngineOptions); + conditionsEvalEngine = new CompiledConditionsEvalEngine(conditionsTreeCompiler, multiplicityEvaluator, conditionsTreeAnalyzer, this.rulesEngineOptions); } else { var operatorEvalStrategyFactory = new OperatorEvalStrategyFactory(); var conditionEvalDispatchProvider = new ConditionEvalDispatchProvider(operatorEvalStrategyFactory, multiplicityEvaluator, dataTypesConfigurationProvider); var deferredEval = new DeferredEval(conditionEvalDispatchProvider, this.rulesEngineOptions); - var conditionsTreeAnalyzer = new ConditionsTreeAnalyzer(); conditionsEvalEngine = new ClassicConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); } diff --git a/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs b/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs index 155e804a..aa89108f 100644 --- a/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs +++ b/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs @@ -9,6 +9,7 @@ namespace Rules.Framework.Evaluation.Compiled internal sealed class CompiledConditionsEvalEngine : IConditionsEvalEngine { + private readonly IConditionsTreeAnalyzer conditionsTreeAnalyzer; private readonly IConditionsTreeCompiler conditionsTreeCompiler; private readonly IMultiplicityEvaluator multiplicityEvaluator; private readonly RulesEngineOptions rulesEngineOptions; @@ -16,16 +17,18 @@ internal sealed class CompiledConditionsEvalEngine : IConditions public CompiledConditionsEvalEngine( IConditionsTreeCompiler conditionsTreeCompiler, IMultiplicityEvaluator multiplicityEvaluator, + IConditionsTreeAnalyzer conditionsTreeAnalyzer, RulesEngineOptions rulesEngineOptions) { this.conditionsTreeCompiler = conditionsTreeCompiler; this.multiplicityEvaluator = multiplicityEvaluator; + this.conditionsTreeAnalyzer = conditionsTreeAnalyzer; this.rulesEngineOptions = rulesEngineOptions; } public bool Eval(IConditionNode conditionNode, IDictionary conditions, EvaluationOptions evaluationOptions) { - if (evaluationOptions.ExcludeRulesWithoutSearchConditions && !AreAllSearchConditionsPresent(conditionNode, conditions)) + if (evaluationOptions.ExcludeRulesWithoutSearchConditions && !this.conditionsTreeAnalyzer.AreAllSearchConditionsPresent(conditionNode, conditions)) { return false; } @@ -40,44 +43,6 @@ public bool Eval(IConditionNode conditionNode, IDictionary conditionNode, IDictionary conditions) - { - // Conditions checklist is a mere control construct to avoid a full sweep of the - // condition nodes tree when we already found all conditions. - IDictionary conditionsChecklist = new Dictionary(conditions.ToDictionary(ks => ks.Key, vs => false)); - - return VisitConditionNode(conditionNode, conditionsChecklist); - } - - private static bool VisitConditionNode(IConditionNode conditionNode, IDictionary conditionsChecklist) - { - switch (conditionNode) - { - case ValueConditionNode valueConditionNode: - if (conditionsChecklist.ContainsKey(valueConditionNode.ConditionType)) - { - conditionsChecklist[valueConditionNode.ConditionType] = true; - } - - return conditionsChecklist.All(kvp => kvp.Value); - - case ComposedConditionNode composedConditionNode: - foreach (IConditionNode childConditionNode in composedConditionNode.ChildConditionNodes) - { - bool allPresentAlready = VisitConditionNode(childConditionNode, conditionsChecklist); - if (allPresentAlready) - { - return true; - } - } - - return false; - - default: - throw new NotSupportedException($"Unsupported condition node: '{conditionNode.GetType().Name}'."); - } - } - private ISpecification> BuildSpecification( IConditionNode conditionNode, IDictionary conditions, diff --git a/src/Rules.Framework/Evaluation/ConditionsTreeAnalyzer.cs b/src/Rules.Framework/Evaluation/ConditionsTreeAnalyzer.cs index 8ff589a7..88b38051 100644 --- a/src/Rules.Framework/Evaluation/ConditionsTreeAnalyzer.cs +++ b/src/Rules.Framework/Evaluation/ConditionsTreeAnalyzer.cs @@ -1,5 +1,7 @@ namespace Rules.Framework.Evaluation { + using Rules.Framework.Core; + using Rules.Framework.Core.ConditionNodes; using System; using System.Collections.Generic; using System.Linq; diff --git a/tests/Rules.Framework.Tests/Evaluation/Classic/ClassicConditionsEvalEngineTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ClassicConditionsEvalEngineTests.cs index b233e1fa..9fd985cb 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Classic/ClassicConditionsEvalEngineTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ClassicConditionsEvalEngineTests.cs @@ -17,17 +17,23 @@ public class ClassicConditionsEvalEngineTests public void Eval_GivenComposedConditionNodeWithAndOperatorAndMissingConditionWithSearchMode_EvalsAndReturnsResult() { // Arrange - var condition1 = new ValueConditionNode(DataTypes.Boolean, ConditionType.IsVip, Operators.Equal, true); - var condition2 = new ValueConditionNode(DataTypes.String, ConditionType.IsoCurrency, Operators.NotEqual, "SGD"); + ValueConditionNode condition1 = new(DataTypes.Boolean, ConditionType.IsVip, Operators.Equal, true); + ValueConditionNode condition2 = new(DataTypes.String, ConditionType.IsoCurrency, Operators.NotEqual, "SGD"); - var composedConditionNode = new ComposedConditionNode( - LogicalOperators.Or, + ComposedConditionNode composedConditionNode = new( + LogicalOperators.And, new IConditionNode[] { condition1, condition2 }); - var conditions = new Dictionary + Dictionary conditions = new() { - { ConditionType.IsoCurrency, "SGD" }, - { ConditionType.IsoCountryCode, "PT" } + { + ConditionType.IsoCurrency, + "SGD" + }, + { + ConditionType.IsVip, + true + } }; var evaluationOptions = new EvaluationOptions @@ -49,7 +55,7 @@ public void Eval_GivenComposedConditionNodeWithAndOperatorAndMissingConditionWit .Throws(new NotImplementedException("Shouldn't have gotten any more deferred evals.")); var conditionsTreeAnalyzer = Mock.Of>(); - var sut = new ClassicConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); + ClassicConditionsEvalEngine sut = new ClassicConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); // Act bool actual = sut.Eval(composedConditionNode, conditions, evaluationOptions); @@ -64,17 +70,23 @@ public void Eval_GivenComposedConditionNodeWithAndOperatorAndMissingConditionWit public void Eval_GivenComposedConditionNodeWithAndOperatorWithExactMatch_EvalsAndReturnsResult() { // Arrange - var condition1 = new ValueConditionNode(DataTypes.Boolean, ConditionType.IsVip, Operators.Equal, true); - var condition2 = new ValueConditionNode(DataTypes.String, ConditionType.IsoCurrency, Operators.NotEqual, "SGD"); + ValueConditionNode condition1 = new(DataTypes.Boolean, ConditionType.IsVip, Operators.Equal, true); + ValueConditionNode condition2 = new(DataTypes.String, ConditionType.IsoCurrency, Operators.NotEqual, "SGD"); - var composedConditionNode = new ComposedConditionNode( - LogicalOperators.And, + ComposedConditionNode composedConditionNode = new( + LogicalOperators.Eval, new IConditionNode[] { condition1, condition2 }); - var conditions = new Dictionary + Dictionary conditions = new() { - { ConditionType.IsoCurrency, "SGD" }, - { ConditionType.IsVip, true } + { + ConditionType.IsoCurrency, + "SGD" + }, + { + ConditionType.IsVip, + true + } }; var evaluationOptions = new EvaluationOptions @@ -82,28 +94,19 @@ public void Eval_GivenComposedConditionNodeWithAndOperatorWithExactMatch_EvalsAn MatchMode = MatchModes.Exact }; - var mockDeferredEval = new Mock(); - mockDeferredEval.SetupSequence(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact))) - .Returns(() => - { - return (c) => true; - }) - .Returns(() => - { - return (c) => true; - }) - .Throws(new NotImplementedException("Shouldn't have gotten any more deferred evals.")); - var conditionsTreeAnalyzer = Mock.Of>(); + Mock mockDeferredEval = new Mock(); - var sut = new ClassicConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); + IConditionsTreeAnalyzer conditionsTreeAnalyzer = Mock.Of>(); + + ClassicConditionsEvalEngine sut = new ClassicConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); // Act - bool actual = sut.Eval(composedConditionNode, conditions, evaluationOptions); + NotSupportedException notSupportedException = Assert.Throws(() => sut.Eval(composedConditionNode, conditions, evaluationOptions)); // Assert - actual.Should().BeTrue(); - - mockDeferredEval.Verify(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact)), Times.Exactly(2)); + notSupportedException.Should().NotBeNull(); + notSupportedException.Message.Should().Be("Unsupported logical operator: 'Eval'."); + mockDeferredEval.Verify(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact)), Times.Never()); } [Fact] @@ -117,10 +120,16 @@ public void Eval_GivenComposedConditionNodeWithEvalOperator_ThrowsNotSupportedEx LogicalOperators.Eval, new IConditionNode[] { condition1, condition2 }); - var conditions = new Dictionary + Dictionary conditions = new() { - { ConditionType.IsoCurrency, "SGD" }, - { ConditionType.IsVip, true } + { + ConditionType.IsoCurrency, + "SGD" + }, + { + ConditionType.IsVip, + true + } }; var evaluationOptions = new EvaluationOptions @@ -131,7 +140,7 @@ public void Eval_GivenComposedConditionNodeWithEvalOperator_ThrowsNotSupportedEx var mockDeferredEval = new Mock(); var conditionsTreeAnalyzer = Mock.Of>(); - var sut = new ClassicConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); + ClassicConditionsEvalEngine sut = new ClassicConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); // Act var notSupportedException = Assert.Throws(() => sut.Eval(composedConditionNode, conditions, evaluationOptions)); @@ -153,10 +162,16 @@ public void Eval_GivenComposedConditionNodeWithOrOperatorWithExactMatch_EvalsAnd LogicalOperators.Or, new IConditionNode[] { condition1, condition2 }); - var conditions = new Dictionary + var conditions = new Dictionary() { - { ConditionType.IsoCurrency, "SGD" }, - { ConditionType.IsVip, true } + { + ConditionType.IsoCurrency, + "SGD" + }, + { + ConditionType.IsVip, + true + } }; var evaluationOptions = new EvaluationOptions @@ -168,7 +183,7 @@ public void Eval_GivenComposedConditionNodeWithOrOperatorWithExactMatch_EvalsAnd mockDeferredEval.SetupSequence(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact))) .Returns(() => { - return (c) => false; + return (c) => true; }) .Returns(() => { @@ -187,37 +202,5 @@ public void Eval_GivenComposedConditionNodeWithOrOperatorWithExactMatch_EvalsAnd mockDeferredEval.Verify(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact)), Times.Exactly(2)); } - - [Fact] - public void Eval_GivenComposedConditionNodeWithUnknownConditionNode_ThrowsNotSupportedException() - { - // Arrange - var mockConditionNode = new Mock>(); - - var conditions = new Dictionary - { - { ConditionType.IsoCurrency, "SGD" }, - { ConditionType.IsVip, true } - }; - - var evaluationOptions = new EvaluationOptions - { - MatchMode = MatchModes.Exact - }; - - var mockDeferredEval = new Mock(); - var conditionsTreeAnalyzer = Mock.Of>(); - - var sut = new ClassicConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); - - // Act - var notSupportedException = Assert.Throws(() => sut.Eval(mockConditionNode.Object, conditions, evaluationOptions)); - - // Assert - notSupportedException.Should().NotBeNull(); - notSupportedException.Message.Should().Be($"Unsupported condition node: '{mockConditionNode.Object.GetType().Name}'."); - - mockDeferredEval.Verify(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact)), Times.Never()); - } } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/ConditionsTreeAnalyzerTests.cs b/tests/Rules.Framework.Tests/Evaluation/ConditionsTreeAnalyzerTests.cs index ee2e7f31..65bba8f2 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ConditionsTreeAnalyzerTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/ConditionsTreeAnalyzerTests.cs @@ -3,10 +3,13 @@ namespace Rules.Framework.Tests.Evaluation using System; using System.Collections.Generic; using FluentAssertions; + using Rules.Framework.Core.ConditionNodes; using Rules.Framework.Core; using Rules.Framework.Core.ConditionNodes; using Rules.Framework.Evaluation; using Rules.Framework.Tests.TestStubs; + using System; + using System.Collections.Generic; using Xunit; public class ConditionsTreeAnalyzerTests From e01a85bae3670fe720919d820e3ba91dcfbeeaef Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Thu, 1 Dec 2022 13:09:15 +0000 Subject: [PATCH 11/54] feat: move rules compilation to rules source middleware --- .../Builder/ConfiguredRulesEngineBuilder.cs | 11 ++- .../Core/ConditionNodeProperties.cs | 10 +- .../ConditionNodes/ComposedConditionNode.cs | 24 +++++ .../Core/ConditionNodes/ValueConditionNode.cs | 27 ++++++ .../CompilationRulesSourceMiddleware.cs | 94 +++++++++++++++++++ .../Compiled/CompiledConditionsEvalEngine.cs | 8 -- .../Compiled/ConditionsTreeCompiler.cs | 1 - .../Extensions/GenericRuleExtensions.cs | 7 +- .../Core/ConditionNodePropertiesTests.cs | 2 +- .../Compiled/ConditionsTreeCompilerTests.cs | 2 - .../Extensions/GenericRuleExtensionsTests.cs | 11 ++- 11 files changed, 172 insertions(+), 25 deletions(-) create mode 100644 src/Rules.Framework/Evaluation/Compiled/CompilationRulesSourceMiddleware.cs diff --git a/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs b/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs index c7666786..931edb96 100644 --- a/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs +++ b/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs @@ -34,13 +34,22 @@ public RulesEngine Build() if (this.rulesEngineOptions.EnableCompilation) { + // Use specific conditions eval engine to use compiled parts of conditions tree. var conditionExpressionBuilderProvider = new ConditionExpressionBuilderProvider(); var valueConditionNodeCompilerProvider = new ValueConditionNodeCompilerProvider(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); var conditionsTreeCompiler = new ConditionsTreeCompiler(valueConditionNodeCompilerProvider); - conditionsEvalEngine = new CompiledConditionsEvalEngine(conditionsTreeCompiler, multiplicityEvaluator, conditionsTreeAnalyzer, this.rulesEngineOptions); + conditionsEvalEngine = new CompiledConditionsEvalEngine(multiplicityEvaluator, conditionsTreeAnalyzer, this.rulesEngineOptions); + + // Add conditions compiler middleware to ensure compilation occurs before rules + // engine uses the rules, while also ensuring that the compilation result is kept on + // data source (avoiding future re-compilation). + CompilationRulesSourceMiddleware compilationRulesSourceMiddleware = new(conditionsTreeCompiler, this.rulesDataSource); + rulesSourceMiddlewares.Add(compilationRulesSourceMiddleware); } else { + // Use classic conditions eval engine that runs throught all conditions and + // interprets them at each evaluation. var operatorEvalStrategyFactory = new OperatorEvalStrategyFactory(); var conditionEvalDispatchProvider = new ConditionEvalDispatchProvider(operatorEvalStrategyFactory, multiplicityEvaluator, dataTypesConfigurationProvider); var deferredEval = new DeferredEval(conditionEvalDispatchProvider, this.rulesEngineOptions); diff --git a/src/Rules.Framework/Core/ConditionNodeProperties.cs b/src/Rules.Framework/Core/ConditionNodeProperties.cs index 10502b71..5eb4c562 100644 --- a/src/Rules.Framework/Core/ConditionNodeProperties.cs +++ b/src/Rules.Framework/Core/ConditionNodeProperties.cs @@ -1,11 +1,13 @@ namespace Rules.Framework.Core { using System; - using System.Collections.Generic; - using System.Text; internal static class ConditionNodeProperties { + public static string CompilationPrefix => "_compilation"; + + public static string CompiledFlagKey => $"{CompilationPrefix}_isCompiled"; + public static string GetCompiledDelegateKey(string multiplicity) { if (string.IsNullOrWhiteSpace(multiplicity)) @@ -13,9 +15,7 @@ public static string GetCompiledDelegateKey(string multiplicity) throw new ArgumentException($"'{nameof(multiplicity)}' cannot be null or whitespace.", nameof(multiplicity)); } - return $"_compiled_{multiplicity}"; + return $"{CompilationPrefix}_compiled_{multiplicity}"; } - - public static string CompiledFlagKey => "_compiled"; } } diff --git a/src/Rules.Framework/Core/ConditionNodes/ComposedConditionNode.cs b/src/Rules.Framework/Core/ConditionNodes/ComposedConditionNode.cs index d29c6429..ffebcc12 100644 --- a/src/Rules.Framework/Core/ConditionNodes/ComposedConditionNode.cs +++ b/src/Rules.Framework/Core/ConditionNodes/ComposedConditionNode.cs @@ -46,5 +46,29 @@ public ComposedConditionNode(LogicalOperators logicalOperator, IEnumerable public IConditionNode Clone() => new ComposedConditionNode(this.LogicalOperator, this.ChildConditionNodes.Select(cn => cn.Clone()).ToList().AsReadOnly()); + + /// + /// Determines whether the specified , is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + public override bool Equals(object obj) => obj is ComposedConditionNode node && EqualityComparer>>.Default.Equals(this.ChildConditionNodes, node.ChildConditionNodes) && this.LogicalOperator == node.LogicalOperator && EqualityComparer>.Default.Equals(this.Properties, node.Properties); + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() + { + int hashCode = 321258115; + hashCode = hashCode * -1521134295 + EqualityComparer>>.Default.GetHashCode(this.ChildConditionNodes); + hashCode = hashCode * -1521134295 + this.LogicalOperator.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(this.Properties); + return hashCode; + } } } \ No newline at end of file diff --git a/src/Rules.Framework/Core/ConditionNodes/ValueConditionNode.cs b/src/Rules.Framework/Core/ConditionNodes/ValueConditionNode.cs index b526765e..0312a98e 100644 --- a/src/Rules.Framework/Core/ConditionNodes/ValueConditionNode.cs +++ b/src/Rules.Framework/Core/ConditionNodes/ValueConditionNode.cs @@ -80,5 +80,32 @@ public ValueConditionNode(DataTypes dataType, TConditionType conditionType, Oper /// public IConditionNode Clone() => new ValueConditionNode(this.DataType, this.ConditionType, this.Operator, this.Operand, new Dictionary(this.Properties, StringComparer.Ordinal)); + + /// + /// Determines whether the specified , is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + public override bool Equals(object obj) => obj is ValueConditionNode node && EqualityComparer.Default.Equals(this.ConditionType, node.ConditionType) && this.DataType == node.DataType && this.LogicalOperator == node.LogicalOperator && EqualityComparer.Default.Equals(this.Operand, node.Operand) && this.Operator == node.Operator && EqualityComparer>.Default.Equals(this.Properties, node.Properties); + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() + { + int hashCode = -847281186; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.ConditionType); + hashCode = hashCode * -1521134295 + this.DataType.GetHashCode(); + hashCode = hashCode * -1521134295 + this.LogicalOperator.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.Operand); + hashCode = hashCode * -1521134295 + this.Operator.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(this.Properties); + return hashCode; + } } } \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/CompilationRulesSourceMiddleware.cs b/src/Rules.Framework/Evaluation/Compiled/CompilationRulesSourceMiddleware.cs new file mode 100644 index 00000000..f70f4d0e --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/CompilationRulesSourceMiddleware.cs @@ -0,0 +1,94 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using Rules.Framework.Core; + using Rules.Framework.Source; + using System.Collections.Generic; + using System.Numerics; + using System.Threading.Tasks; + + internal class CompilationRulesSourceMiddleware : IRulesSourceMiddleware + { + private readonly IConditionsTreeCompiler conditionsTreeCompiler; + private readonly IRulesDataSource rulesDataSource; + + public CompilationRulesSourceMiddleware( + IConditionsTreeCompiler conditionsTreeCompiler, + IRulesDataSource rulesDataSource) + { + this.conditionsTreeCompiler = conditionsTreeCompiler; + this.rulesDataSource = rulesDataSource; + } + + public async Task HandleAddRuleAsync( + AddRuleArgs args, + AddRuleDelegate next) + { + this.TryCompile(args.Rule); + + await next.Invoke(args).ConfigureAwait(false); + } + + public async Task>> HandleGetRulesAsync( + GetRulesArgs args, + GetRulesDelegate next) + { + var rules = await next.Invoke(args).ConfigureAwait(false); + + foreach (var rule in rules) + { + bool compiled = this.TryCompile(rule); + if (compiled) + { + // Commit compilation result to data source, so that next time rule is loaded, + // it won't go through the compilation process again. + await this.rulesDataSource.UpdateRuleAsync(rule).ConfigureAwait(false); + } + } + + return rules; + } + + public async Task>> HandleGetRulesFilteredAsync( + GetRulesFilteredArgs args, + GetRulesFilteredDelegate next) + { + var rules = await next.Invoke(args).ConfigureAwait(false); + + foreach (var rule in rules) + { + bool compiled = this.TryCompile(rule); + if (compiled) + { + // Commit compilation result to data source, so that next time rule is loaded, + // it won't go through the compilation process again. + await this.rulesDataSource.UpdateRuleAsync(rule).ConfigureAwait(false); + } + } + + return rules; + } + + public async Task HandleUpdateRuleAsync( + UpdateRuleArgs args, + UpdateRuleDelegate next) + { + this.TryCompile(args.Rule); + + await next.Invoke(args).ConfigureAwait(false); + } + + private bool TryCompile(Rule rule) + { + var conditionNode = rule.RootCondition; + + if (conditionNode is { } && (!conditionNode.Properties.TryGetValue(ConditionNodeProperties.CompiledFlagKey, out var compiledFlag) || !(bool)compiledFlag)) + { + this.conditionsTreeCompiler.Compile(conditionNode); + conditionNode.Properties[ConditionNodeProperties.CompiledFlagKey] = true; + return true; + } + + return false; + } + } +} diff --git a/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs b/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs index aa89108f..8c6d8fee 100644 --- a/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs +++ b/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs @@ -10,17 +10,14 @@ namespace Rules.Framework.Evaluation.Compiled internal sealed class CompiledConditionsEvalEngine : IConditionsEvalEngine { private readonly IConditionsTreeAnalyzer conditionsTreeAnalyzer; - private readonly IConditionsTreeCompiler conditionsTreeCompiler; private readonly IMultiplicityEvaluator multiplicityEvaluator; private readonly RulesEngineOptions rulesEngineOptions; public CompiledConditionsEvalEngine( - IConditionsTreeCompiler conditionsTreeCompiler, IMultiplicityEvaluator multiplicityEvaluator, IConditionsTreeAnalyzer conditionsTreeAnalyzer, RulesEngineOptions rulesEngineOptions) { - this.conditionsTreeCompiler = conditionsTreeCompiler; this.multiplicityEvaluator = multiplicityEvaluator; this.conditionsTreeAnalyzer = conditionsTreeAnalyzer; this.rulesEngineOptions = rulesEngineOptions; @@ -33,11 +30,6 @@ public bool Eval(IConditionNode conditionNode, IDictionary> specification = this.BuildSpecification(conditionNode, conditions, evaluationOptions.MatchMode); return specification.IsSatisfiedBy(conditions); diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionsTreeCompiler.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionsTreeCompiler.cs index 1acd01d1..105f7ad2 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionsTreeCompiler.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionsTreeCompiler.cs @@ -21,7 +21,6 @@ public void Compile(IConditionNode conditionNode) { ParameterExpression parameterExpression = Expression.Parameter(dictionaryOfConditionTypeAndObjectType, "conditions"); this.CompileInternal(conditionNode, parameterExpression); - conditionNode.Properties[ConditionNodeProperties.CompiledFlagKey] = true; } private void CompileInternal( diff --git a/src/Rules.Framework/Extensions/GenericRuleExtensions.cs b/src/Rules.Framework/Extensions/GenericRuleExtensions.cs index 987e0264..fa4ba989 100644 --- a/src/Rules.Framework/Extensions/GenericRuleExtensions.cs +++ b/src/Rules.Framework/Extensions/GenericRuleExtensions.cs @@ -18,8 +18,9 @@ public static GenericConditionNode ToGenericConditionNode(this I { ConditionTypeName = condition.ConditionType.ToString(), DataType = condition.DataType, + LogicalOperator = condition.LogicalOperator, Operand = condition.Operand, - Operator = condition.Operator + Operator = condition.Operator, }; } @@ -35,7 +36,7 @@ public static GenericConditionNode ToGenericConditionNode(this I return new GenericComposedConditionNode { ChildConditionNodes = conditionNodeDataModels, - LogicalOperator = composedConditionNode.LogicalOperator + LogicalOperator = composedConditionNode.LogicalOperator, }; } @@ -48,7 +49,7 @@ public static GenericRule ToGenericRule(this Rule< DateBegin = rule.DateBegin, DateEnd = rule.DateEnd, Name = rule.Name, - Priority = rule.Priority + Priority = rule.Priority, }; } } diff --git a/tests/Rules.Framework.Tests/Core/ConditionNodePropertiesTests.cs b/tests/Rules.Framework.Tests/Core/ConditionNodePropertiesTests.cs index 5a77c2ef..44c0dab2 100644 --- a/tests/Rules.Framework.Tests/Core/ConditionNodePropertiesTests.cs +++ b/tests/Rules.Framework.Tests/Core/ConditionNodePropertiesTests.cs @@ -12,7 +12,7 @@ public void GetCompiledDelegateKey_GivenMultiplicityString_ReturnsString() { // Arrange string multiplicity = "test"; - string expected = "_compiled_test"; + string expected = "_compilation_compiled_test"; // Act string actual = ConditionNodeProperties.GetCompiledDelegateKey(multiplicity); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionsTreeCompilerTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionsTreeCompilerTests.cs index f1e408a4..6782d069 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionsTreeCompilerTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionsTreeCompilerTests.cs @@ -60,8 +60,6 @@ ComposedConditionNode composedConditionNode .BeOfType, bool>>() .And .BeSameAs(expectedCompiledFunc); - composedConditionNode.Properties.TryGetValue(ConditionNodeProperties.CompiledFlagKey, out object compiled); - compiled.Should().Be(true); } [Fact] diff --git a/tests/Rules.Framework.Tests/Extensions/GenericRuleExtensionsTests.cs b/tests/Rules.Framework.Tests/Extensions/GenericRuleExtensionsTests.cs index 181e29d2..a5ab988f 100644 --- a/tests/Rules.Framework.Tests/Extensions/GenericRuleExtensionsTests.cs +++ b/tests/Rules.Framework.Tests/Extensions/GenericRuleExtensionsTests.cs @@ -26,6 +26,7 @@ public void GenericRuleExtensions_ToGenericRule_WithComposedCondition_Success() { ConditionTypeName = ConditionType.IsVip.ToString(), DataType = DataTypes.Boolean, + LogicalOperator = LogicalOperators.Eval, Operand = true, Operator = Operators.Equal }, @@ -37,6 +38,7 @@ public void GenericRuleExtensions_ToGenericRule_WithComposedCondition_Success() { ConditionTypeName = ConditionType.IsoCurrency.ToString(), DataType = DataTypes.String, + LogicalOperator = LogicalOperators.Eval, Operand = "EUR", Operator = Operators.Equal }, @@ -44,6 +46,7 @@ public void GenericRuleExtensions_ToGenericRule_WithComposedCondition_Success() { ConditionTypeName = ConditionType.IsoCurrency.ToString(), DataType = DataTypes.String, + LogicalOperator = LogicalOperators.Eval, Operand = "USD", Operator = Operators.Equal } @@ -150,11 +153,11 @@ public void GenericRuleExtensions_ToGenericRule_WithValueCondition_Success() genericRule.RootCondition.Should().BeOfType(); var genericValueRootCondition = genericRule.RootCondition as GenericValueConditionNode; - genericValueRootCondition.Should().BeEquivalentTo(expectedRootCondition, config => config - .Excluding(r => r.ConditionType) - .Excluding(r => r.LogicalOperator) - .Excluding(r => r.Properties)); genericValueRootCondition.ConditionTypeName.Should().Be(expectedRootCondition.ConditionType.ToString()); + genericValueRootCondition.DataType.Should().Be(expectedRootCondition.DataType); + genericValueRootCondition.LogicalOperator.Should().Be(expectedRootCondition.LogicalOperator); + genericValueRootCondition.Operand.Should().Be(expectedRootCondition.Operand); + genericValueRootCondition.Operator.Should().Be(expectedRootCondition.Operator); } } } \ No newline at end of file From 1fa34c6709ef22720ccfbd8dc0b6eb92f0586ff0 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sat, 7 Jan 2023 17:41:52 +0000 Subject: [PATCH 12/54] feat: add support for compiling whole rule conditions tree --- docs/expression-builder-fluent-api.drawio | 1 + .../Rules.Framework.Providers.InMemory.csproj | 2 +- .../Rules.Framework.Providers.MongoDb.csproj | 2 +- .../Builder/ConfiguredRulesEngineBuilder.cs | 6 +- .../Core/ConditionNodeProperties.cs | 19 +- .../BuildValueConditionNodeExpressionArgs.cs | 18 + .../CompilationRulesSourceMiddleware.cs | 23 +- .../Compiled/CompiledConditionsEvalEngine.cs | 70 +--- .../BuildConditionExpressionArgs.cs | 13 + ...sWithOneToOneConditionExpressionBuilder.cs | 32 +- ...sWithOneToOneConditionExpressionBuilder.cs | 36 +- ...tainsOneToOneConditionExpressionBuilder.cs | 33 +- ...sWithOneToOneConditionExpressionBuilder.cs | 32 +- ...EqualOneToOneConditionExpressionBuilder.cs | 10 +- ...rThanOneToOneConditionExpressionBuilder.cs | 16 +- ...EqualOneToOneConditionExpressionBuilder.cs | 16 +- .../IConditionExpressionBuilder.cs | 7 +- .../InOneToManyConditionExpressionBuilder.cs | 13 +- ...rThanOneToOneConditionExpressionBuilder.cs | 16 +- ...EqualOneToOneConditionExpressionBuilder.cs | 16 +- ...tainsOneToOneConditionExpressionBuilder.cs | 31 +- ...sWithOneToOneConditionExpressionBuilder.cs | 29 +- ...EqualOneToOneConditionExpressionBuilder.cs | 10 +- ...sWithOneToOneConditionExpressionBuilder.cs | 29 +- ...sWithOneToOneConditionExpressionBuilder.cs | 32 +- .../Compiled/ConditionsTreeCompiler.cs | 62 ---- .../ConditionsValueLookupExtension.cs | 17 + .../Evaluation/Compiled/EvaluationContext.cs | 23 ++ .../ExpressionBuilders/ExpressionBuilder.cs | 17 + .../ExpressionBuilders/ExpressionResult.cs | 29 ++ .../ConfiguredExpressionBuilder.cs | 34 ++ .../ConfiguredSignatureExpressionBuilder.cs | 35 ++ .../StateMachine/ExpressionConfiguration.cs | 25 ++ .../ExpressionParametersConfiguration.cs | 32 ++ .../IConfiguredExpressionBuilder.cs | 7 + .../IConfiguredSignatureExpressionBuilder.cs | 9 + .../IExpressionParametersConfiguration.cs | 12 + .../IImplementationExpressionBuilder.cs | 87 +++++ .../StateMachine/INamedExpressionBuilder.cs | 11 + .../IParameterizedExpressionBuilder.cs | 11 + .../StateMachine/ISwitchExpressionBuilder.cs | 19 + .../ImplementationExpressionBuilder.cs | 327 ++++++++++++++++++ .../StateMachine/NamedExpressionBuilder.cs | 39 +++ .../ParameterizedExpressionBuilder.cs | 33 ++ .../StateMachine/SwitchExpressionBuilder.cs | 65 ++++ .../Compiled/IConditionsTreeCompiler.cs | 12 - .../IRuleConditionsExpressionBuilder.cs | 11 + .../Compiled/IValueConditionNodeCompiler.cs | 15 - .../IValueConditionNodeCompilerProvider.cs | 12 - .../IValueConditionNodeExpressionBuilder.cs | 11 + ...eConditionNodeExpressionBuilderProvider.cs | 7 + .../ManyToManyValueConditionNodeCompiler.cs | 40 --- ...ManyValueConditionNodeExpressionBuilder.cs | 67 ++++ .../ManyToOneValueConditionNodeCompiler.cs | 40 --- ...oOneValueConditionNodeExpressionBuilder.cs | 67 ++++ .../OneToManyValueConditionNodeCompiler.cs | 43 --- ...ManyValueConditionNodeExpressionBuilder.cs | 81 +++++ .../OneToOneValueConditionNodeCompiler.cs | 39 --- ...oOneValueConditionNodeExpressionBuilder.cs | 80 +++++ .../RuleConditionsExpressionBuilder.cs | 195 +++++++++++ .../ValueConditionNodeCompilerBase.cs | 124 ------- .../ValueConditionNodeCompilerProvider.cs | 39 --- ...eConditionNodeExpressionBuilderProvider.cs | 34 ++ .../Evaluation/ConditionsTreeAnalyzer.cs | 2 - .../Evaluation/MultiplicityEvaluator.cs | 6 +- src/Rules.Framework/Rules.Framework.csproj | 2 +- .../Rules.Framework.BenchmarkTests.csproj | 6 +- .../Rules.Framework.IntegrationTests.csproj | 7 +- .../RulesFromJsonFile.cs | 41 +-- .../Tests/Scenario1/BodyMassIndexTests.cs | 22 +- ...Providers.InMemory.IntegrationTests.csproj | 6 +- ....Framework.Providers.InMemory.Tests.csproj | 8 +- ....Providers.MongoDb.IntegrationTests.csproj | 6 +- ...s.Framework.Providers.MongoDb.Tests.csproj | 8 +- .../Core/ConditionNodePropertiesTests.cs | 38 -- ...OneToOneConditionExpressionBuilderTests.cs | 98 +++--- ...OneToOneConditionExpressionBuilderTests.cs | 96 ++--- ...OneToOneConditionExpressionBuilderTests.cs | 97 +++--- ...OneToOneConditionExpressionBuilderTests.cs | 92 +++-- ...OneToOneConditionExpressionBuilderTests.cs | 43 ++- ...OneToOneConditionExpressionBuilderTests.cs | 65 ++-- ...OneToOneConditionExpressionBuilderTests.cs | 63 ++-- ...neToManyConditionExpressionBuilderTests.cs | 45 ++- ...OneToOneConditionExpressionBuilderTests.cs | 63 ++-- ...OneToOneConditionExpressionBuilderTests.cs | 63 ++-- ...OneToOneConditionExpressionBuilderTests.cs | 97 +++--- ...OneToOneConditionExpressionBuilderTests.cs | 92 +++-- ...OneToOneConditionExpressionBuilderTests.cs | 43 ++- ...OneToOneConditionExpressionBuilderTests.cs | 96 ++--- ...OneToOneConditionExpressionBuilderTests.cs | 99 ++++-- .../Compiled/ConditionsTreeCompilerTests.cs | 83 ----- ...nyToManyValueConditionNodeCompilerTests.cs | 68 ---- ...NodeExpressionBuilderTests.GoldenFile1.csx | 18 + ...alueConditionNodeExpressionBuilderTests.cs | 105 ++++++ ...anyToOneValueConditionNodeCompilerTests.cs | 68 ---- ...NodeExpressionBuilderTests.GoldenFile1.csx | 18 + ...alueConditionNodeExpressionBuilderTests.cs | 105 ++++++ ...neToManyValueConditionNodeCompilerTests.cs | 109 ------ ...NodeExpressionBuilderTests.GoldenFile1.csx | 20 ++ ...alueConditionNodeExpressionBuilderTests.cs | 105 ++++++ ...OneToOneValueConditionNodeCompilerTests.cs | 159 --------- ...NodeExpressionBuilderTests.GoldenFile1.csx | 20 ++ ...alueConditionNodeExpressionBuilderTests.cs | 104 ++++++ ...ionsExpressionBuilderTests.GoldenFile1.csx | 75 ++++ .../RuleConditionsExpressionBuilderTests.cs | 174 ++++++++++ ...ValueConditionNodeCompilerProviderTests.cs | 59 ---- ...itionNodeExpressionBuilderProviderTests.cs | 57 +++ .../Generics/GenericRulesEngineTests.cs | 1 + .../Rules.Framework.Tests.csproj | 23 +- .../Rules.Framework.WebUI.Tests.csproj | 18 +- 110 files changed, 3194 insertions(+), 1822 deletions(-) create mode 100644 docs/expression-builder-fluent-api.drawio create mode 100644 src/Rules.Framework/Evaluation/Compiled/BuildValueConditionNodeExpressionArgs.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/BuildConditionExpressionArgs.cs delete mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionsTreeCompiler.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ConditionsValueLookupExtension.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/EvaluationContext.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionResult.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ConfiguredExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ConfiguredSignatureExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ExpressionConfiguration.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ExpressionParametersConfiguration.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IConfiguredExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IConfiguredSignatureExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IExpressionParametersConfiguration.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IImplementationExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/INamedExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IParameterizedExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ISwitchExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ImplementationExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/NamedExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ParameterizedExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/SwitchExpressionBuilder.cs delete mode 100644 src/Rules.Framework/Evaluation/Compiled/IConditionsTreeCompiler.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/IRuleConditionsExpressionBuilder.cs delete mode 100644 src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeCompiler.cs delete mode 100644 src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeCompilerProvider.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeExpressionBuilderProvider.cs delete mode 100644 src/Rules.Framework/Evaluation/Compiled/ManyToManyValueConditionNodeCompiler.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilder.cs delete mode 100644 src/Rules.Framework/Evaluation/Compiled/ManyToOneValueConditionNodeCompiler.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilder.cs delete mode 100644 src/Rules.Framework/Evaluation/Compiled/OneToManyValueConditionNodeCompiler.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilder.cs delete mode 100644 src/Rules.Framework/Evaluation/Compiled/OneToOneValueConditionNodeCompiler.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/RuleConditionsExpressionBuilder.cs delete mode 100644 src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerBase.cs delete mode 100644 src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerProvider.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeExpressionBuilderProvider.cs delete mode 100644 tests/Rules.Framework.Tests/Core/ConditionNodePropertiesTests.cs delete mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionsTreeCompilerTests.cs delete mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeCompilerTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilderTests.GoldenFile1.csx create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilderTests.cs delete mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeCompilerTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilderTests.GoldenFile1.csx create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilderTests.cs delete mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeCompilerTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilderTests.GoldenFile1.csx create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilderTests.cs delete mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeCompilerTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilderTests.GoldenFile1.csx create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.GoldenFile1.csx create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.cs delete mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ValueConditionNodeCompilerProviderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ValueConditionNodeExpressionBuilderProviderTests.cs diff --git a/docs/expression-builder-fluent-api.drawio b/docs/expression-builder-fluent-api.drawio new file mode 100644 index 00000000..5ceda2cb --- /dev/null +++ b/docs/expression-builder-fluent-api.drawio @@ -0,0 +1 @@ +7V3dd+K2Ev9rcs7tQ3KsD8v2Y0I22Z52tzlNT3d737xgwI3BrDH5uH/9lcFyLMlg4QBDiPqwxbIZB83Mb0ajGc0Z6U2eb7NwNv6SDqLkDDuD5zNyfYYxYq7P/1eMvJQjhJYjoywerMac14H7+H9R+aAYXcSDaF6OrYbyNE3yeCYP9tPpNOrn0liYZemT/NgwTQbSwCwcRdKfUQzc98Mk0h77Fg/y8WrUd2tPf47i0Vi8GTnlnUkoHi4H5uNwkD7VhsinM9LL0jRffZo896KkmD15Xm7W3K3+sCya5iZfoF/i7P7xZ3T9Gf37lMXp5K+n7+d4ReUxTBblD/4aTqIBH/r0PMui+TxOp1eLOBlEWfkr8hcxNVm6mA6igrpzRq6exnEe3c/CfnH3iUsDHxvnk4RfIf5x/hDl/XF50V9kj9FNnOfxdFQO/RuPRkvCfF6uhuk0vwkncVJIzWXWL0j38zl//XW44HPN/5jVQ/fpIlu+cJznXCCwSy75P3wKin+KB+YXozTlhMNZPL/op5Pljf58+ejNcPUK/lF6iYuv1NesxBIxfq1Pe8mJxyjLo+faUMmG2yidRHnG3+OUd1124a6+U2rFOXJKRjy9yhh2SsLjmnx5QSnapViPKuKvnOcfSuZvIQiuJgh3YcZFgU9CMe+9dDqMR4sszLlA7FASTpzTnifxmQhUq7EZMUdns+/sic1MY7PGzGjAAbC8nKZT/r+rNMvH6SidhsnvaToTGhvl+Us5X+EiT2UuyyJxzGyOpoPLwlDw63QWTQsJzcOseoSuHqlfzpd/5F2UxUv9KER9BWQ+v8m/O4ryNTeXwDcop2itfM3FJLSg9upVG54rwaLg6EZprYsjLUUvixKu7I+yGWySx5LcXRrzn1FJPvMlyec8kCmUc7j6Ut1qKXSw00KonG+VEGdp+FJ7bFY8MNf0p5qE7irlWZU6AZVyDVVKiB+ETlEhWqUuUKqQMFUqgmSloogcmVIFmlJ9i/l0YqfulahaliR8VRBZD6TugSBHdjUR84XzWZdQhzY4IWxPTojwgixkHgFkRs9x/r2coOLzP7Xx6+fajesXcTHlQrD8ygV2xfU/9Zuv31teiS92R+fAFJ3hwBnJeqa58KbYjNwWQltjc/N7AiS/hrmKXq8I7hTVkYAvq/hHpPgXnlvTfcdM91Fd8dHeFN/ULQNc6WCirFA82tEr81oI7Ujz+RpKeQ86gOrrkcbKleOybSOOe/UCiWKdkAcdbxQrDmsKjsAU7N8xE+oP4pl5ivCLYPv2y+YWQuDLZkQ1rfoaPUnYyi/+M+W4+4uubnb9rCMnFZL7ulXjwi+g9TC+2KBZWtL7eDQNc35hreo+ZcMnsmwcgVXVg9Gfw8eCO9j5M+ISoe/dWa1v4CxTIq1HETXzrcd0Ah6TsCftLpMH5zK5jMnyT5QNAlOXyXNaCMG7TPpWg1Wrd6hWnqlaMTi1Yko+SOB30yofbaYDrlRYD/TeR8UP+HUyS6IJ525zIpF1RprcTN9R8sUI9YCdEQGSFjXfN2oyQ9QUFCFQM3DlrSqCFKk23lpzghZK8MCph0Wllb1dzR9sNe/iQAPZw67msR7NsyD7/kC2qltoBVkCB7KvhQsiJ6zrNibCtIUSPMo2xE+zKMyLeOnfYRaHP7g+Wd/UADQRUZiNRMUPnGuqx8nUVQdL+B9x9YMbTzYqPu3Tqp66AGAqZy1UBV5gNQ7YRnQ+ltn0Ac2mg2X8qwoftjablZKsowRuNolN3zsJxfJNFQswVIpwEMjqILI4tlYs6lPZQjkKJXjF0lf9lwN5uW/dUSNvhHqK1IgID5g7Suwi/kOBJoFcxFNP3m4lRKFhDJpeIHsjJFCiXPCguWER/3v4Y3kQxV8rblnsNMFOF8nYSSh0yguxtbUfCzshHU6XyOLv4q4VXNQniiLhY8NOPURylaT9B4uUhl6mEuEOCDBSUltS+7GQErDgDbmevI1OWdetIg+1UNpRyRuiim8jIHqvNW9UX9Yv9xcsypqgrJqD7WL4wguqL+Y3hGhsKkZH1quFVvCpGFQ/H83a13doX4mhfRXQDZIlrGZi+B0j356aiKESAl+HUHse3SmoFTVWKwqnVlSJaFK3Y00LIy2E4NVKT4G5Xabfz8SRC2e2CngbhyRQHBLPgU7Ap3oEZ8Xixyp/zXJ4q3PSFHhg4ohnMBa7NqZzCsbR3OcEzGPylWMCWdeQTkBbCIEbR1FNYtXqg6gVZKgUKZWarGshNcJuCyV4xcKaYt2p/uYZ7hU8e5lZv8QwFMaIWvx5SN/zKQkI/mP42H94uGXfs+dhRkbnOoD2wuJblp8GfiZTEoZdcWYTGDt1tT2IPXyPNqdhe26ThsDszrnyVhfDXkeTw4IWSoc0OY3z3JA3O5/HI5sta1i85agpL+SQW2yNLAVKlz1lNALNSBXrvyqs0rV4nzktlMDRSN8jvORSlcxTC0dGcER9ORuk6l0ABkZA21OnDEaQR/QzqggY6wpGniqqKiVwMNJTp3vplIvR1KbDG6bDKxsCuKmw+aBoBHQA5CmjEeAZi3x5pZTYdK5w9EkLJXA00ncrORoVysgHe+Oo/1CcbmRhySh+JBcXUATtJEG1czplXILcs/CU7nCEdS2E8d0WSuC41NCR6NNklr9YMDICI4zlTSlEoYPZDX1mLBi9NZoNGc5GSkkzDnBHNKpOe1xHCR6NGsoLfi5Cu7VmGD9SsvSYE4CHsxFQ4cBJ4xFgFjEibE1tyvaNHp0WSvB4pMc/V0mm9nyFDvCElFZCCEEHlBraxVhweis4QZ5hoCYx087OElaLHI7PWVpX5FBLOrO4ZIBLjtp31AHHJT1caHHprbgEekqmcoS/63QNKaGghRI4LmE9HLrCJXuy9FYlV76y/cYwMCphPVZoUemNqCSUBQaVlHZMxO2KShi1UIJHJT1V8jYtOGSTk8zgKJA5XPWFhYMjmym5eziCTJV0FBFzxWZKh8VbCyV4ONLDor8OLRKZIBFCjpIBII5fgIMimye5eyiCTJSs9nGrnZTOUOS3UIKHIj0IWoa3LRoZRY+UXQx6wHMTmhlq8yR3j0aQiZKYyAaPsa5bbsRroQSPRnro82ta/ASbCLCFh6R0gahONgfDJGJzJHePSZBJkliJHfmdPSSKWiiBY1JD36c/sk/J3EayDbMkiZx4xsBL/kWrWwtHO6yyhcySxIG8h1u1b9kejkgLJXg40kPZf0b5IrNV/4aFbVjmMHPBvSMbyt49HB1T1b+Hupa2qVX/GiV4ONJD2fdPcQEnFo6MvCOlW84hG0g3M9RGs3ePRpDRbCpaBVSFHx3ByKWbCcFjkR7LFlhke57vELJcOeZ9jpyGNqP7anrezHkb9G4ArY1Y1A5akEFvqlYLdI0vue5mQvCg1XA4QGiDS6b7b8pxfxyLoJdzDc3orqNhuEhspZDhCp0Shafg2WZUjwEfxL4srYmwMCXR5dhNXPyAd2uDPEMbRCETZF0s+zm0q+PM2GZC4DZIdGi14n1Y8YbsYxn4suX0FArmNSl4MyF46dYj5la6jaR7Y/PV1tYDGLLmylM6cRAPd5Rv32+htLMurcreE6Weog5v69LarBxAmw8nqhxiedmuHHC6cY5cpbisa6p9dfjZOkLw2A90AMn7F+/m6TTFfshDStRNM1J1Tt66hairnJl0bMfcUpMjLKogQz2gMA5nxf1CwPIwL+4OuWT20iTNll8jzvK/pVBm6UNUuzMclnfWypH5Yh8rncldt+kMftIQSyb7iiU3tI+0kNHdItIGi7hRliEggyhtaagoMtoWMShrIQQPGSanS2yCjJXEQYIGE0tMESD0vAOCxk/66FzFk4F/99/+jzif/D3u/dbQFOpbzPFgIZ3IMt8w0acVANZ42cDxtezVm34JET1E/LeRvUDxMWC4V5C4Y5KEDv/rNeggYI89df+6G9YzH0t0II/3bZxSoLjXO5PaTXjevs7BhxNbpV1V0LF7B6Gb6exRbPlllqZ5/XFu6cZf0kFUPPF/ \ No newline at end of file diff --git a/src/Rules.Framework.Providers.InMemory/Rules.Framework.Providers.InMemory.csproj b/src/Rules.Framework.Providers.InMemory/Rules.Framework.Providers.InMemory.csproj index 5e82847a..6542ff74 100644 --- a/src/Rules.Framework.Providers.InMemory/Rules.Framework.Providers.InMemory.csproj +++ b/src/Rules.Framework.Providers.InMemory/Rules.Framework.Providers.InMemory.csproj @@ -27,7 +27,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Rules.Framework.Providers.MongoDb/Rules.Framework.Providers.MongoDb.csproj b/src/Rules.Framework.Providers.MongoDb/Rules.Framework.Providers.MongoDb.csproj index 3fb101f7..6819be89 100644 --- a/src/Rules.Framework.Providers.MongoDb/Rules.Framework.Providers.MongoDb.csproj +++ b/src/Rules.Framework.Providers.MongoDb/Rules.Framework.Providers.MongoDb.csproj @@ -27,7 +27,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs b/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs index 931edb96..577ee43c 100644 --- a/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs +++ b/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs @@ -36,14 +36,14 @@ public RulesEngine Build() { // Use specific conditions eval engine to use compiled parts of conditions tree. var conditionExpressionBuilderProvider = new ConditionExpressionBuilderProvider(); - var valueConditionNodeCompilerProvider = new ValueConditionNodeCompilerProvider(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); - var conditionsTreeCompiler = new ConditionsTreeCompiler(valueConditionNodeCompilerProvider); + var valueConditionNodeCompilerProvider = new ValueConditionNodeExpressionBuilderProvider(conditionExpressionBuilderProvider); + var ruleConditionsExpressionBuilder = new RuleConditionsExpressionBuilder(valueConditionNodeCompilerProvider, dataTypesConfigurationProvider); conditionsEvalEngine = new CompiledConditionsEvalEngine(multiplicityEvaluator, conditionsTreeAnalyzer, this.rulesEngineOptions); // Add conditions compiler middleware to ensure compilation occurs before rules // engine uses the rules, while also ensuring that the compilation result is kept on // data source (avoiding future re-compilation). - CompilationRulesSourceMiddleware compilationRulesSourceMiddleware = new(conditionsTreeCompiler, this.rulesDataSource); + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware(ruleConditionsExpressionBuilder, this.rulesDataSource); rulesSourceMiddlewares.Add(compilationRulesSourceMiddleware); } else diff --git a/src/Rules.Framework/Core/ConditionNodeProperties.cs b/src/Rules.Framework/Core/ConditionNodeProperties.cs index 5eb4c562..e12e7411 100644 --- a/src/Rules.Framework/Core/ConditionNodeProperties.cs +++ b/src/Rules.Framework/Core/ConditionNodeProperties.cs @@ -1,21 +1,12 @@ namespace Rules.Framework.Core { - using System; - internal static class ConditionNodeProperties { - public static string CompilationPrefix => "_compilation"; - - public static string CompiledFlagKey => $"{CompilationPrefix}_isCompiled"; - - public static string GetCompiledDelegateKey(string multiplicity) + internal static class CompilationProperties { - if (string.IsNullOrWhiteSpace(multiplicity)) - { - throw new ArgumentException($"'{nameof(multiplicity)}' cannot be null or whitespace.", nameof(multiplicity)); - } - - return $"{CompilationPrefix}_compiled_{multiplicity}"; + public static string CompiledDelegateKey => $"{Prefix}_compiledDelegate"; + public static string IsCompiledKey => $"{Prefix}_isCompiled"; + public static string Prefix => "_compilation"; } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/BuildValueConditionNodeExpressionArgs.cs b/src/Rules.Framework/Evaluation/Compiled/BuildValueConditionNodeExpressionArgs.cs new file mode 100644 index 00000000..a65b5139 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/BuildValueConditionNodeExpressionArgs.cs @@ -0,0 +1,18 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using System.Linq.Expressions; + using Rules.Framework.Core; + + internal struct BuildValueConditionNodeExpressionArgs + { + public DataTypeConfiguration DataTypeConfiguration { get; set; } + + public ParameterExpression LeftOperandVariableExpression { get; set; } + + public Operators Operator { get; set; } + + public ParameterExpression ResultVariableExpression { get; set; } + + public ParameterExpression RightOperandVariableExpression { get; set; } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/CompilationRulesSourceMiddleware.cs b/src/Rules.Framework/Evaluation/Compiled/CompilationRulesSourceMiddleware.cs index f70f4d0e..ffd27b82 100644 --- a/src/Rules.Framework/Evaluation/Compiled/CompilationRulesSourceMiddleware.cs +++ b/src/Rules.Framework/Evaluation/Compiled/CompilationRulesSourceMiddleware.cs @@ -1,21 +1,20 @@ namespace Rules.Framework.Evaluation.Compiled { - using Rules.Framework.Core; - using Rules.Framework.Source; using System.Collections.Generic; - using System.Numerics; using System.Threading.Tasks; + using Rules.Framework.Core; + using Rules.Framework.Source; - internal class CompilationRulesSourceMiddleware : IRulesSourceMiddleware + internal sealed class CompilationRulesSourceMiddleware : IRulesSourceMiddleware { - private readonly IConditionsTreeCompiler conditionsTreeCompiler; + private readonly IRuleConditionsExpressionBuilder ruleConditionsExpressionBuilder; private readonly IRulesDataSource rulesDataSource; public CompilationRulesSourceMiddleware( - IConditionsTreeCompiler conditionsTreeCompiler, + IRuleConditionsExpressionBuilder rulesExpressionBuilder, IRulesDataSource rulesDataSource) { - this.conditionsTreeCompiler = conditionsTreeCompiler; + this.ruleConditionsExpressionBuilder = rulesExpressionBuilder; this.rulesDataSource = rulesDataSource; } @@ -81,14 +80,16 @@ private bool TryCompile(Rule rule) { var conditionNode = rule.RootCondition; - if (conditionNode is { } && (!conditionNode.Properties.TryGetValue(ConditionNodeProperties.CompiledFlagKey, out var compiledFlag) || !(bool)compiledFlag)) + if (conditionNode is { } && (!conditionNode.Properties.TryGetValue(ConditionNodeProperties.CompilationProperties.IsCompiledKey, out var compiledFlag) || !(bool)compiledFlag)) { - this.conditionsTreeCompiler.Compile(conditionNode); - conditionNode.Properties[ConditionNodeProperties.CompiledFlagKey] = true; + var expression = this.ruleConditionsExpressionBuilder.BuildExpression(conditionNode); + var compiledExpression = expression.Compile(); + conditionNode.Properties[ConditionNodeProperties.CompilationProperties.CompiledDelegateKey] = compiledExpression; + conditionNode.Properties[ConditionNodeProperties.CompilationProperties.IsCompiledKey] = true; return true; } return false; } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs b/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs index 8c6d8fee..a096d738 100644 --- a/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs +++ b/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs @@ -2,10 +2,7 @@ namespace Rules.Framework.Evaluation.Compiled { using System; using System.Collections.Generic; - using System.Linq; using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Evaluation.Specification; internal sealed class CompiledConditionsEvalEngine : IConditionsEvalEngine { @@ -30,68 +27,17 @@ public bool Eval(IConditionNode conditionNode, IDictionary> specification = this.BuildSpecification(conditionNode, conditions, evaluationOptions.MatchMode); - - return specification.IsSatisfiedBy(conditions); - } - - private ISpecification> BuildSpecification( - IConditionNode conditionNode, - IDictionary conditions, - MatchModes matchMode) - { - return conditionNode switch - { - ValueConditionNode valueConditionNode => this.BuildSpecificationForValueNode(valueConditionNode, conditions, matchMode), - ComposedConditionNode composedConditionNode => this.BuildSpecificationForComposedNode(composedConditionNode, conditions, matchMode), - _ => throw new NotSupportedException($"Unsupported condition node: '{conditionNode.GetType().Name}'."), - }; - } - - private ISpecification> BuildSpecificationForComposedNode( - ComposedConditionNode composedConditionNode, - IDictionary conditions, - MatchModes matchMode) - { - IEnumerable>> childConditionNodesSpecifications = composedConditionNode - .ChildConditionNodes - .Select(cn => this.BuildSpecification(cn, conditions, matchMode)); - - return composedConditionNode.LogicalOperator switch - { - LogicalOperators.And => childConditionNodesSpecifications.Aggregate((s1, s2) => s1.And(s2)), - LogicalOperators.Or => childConditionNodesSpecifications.Aggregate((s1, s2) => s1.Or(s2)), - _ => throw new NotSupportedException($"Unsupported logical operator: '{composedConditionNode.LogicalOperator}'."), - }; - } - - private ISpecification> BuildSpecificationForValueNode( - ValueConditionNode valueConditionNode, - IDictionary conditions, - MatchModes matchMode) - { - conditions.TryGetValue(valueConditionNode.ConditionType, out object leftOperand); - - if (leftOperand is null) + if (conditionNode.Properties.TryGetValue(ConditionNodeProperties.CompilationProperties.CompiledDelegateKey, out object conditionFuncAux)) { - if (this.rulesEngineOptions.MissingConditionBehavior == MissingConditionBehaviors.Discard) - { - return new FuncSpecification>(conditions => false); - } - else if (matchMode == MatchModes.Search) - { - // When match mode is search, if condition is missing, it is not used as search - // criteria, so we don't filter out the rule. - return new FuncSpecification>(conditions => true); - } + throw new ArgumentException("Condition node does not contain compiled information.", nameof(conditionNode)); } - string multiplicity = this.multiplicityEvaluator.EvaluateMultiplicity(leftOperand, valueConditionNode.Operator, valueConditionNode.Operand); - - valueConditionNode.Properties.TryGetValue(ConditionNodeProperties.GetCompiledDelegateKey(multiplicity), out object conditionFuncAux); - Func, bool> conditionFunc = conditionFuncAux as Func, bool>; - - return new FuncSpecification>(conditionFunc); + var conditionFunc = conditionFuncAux as Func, bool>; + var compiledConditionsEvaluationContext = new EvaluationContext( + conditions, + evaluationOptions.MatchMode, + this.rulesEngineOptions.MissingConditionBehavior); + return conditionFunc.Invoke(compiledConditionsEvaluationContext); } } } \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/BuildConditionExpressionArgs.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/BuildConditionExpressionArgs.cs new file mode 100644 index 00000000..54b75056 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/BuildConditionExpressionArgs.cs @@ -0,0 +1,13 @@ +namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders +{ + using System.Linq.Expressions; + + internal struct BuildConditionExpressionArgs + { + public DataTypeConfiguration DataTypeConfiguration { get; set; } + + public Expression LeftHandOperand { get; set; } + + public Expression RightHandOperand { get; set; } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs index 8f20e49a..b0bf25c2 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs @@ -1,37 +1,31 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { - using Rules.Framework.Core; using System; using System.Linq.Expressions; using System.Reflection; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; internal sealed class CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { private static readonly Type booleanType = typeof(bool); + private static readonly MethodInfo endsWithMethodInfo = typeof(string) .GetMethod(nameof(string.EndsWith), new[] { typeof(string), typeof(StringComparison) }); - public Expression BuildConditionExpression( - Expression leftHandOperandExpression, - Expression rightHandOperatorExpression, - DataTypeConfiguration dataTypeConfiguration) + public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) { - if (dataTypeConfiguration.DataType != DataTypes.String) + if (args.DataTypeConfiguration.DataType != DataTypes.String) { - throw new NotSupportedException($"The operator '{Operators.CaseInsensitiveEndsWith}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + throw new NotSupportedException($"The operator '{Operators.CaseInsensitiveEndsWith}' is not supported for data type '{args.DataTypeConfiguration.DataType}'."); } - var returnLabelTarget = Expression.Label(booleanType); - Expression notNullScenario = Expression.Call( - leftHandOperandExpression, - endsWithMethodInfo, - rightHandOperatorExpression, - Expression.Constant(StringComparison.InvariantCultureIgnoreCase)); - Expression ifNotNullExpression = Expression.IfThen( - Expression.NotEqual(leftHandOperandExpression, Expression.Constant(value: null)), - Expression.Return(returnLabelTarget, notNullScenario)); - - return Expression.Block(new[] { ifNotNullExpression, Expression.Label(returnLabelTarget, Expression.Constant(value: false)) }); + return builder.AndAlso( + builder.NotEqual(args.LeftHandOperand, builder.Constant(value: null)), + builder.Call( + args.LeftHandOperand, + endsWithMethodInfo, + new Expression[] { args.RightHandOperand, builder.Constant(StringComparison.InvariantCultureIgnoreCase) })); } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs index 6f0a8f1d..c80aa48c 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs @@ -1,39 +1,31 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { - using Rules.Framework.Core; using System; - using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; - using System.Text; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; internal sealed class CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { private static readonly Type booleanType = typeof(bool); - private static readonly MethodInfo endsWithMethodInfo = typeof(string) + + private static readonly MethodInfo startsWithMethodInfo = typeof(string) .GetMethod(nameof(string.StartsWith), new[] { typeof(string), typeof(StringComparison) }); - public Expression BuildConditionExpression( - Expression leftHandOperandExpression, - Expression rightHandOperatorExpression, - DataTypeConfiguration dataTypeConfiguration) + public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) { - if (dataTypeConfiguration.DataType != DataTypes.String) + if (args.DataTypeConfiguration.DataType != DataTypes.String) { - throw new NotSupportedException($"The operator '{Operators.CaseInsensitiveStartsWith}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + throw new NotSupportedException($"The operator '{Operators.CaseInsensitiveStartsWith}' is not supported for data type '{args.DataTypeConfiguration.DataType}'."); } - var returnLabelTarget = Expression.Label(booleanType); - Expression notNullScenario = Expression.Call( - leftHandOperandExpression, - endsWithMethodInfo, - rightHandOperatorExpression, - Expression.Constant(StringComparison.InvariantCultureIgnoreCase)); - Expression ifNotNullExpression = Expression.IfThen( - Expression.NotEqual(leftHandOperandExpression, Expression.Constant(null)), - Expression.Return(returnLabelTarget, notNullScenario)); - - return Expression.Block(new[] { ifNotNullExpression, Expression.Label(returnLabelTarget, Expression.Constant(false)) }); + return builder.AndAlso( + builder.NotEqual(args.LeftHandOperand, builder.Constant(value: null)), + builder.Call( + args.LeftHandOperand, + startsWithMethodInfo, + new Expression[] { args.RightHandOperand, builder.Constant(StringComparison.InvariantCultureIgnoreCase) })); } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs index 43c8b2da..57cb302b 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs @@ -1,37 +1,28 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { - using Rules.Framework.Core; using System; - using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; - using System.Text; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; internal sealed class ContainsOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { - private static readonly Type booleanType = typeof(bool); private static readonly MethodInfo stringContainsMethodInfo = typeof(string).GetMethod("Contains", new[] { typeof(string) }); - public Expression BuildConditionExpression( - Expression leftHandOperandExpression, - Expression rightHandOperatorExpression, - DataTypeConfiguration dataTypeConfiguration) + public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) { - if (dataTypeConfiguration.DataType != DataTypes.String) + if (args.DataTypeConfiguration.DataType != DataTypes.String) { - throw new NotSupportedException($"The operator '{Operators.Contains}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + throw new NotSupportedException($"The operator '{Operators.Contains}' is not supported for data type '{args.DataTypeConfiguration.DataType}'."); } - var returnLabelTarget = Expression.Label(booleanType); - Expression notNullScenario = Expression.Call( - leftHandOperandExpression, - stringContainsMethodInfo, - rightHandOperatorExpression); - Expression ifNotNullExpression = Expression.IfThen( - Expression.NotEqual(leftHandOperandExpression, Expression.Constant(value: null)), - Expression.Return(returnLabelTarget, notNullScenario)); - - return Expression.Block(new[] { ifNotNullExpression, Expression.Label(returnLabelTarget, Expression.Constant(value: false)) }); + return builder.AndAlso( + builder.NotEqual(args.LeftHandOperand, builder.Constant(value: null)), + builder.Call( + args.LeftHandOperand, + stringContainsMethodInfo, + new Expression[] { args.RightHandOperand })); } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs index afb3cd5b..b3b8fe86 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs @@ -1,37 +1,31 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { - using Rules.Framework.Core; using System; using System.Linq.Expressions; using System.Reflection; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; internal sealed class EndsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { private static readonly Type booleanType = typeof(bool); + private static readonly MethodInfo endsWithMethodInfo = typeof(string) .GetMethod(nameof(string.EndsWith), new[] { typeof(string), typeof(StringComparison) }); - public Expression BuildConditionExpression( - Expression leftHandOperandExpression, - Expression rightHandOperatorExpression, - DataTypeConfiguration dataTypeConfiguration) + public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) { - if (dataTypeConfiguration.DataType != DataTypes.String) + if (args.DataTypeConfiguration.DataType != DataTypes.String) { - throw new NotSupportedException($"The operator '{Operators.EndsWith}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + throw new NotSupportedException($"The operator '{Operators.EndsWith}' is not supported for data type '{args.DataTypeConfiguration.DataType}'."); } - var returnLabelTarget = Expression.Label(booleanType); - Expression notNullScenario = Expression.Call( - leftHandOperandExpression, - endsWithMethodInfo, - rightHandOperatorExpression, - Expression.Constant(StringComparison.InvariantCulture)); - Expression ifNotNullExpression = Expression.IfThen( - Expression.NotEqual(leftHandOperandExpression, Expression.Constant(value: null)), - Expression.Return(returnLabelTarget, notNullScenario)); - - return Expression.Block(new[] { ifNotNullExpression, Expression.Label(returnLabelTarget, Expression.Constant(value: false)) }); + return builder.AndAlso( + builder.NotEqual(args.LeftHandOperand, builder.Constant(value: null)), + builder.Call( + args.LeftHandOperand, + endsWithMethodInfo, + new Expression[] { args.RightHandOperand, builder.Constant(StringComparison.InvariantCulture) })); } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilder.cs index 5cecde31..a4836d84 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilder.cs @@ -1,15 +1,13 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { using System.Linq.Expressions; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; internal sealed class EqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { - public Expression BuildConditionExpression( - Expression leftHandOperandExpression, - Expression rightHandOperatorExpression, - DataTypeConfiguration dataTypeConfiguration) + public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) { - return Expression.Equal(leftHandOperandExpression, rightHandOperatorExpression); + return builder.Equal(args.LeftHandOperand, args.RightHandOperand); } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs index 5198d813..190a8b42 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs @@ -1,22 +1,20 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { - using Rules.Framework.Core; using System; using System.Linq.Expressions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; internal sealed class GreaterThanOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { - public Expression BuildConditionExpression( - Expression leftHandOperandExpression, - Expression rightHandOperatorExpression, - DataTypeConfiguration dataTypeConfiguration) + public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) { - if (!dataTypeConfiguration.Type.HasLanguageOperator(LanguageOperator.GreaterThan)) + if (!args.DataTypeConfiguration.Type.HasLanguageOperator(LanguageOperator.GreaterThan)) { - throw new NotSupportedException($"The operator '{Operators.GreaterThan}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + throw new NotSupportedException($"The operator '{Operators.GreaterThan}' is not supported for data type '{args.DataTypeConfiguration.DataType}'."); } - return Expression.GreaterThan(leftHandOperandExpression, rightHandOperatorExpression); + return builder.GreaterThan(args.LeftHandOperand, args.RightHandOperand); } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs index 07c81818..44ac560e 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs @@ -1,22 +1,20 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { - using Rules.Framework.Core; using System; using System.Linq.Expressions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; internal sealed class GreaterThanOrEqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { - public Expression BuildConditionExpression( - Expression leftHandOperandExpression, - Expression rightHandOperatorExpression, - DataTypeConfiguration dataTypeConfiguration) + public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) { - if (!dataTypeConfiguration.Type.HasLanguageOperator(LanguageOperator.GreaterThanOrEqual)) + if (!args.DataTypeConfiguration.Type.HasLanguageOperator(LanguageOperator.GreaterThanOrEqual)) { - throw new NotSupportedException($"The operator '{Operators.GreaterThanOrEqual}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + throw new NotSupportedException($"The operator '{Operators.GreaterThanOrEqual}' is not supported for data type '{args.DataTypeConfiguration.DataType}'."); } - return Expression.GreaterThanOrEqual(leftHandOperandExpression, rightHandOperatorExpression); + return builder.GreaterThanOrEqual(args.LeftHandOperand, args.RightHandOperand); } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilder.cs index f598c881..da10c0d9 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilder.cs @@ -1,13 +1,10 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { - using Rules.Framework.Evaluation; using System.Linq.Expressions; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; internal interface IConditionExpressionBuilder { - Expression BuildConditionExpression( - Expression leftHandOperandExpression, - Expression rightHandOperatorExpression, - DataTypeConfiguration dataTypeConfiguration); + Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args); } } \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilder.cs index ebdfd3b0..1d648dda 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilder.cs @@ -4,6 +4,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq; using System.Linq.Expressions; using System.Reflection; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; internal sealed class InOneToManyConditionExpressionBuilder : IConditionExpressionBuilder { @@ -12,12 +13,12 @@ private static readonly MethodInfo enumerableGenericContains .GetMethods() .FirstOrDefault(m => string.Equals(m.Name, "Contains", StringComparison.Ordinal) && m.GetParameters().Length == 2); - public Expression BuildConditionExpression( - Expression leftHandOperandExpression, - Expression rightHandOperatorExpression, - DataTypeConfiguration dataTypeConfiguration) + public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) { - return Expression.Call(null, enumerableGenericContains.MakeGenericMethod(dataTypeConfiguration.Type), rightHandOperatorExpression, leftHandOperandExpression); + return builder.Call( + instance: null, + enumerableGenericContains.MakeGenericMethod(args.DataTypeConfiguration.Type), + new Expression[] { args.RightHandOperand, args.LeftHandOperand }); } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs index 14e0c4fd..a164ac2c 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs @@ -1,22 +1,20 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { - using Rules.Framework.Core; using System; using System.Linq.Expressions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; internal sealed class LesserThanOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { - public Expression BuildConditionExpression( - Expression leftHandOperandExpression, - Expression rightHandOperatorExpression, - DataTypeConfiguration dataTypeConfiguration) + public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) { - if (!dataTypeConfiguration.Type.HasLanguageOperator(LanguageOperator.LessThan)) + if (!args.DataTypeConfiguration.Type.HasLanguageOperator(LanguageOperator.LessThan)) { - throw new NotSupportedException($"The operator '{Operators.LesserThan}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + throw new NotSupportedException($"The operator '{Operators.LesserThan}' is not supported for data type '{args.DataTypeConfiguration.DataType}'."); } - return Expression.LessThan(leftHandOperandExpression, rightHandOperatorExpression); + return builder.LessThan(args.LeftHandOperand, args.RightHandOperand); } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs index 673dcc6d..ced17418 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs @@ -1,22 +1,20 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { - using Rules.Framework.Core; using System; using System.Linq.Expressions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; internal sealed class LesserThanOrEqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { - public Expression BuildConditionExpression( - Expression leftHandOperandExpression, - Expression rightHandOperatorExpression, - DataTypeConfiguration dataTypeConfiguration) + public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) { - if (!dataTypeConfiguration.Type.HasLanguageOperator(LanguageOperator.LessThanOrEqual)) + if (!args.DataTypeConfiguration.Type.HasLanguageOperator(LanguageOperator.LessThanOrEqual)) { - throw new NotSupportedException($"The operator '{Operators.LesserThanOrEqual}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + throw new NotSupportedException($"The operator '{Operators.LesserThanOrEqual}' is not supported for data type '{args.DataTypeConfiguration.DataType}'."); } - return Expression.LessThanOrEqual(leftHandOperandExpression, rightHandOperatorExpression); + return builder.LessThan(args.LeftHandOperand, args.RightHandOperand); } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs index 03c9c473..4f85361e 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs @@ -1,35 +1,28 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { - using Rules.Framework.Core; using System; using System.Linq.Expressions; using System.Reflection; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; internal sealed class NotContainsOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { - private static readonly Type booleanType = typeof(bool); private static readonly MethodInfo stringContainsMethodInfo = typeof(string).GetMethod("Contains", new[] { typeof(string) }); - public Expression BuildConditionExpression( - Expression leftHandOperandExpression, - Expression rightHandOperatorExpression, - DataTypeConfiguration dataTypeConfiguration) + public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) { - if (dataTypeConfiguration.DataType != DataTypes.String) + if (args.DataTypeConfiguration.DataType != DataTypes.String) { - throw new NotSupportedException($"The operator '{Operators.NotContains}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + throw new NotSupportedException($"The operator '{Operators.NotContains}' is not supported for data type '{args.DataTypeConfiguration.DataType}'."); } - var returnLabelTarget = Expression.Label(booleanType); - Expression notNullScenario = Expression.Not(Expression.Call( - leftHandOperandExpression, - stringContainsMethodInfo, - rightHandOperatorExpression)); - Expression ifNotNullExpression = Expression.IfThen( - Expression.NotEqual(leftHandOperandExpression, Expression.Constant(value: null)), - Expression.Return(returnLabelTarget, notNullScenario)); - - return Expression.Block(new[] { ifNotNullExpression, Expression.Label(returnLabelTarget, Expression.Constant(value: false)) }); + return builder.AndAlso( + builder.NotEqual(args.LeftHandOperand, builder.Constant(value: null)), + builder.Not(builder.Call( + args.LeftHandOperand, + stringContainsMethodInfo, + new Expression[] { args.RightHandOperand }))); } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilder.cs index 681cb037..b6758df6 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilder.cs @@ -1,9 +1,10 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { - using Rules.Framework.Core; using System; using System.Linq.Expressions; using System.Reflection; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; internal class NotEndsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { @@ -12,27 +13,19 @@ internal class NotEndsWithOneToOneConditionExpressionBuilder : IConditionExpress private static readonly MethodInfo endsWithMethodInfo = typeof(string) .GetMethod(nameof(string.EndsWith), new[] { typeof(string), typeof(StringComparison) }); - public Expression BuildConditionExpression( - Expression leftHandOperandExpression, - Expression rightHandOperatorExpression, - DataTypeConfiguration dataTypeConfiguration) + public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) { - if (dataTypeConfiguration.DataType != DataTypes.String) + if (args.DataTypeConfiguration.DataType != DataTypes.String) { - throw new NotSupportedException($"The operator '{Operators.NotEndsWith}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + throw new NotSupportedException($"The operator '{Operators.NotEndsWith}' is not supported for data type '{args.DataTypeConfiguration.DataType}'."); } - var returnLabelTarget = Expression.Label(booleanType); - Expression notNullScenario = Expression.Not(Expression.Call( - leftHandOperandExpression, - endsWithMethodInfo, - rightHandOperatorExpression, - Expression.Constant(StringComparison.InvariantCulture))); - Expression ifNotNullExpression = Expression.IfThen( - Expression.NotEqual(leftHandOperandExpression, Expression.Constant(value: null)), - Expression.Return(returnLabelTarget, notNullScenario)); - - return Expression.Block(new[] { ifNotNullExpression, Expression.Label(returnLabelTarget, Expression.Constant(value: false)) }); + return builder.AndAlso( + builder.NotEqual(args.LeftHandOperand, builder.Constant(value: null)), + builder.Not(builder.Call( + args.LeftHandOperand, + endsWithMethodInfo, + new Expression[] { args.RightHandOperand, builder.Constant(StringComparison.InvariantCulture) }))); } } } \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilder.cs index be5659da..b1538850 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilder.cs @@ -1,15 +1,13 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { using System.Linq.Expressions; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; internal sealed class NotEqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { - public Expression BuildConditionExpression( - Expression leftHandOperandExpression, - Expression rightHandOperatorExpression, - DataTypeConfiguration dataTypeConfiguration) + public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) { - return Expression.NotEqual(leftHandOperandExpression, rightHandOperatorExpression); + return builder.NotEqual(args.LeftHandOperand, args.RightHandOperand); } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilder.cs index 3da4cec7..b5e748b8 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilder.cs @@ -1,9 +1,10 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { - using Rules.Framework.Core; using System; using System.Linq.Expressions; using System.Reflection; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; internal class NotStartsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { @@ -12,27 +13,19 @@ internal class NotStartsWithOneToOneConditionExpressionBuilder : IConditionExpre private static readonly MethodInfo startsWithMethodInfo = typeof(string) .GetMethod(nameof(string.StartsWith), new[] { typeof(string), typeof(StringComparison) }); - public Expression BuildConditionExpression( - Expression leftHandOperandExpression, - Expression rightHandOperatorExpression, - DataTypeConfiguration dataTypeConfiguration) + public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) { - if (dataTypeConfiguration.DataType != DataTypes.String) + if (args.DataTypeConfiguration.DataType != DataTypes.String) { - throw new NotSupportedException($"The operator '{Operators.NotStartsWith}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + throw new NotSupportedException($"The operator '{Operators.NotStartsWith}' is not supported for data type '{args.DataTypeConfiguration.DataType}'."); } - var returnLabelTarget = Expression.Label(booleanType); - Expression notNullScenario = Expression.Not(Expression.Call( - leftHandOperandExpression, - startsWithMethodInfo, - rightHandOperatorExpression, - Expression.Constant(StringComparison.InvariantCulture))); - Expression ifNotNullExpression = Expression.IfThen( - Expression.NotEqual(leftHandOperandExpression, Expression.Constant(value: null)), - Expression.Return(returnLabelTarget, notNullScenario)); - - return Expression.Block(new[] { ifNotNullExpression, Expression.Label(returnLabelTarget, Expression.Constant(value: false)) }); + return builder.AndAlso( + builder.NotEqual(args.LeftHandOperand, builder.Constant(value: null)), + builder.Not(builder.Call( + args.LeftHandOperand, + startsWithMethodInfo, + new Expression[] { args.RightHandOperand, builder.Constant(StringComparison.InvariantCulture) }))); } } } \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs index a547be1f..c1112ef5 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs @@ -1,37 +1,31 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { - using Rules.Framework.Core; using System; using System.Linq.Expressions; using System.Reflection; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; internal sealed class StartsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { private static readonly Type booleanType = typeof(bool); + private static readonly MethodInfo startsWithMethodInfo = typeof(string) .GetMethod(nameof(string.StartsWith), new[] { typeof(string), typeof(StringComparison) }); - public Expression BuildConditionExpression( - Expression leftHandOperandExpression, - Expression rightHandOperatorExpression, - DataTypeConfiguration dataTypeConfiguration) + public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) { - if (dataTypeConfiguration.DataType != DataTypes.String) + if (args.DataTypeConfiguration.DataType != DataTypes.String) { - throw new NotSupportedException($"The operator '{Operators.StartsWith}' is not supported for data type '{dataTypeConfiguration.DataType}'."); + throw new NotSupportedException($"The operator '{Operators.StartsWith}' is not supported for data type '{args.DataTypeConfiguration.DataType}'."); } - var returnLabelTarget = Expression.Label(booleanType); - Expression notNullScenario = Expression.Call( - leftHandOperandExpression, - startsWithMethodInfo, - rightHandOperatorExpression, - Expression.Constant(StringComparison.InvariantCulture)); - Expression ifNotNullExpression = Expression.IfThen( - Expression.NotEqual(leftHandOperandExpression, Expression.Constant(value: null)), - Expression.Return(returnLabelTarget, notNullScenario)); - - return Expression.Block(new[] { ifNotNullExpression, Expression.Label(returnLabelTarget, Expression.Constant(value: false)) }); + return builder.AndAlso( + builder.NotEqual(args.LeftHandOperand, builder.Constant(value: null)), + builder.Call( + args.LeftHandOperand, + startsWithMethodInfo, + new Expression[] { args.RightHandOperand, builder.Constant(StringComparison.InvariantCulture) })); } } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionsTreeCompiler.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionsTreeCompiler.cs deleted file mode 100644 index 105f7ad2..00000000 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionsTreeCompiler.cs +++ /dev/null @@ -1,62 +0,0 @@ -namespace Rules.Framework.Evaluation.Compiled -{ - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; - using System; - using System.Collections.Generic; - using System.Linq.Expressions; - using System.Text; - - internal sealed class ConditionsTreeCompiler : IConditionsTreeCompiler - { - private static readonly Type dictionaryOfConditionTypeAndObjectType = typeof(IDictionary); - private readonly IValueConditionNodeCompilerProvider valueConditionNodeCompilerProvider; - - public ConditionsTreeCompiler(IValueConditionNodeCompilerProvider valueConditionNodeCompilerProvider) - { - this.valueConditionNodeCompilerProvider = valueConditionNodeCompilerProvider; - } - - public void Compile(IConditionNode conditionNode) - { - ParameterExpression parameterExpression = Expression.Parameter(dictionaryOfConditionTypeAndObjectType, "conditions"); - this.CompileInternal(conditionNode, parameterExpression); - } - - private void CompileInternal( - IConditionNode conditionNode, - ParameterExpression parameterExpression) - { - switch (conditionNode) - { - case ComposedConditionNode composedConditionNode: - foreach (var childConditionNode in composedConditionNode.ChildConditionNodes) - { - this.CompileInternal(childConditionNode, parameterExpression); - } - break; - case ValueConditionNode valueConditionNode: - this.CompileValueConditionNode(valueConditionNode, parameterExpression); - break; - default: - throw new NotSupportedException($"Unsupported condition node: '{conditionNode.GetType().Name}'."); - } - } - - private void CompileValueConditionNode( - ValueConditionNode valueConditionNode, - ParameterExpression parameterExpression) - { - var operatorMetadata = OperatorsMetadata.AllByOperator[valueConditionNode.Operator]; - - foreach (string multiplicity in operatorMetadata.SupportedMultiplicities) - { - var valueConditionNodeCompiler = this.valueConditionNodeCompilerProvider.GetValueConditionNodeCompiler(multiplicity); - - var compiledCondition = valueConditionNodeCompiler.Compile(valueConditionNode, parameterExpression); - - valueConditionNode.Properties[ConditionNodeProperties.GetCompiledDelegateKey(multiplicity)] = compiledCondition; - } - } - } -} diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionsValueLookupExtension.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionsValueLookupExtension.cs new file mode 100644 index 00000000..bc5eab0a --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionsValueLookupExtension.cs @@ -0,0 +1,17 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using System.Collections.Generic; + + internal static class ConditionsValueLookupExtension + { + public static object GetValueOrDefault(IDictionary conditions, TConditionType conditionType) + { + if (conditions.TryGetValue(conditionType, out var conditionValue)) + { + return conditionValue; + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/EvaluationContext.cs b/src/Rules.Framework/Evaluation/Compiled/EvaluationContext.cs new file mode 100644 index 00000000..8ab74ce6 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/EvaluationContext.cs @@ -0,0 +1,23 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using System.Collections.Generic; + + internal sealed class EvaluationContext + { + public EvaluationContext( + IDictionary conditions, + MatchModes matchMode, + MissingConditionBehaviors missingConditionBehavior) + { + this.Conditions = conditions; + this.MatchMode = matchMode; + this.MissingConditionBehavior = missingConditionBehavior; + } + + public IDictionary Conditions { get; } + + public MatchModes MatchMode { get; } + + public MissingConditionBehaviors MissingConditionBehavior { get; } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionBuilder.cs new file mode 100644 index 00000000..34496733 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionBuilder.cs @@ -0,0 +1,17 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders +{ + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + + internal static class ExpressionBuilder + { + public static INamedExpressionBuilder NewExpression(string name) + { + var expressionConfiguration = new ExpressionConfiguration + { + ExpressionName = name, + }; + + return new NamedExpressionBuilder(expressionConfiguration); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionResult.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionResult.cs new file mode 100644 index 00000000..e2cba406 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionResult.cs @@ -0,0 +1,29 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + + internal sealed class ExpressionResult + { + public ExpressionResult( + string expressionName, + Expression implementation, + IEnumerable parameters, + Type returnType) + { + this.ExpressionName = expressionName; + this.Implementation = implementation; + this.Parameters = parameters; + this.ReturnType = returnType; + } + + public string ExpressionName { get; } + + public Expression Implementation { get; } + + public IEnumerable Parameters { get; } + + public Type ReturnType { get; } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ConfiguredExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ConfiguredExpressionBuilder.cs new file mode 100644 index 00000000..ab1ea0a0 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ConfiguredExpressionBuilder.cs @@ -0,0 +1,34 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +{ + using System.Collections.Generic; + using System.Linq.Expressions; + + internal sealed class ConfiguredExpressionBuilder : IConfiguredExpressionBuilder + { + private readonly ExpressionConfiguration expressionConfiguration; + + public ConfiguredExpressionBuilder(ExpressionConfiguration expressionConfiguration) + { + this.expressionConfiguration = expressionConfiguration; + } + + public ExpressionResult Build() + { + var variableExpressionsCopy = new List(this.expressionConfiguration.Variables.Values); + var bodyBlockExpressionsCopy = new List(this.expressionConfiguration.Expressions) + { + Expression.Label( + this.expressionConfiguration.ReturnLabelTarget, + Expression.Constant(this.expressionConfiguration.ReturnDefaultValue)), + }; + + var implementationExpression = Expression.Block(variables: variableExpressionsCopy, expressions: bodyBlockExpressionsCopy); + + return new ExpressionResult( + this.expressionConfiguration.ExpressionName, + implementationExpression, + this.expressionConfiguration.Parameters.Values, + this.expressionConfiguration.ReturnType); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ConfiguredSignatureExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ConfiguredSignatureExpressionBuilder.cs new file mode 100644 index 00000000..f7ded5ac --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ConfiguredSignatureExpressionBuilder.cs @@ -0,0 +1,35 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +{ + using System; + + internal sealed class ConfiguredSignatureExpressionBuilder : IConfiguredSignatureExpressionBuilder + { + private readonly ExpressionConfiguration expressionConfiguration; + + public ConfiguredSignatureExpressionBuilder(ExpressionConfiguration expressionConfiguration) + { + this.expressionConfiguration = expressionConfiguration; + } + + public IConfiguredExpressionBuilder SetImplementation( + Action builder) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + var implementationExpressionBuilder = new ImplementationExpressionBuilder( + scopeName: string.Empty, + parent: null, + expressionConfiguration: this.expressionConfiguration); + builder.Invoke(implementationExpressionBuilder); + + this.expressionConfiguration.LabelTargets = implementationExpressionBuilder.LabelTargets; + this.expressionConfiguration.Variables = implementationExpressionBuilder.Variables; + this.expressionConfiguration.Expressions = implementationExpressionBuilder.Expressions; + + return new ConfiguredExpressionBuilder(expressionConfiguration); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ExpressionConfiguration.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ExpressionConfiguration.cs new file mode 100644 index 00000000..7a143ef9 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ExpressionConfiguration.cs @@ -0,0 +1,25 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + + internal sealed class ExpressionConfiguration + { + public string ExpressionName { get; set; } + + public IEnumerable Expressions { get; set; } + + public IDictionary LabelTargets { get; set; } + + public IDictionary Parameters { get; set; } + + public object ReturnDefaultValue { get; set; } + + public LabelTarget ReturnLabelTarget { get; set; } + + public Type ReturnType { get; set; } + + public IDictionary Variables { get; set; } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ExpressionParametersConfiguration.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ExpressionParametersConfiguration.cs new file mode 100644 index 00000000..c64ab04e --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ExpressionParametersConfiguration.cs @@ -0,0 +1,32 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + + internal sealed class ExpressionParametersConfiguration : IExpressionParametersConfiguration + { + public ExpressionParametersConfiguration() + { + this.Parameters = new Dictionary(StringComparer.Ordinal); + } + + public IDictionary Parameters { get; } + + public ParameterExpression CreateParameter(string name, Type type) + { + if (this.Parameters.ContainsKey(name)) + { + throw new InvalidOperationException($"A parameter for name '{name}' was already added."); + } + + var parameterExpression = Expression.Parameter(type, name); + this.Parameters.Add(name, parameterExpression); + + return parameterExpression; + } + + public ParameterExpression CreateParameter(string name) + => this.CreateParameter(name, typeof(T)); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IConfiguredExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IConfiguredExpressionBuilder.cs new file mode 100644 index 00000000..4d0a8401 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IConfiguredExpressionBuilder.cs @@ -0,0 +1,7 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +{ + internal interface IConfiguredExpressionBuilder + { + ExpressionResult Build(); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IConfiguredSignatureExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IConfiguredSignatureExpressionBuilder.cs new file mode 100644 index 00000000..69d13ac1 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IConfiguredSignatureExpressionBuilder.cs @@ -0,0 +1,9 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +{ + using System; + + internal interface IConfiguredSignatureExpressionBuilder + { + IConfiguredExpressionBuilder SetImplementation(Action builder); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IExpressionParametersConfiguration.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IExpressionParametersConfiguration.cs new file mode 100644 index 00000000..ef7aca45 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IExpressionParametersConfiguration.cs @@ -0,0 +1,12 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +{ + using System; + using System.Linq.Expressions; + + internal interface IExpressionParametersConfiguration + { + ParameterExpression CreateParameter(string name); + + ParameterExpression CreateParameter(string name, Type type); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IImplementationExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IImplementationExpressionBuilder.cs new file mode 100644 index 00000000..bd2385d4 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IImplementationExpressionBuilder.cs @@ -0,0 +1,87 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using System.Reflection; + + internal interface IImplementationExpressionBuilder + { + string ScopeName { get; } + + void AddExpression(Expression expression); + + Expression AndAlso(Expression left, Expression right); + + Expression AndAlso(IEnumerable expressions); + + void Assign(Expression left, Expression right); + + Expression Block(Action blcokImplementationBuilderAction); + + Expression Block(string scopeName, Action blockImplementationBuilderAction); + + Expression Call(Expression instance, MethodInfo method); + + Expression Call(Expression instance, MethodInfo method, IEnumerable arguments); + + Expression Constant(T value); + + Expression Constant(object value, Type type); + + Expression ConvertChecked(Expression expression); + + Expression ConvertChecked(Expression expression, Type type); + + LabelTarget CreateLabelTarget(string name); + + ParameterExpression CreateVariable(string name); + + ParameterExpression CreateVariable(string name, Type type); + + Expression Empty(); + + Expression Equal(Expression left, Expression right); + + LabelTarget GetLabelTarget(string name); + + ParameterExpression GetParameter(string name); + + ParameterExpression GetVariable(string name); + + void Goto(LabelTarget labelTarget); + + Expression GreaterThan(Expression left, Expression right); + + Expression GreaterThanOrEqual(Expression left, Expression right); + + void If( + Func testExpressionBuilderFunc, + Func thenExpressionBuilderFunc); + + void If( + Func testExpressionBuilderFunc, + Func thenExpressionBuilderFunc, + Func elseExpressionBuilderFunc); + + void Label(LabelTarget labelTarget); + + Expression LessThan(Expression left, Expression right); + + Expression LessThanOrEqual(Expression left, Expression right); + + Expression Not(Expression expression); + + Expression NotEqual(Expression left, Expression right); + + Expression OrElse(Expression left, Expression right); + + Expression OrElse(IEnumerable expressions); + + void Return(Expression returnValueExpression); + + void Switch( + Expression switchExpressionValue, + Action switchExpressionBuilderAction); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/INamedExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/INamedExpressionBuilder.cs new file mode 100644 index 00000000..97eae4f4 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/INamedExpressionBuilder.cs @@ -0,0 +1,11 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +{ + using System; + + internal interface INamedExpressionBuilder + { + IParameterizedExpressionBuilder WithoutParameters(); + + IParameterizedExpressionBuilder WithParameters(Action parametersConfigurationAction); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IParameterizedExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IParameterizedExpressionBuilder.cs new file mode 100644 index 00000000..69e78584 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IParameterizedExpressionBuilder.cs @@ -0,0 +1,11 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +{ + using System; + + internal interface IParameterizedExpressionBuilder + { + IConfiguredSignatureExpressionBuilder HavingReturn(object defaultValue); + + IConfiguredSignatureExpressionBuilder HavingReturn(Type type, object defaultValue); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ISwitchExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ISwitchExpressionBuilder.cs new file mode 100644 index 00000000..084264f7 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ISwitchExpressionBuilder.cs @@ -0,0 +1,19 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + + internal interface ISwitchExpressionBuilder + { + ISwitchExpressionBuilder Case( + Expression caseExpression, + Func caseBodyExpressionBuilder); + + ISwitchExpressionBuilder Case( + IEnumerable caseExpressions, + Func caseBodyExpressionBuilder); + + void Default(Func defaultBodyExpressionBuilder); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ImplementationExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ImplementationExpressionBuilder.cs new file mode 100644 index 00000000..1e924959 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ImplementationExpressionBuilder.cs @@ -0,0 +1,327 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + + internal sealed class ImplementationExpressionBuilder : IImplementationExpressionBuilder + { + private readonly ExpressionConfiguration expressionConfiguration; + private readonly List expressions; + private readonly Dictionary labelTargets; + private readonly Dictionary variables; + + public ImplementationExpressionBuilder(string scopeName, IImplementationExpressionBuilder parent, ExpressionConfiguration expressionConfiguration) + { + this.expressionConfiguration = expressionConfiguration; + this.expressions = new List(); + this.labelTargets = new Dictionary(StringComparer.Ordinal); + this.ScopeName = scopeName; + this.Parent = parent; + this.variables = new Dictionary(StringComparer.Ordinal); + } + + public IList Expressions => this.expressions; + + public IDictionary LabelTargets => this.labelTargets; + + public IImplementationExpressionBuilder Parent { get; } + + public string ScopeName { get; } + + public IDictionary Variables => this.variables; + + public void AddExpression(Expression expression) + { + if (expression is null) + { + throw new ArgumentNullException(nameof(expression)); + } + + this.expressions.Add(expression); + } + + public Expression AndAlso(Expression left, Expression right) + => Expression.AndAlso(left, right); + + public Expression AndAlso(IEnumerable expressions) + { + if (expressions is null) + { + throw new ArgumentNullException(nameof(expressions)); + } + + if (!expressions.Any()) + { + throw new ArgumentException( + "A empty enumeration of expressions was provided and it should contain at least one expression", + nameof(expressions)); + } + + return expressions.Aggregate((left, right) => this.AndAlso(left, right)); + } + + public void Assign(Expression left, Expression right) + { + var expression = Expression.Assign(left, right); + this.AddExpression(expression); + } + + public Expression Block(Action implementationBuilder) + => this.Block(string.Empty, implementationBuilder); + + public Expression Block(string scopeName, Action implementationBuilder) + { + if (implementationBuilder is null) + { + throw new ArgumentNullException(nameof(implementationBuilder)); + } + + var actualScopeName = scopeName ?? string.Empty; + var implementationExpressionBuilder = new ImplementationExpressionBuilder(actualScopeName, this, this.expressionConfiguration); + implementationBuilder.Invoke(implementationExpressionBuilder); + + if (implementationExpressionBuilder.Expressions.Count == 0) + { + throw new InvalidOperationException($"No body block expressions were added for '{implementationExpressionBuilder.ScopeName}'."); + } + + var expression = Expression.Block(expressions: implementationExpressionBuilder.Expressions); + + return expression; + } + + public Expression Call(Expression instance, MethodInfo method, IEnumerable arguments) + => Expression.Call(instance, method, arguments); + + public Expression Call(Expression instance, MethodInfo method) + => Expression.Call(instance, method); + + public Expression Constant(T value) + => this.Constant(value, typeof(T)); + + public Expression Constant(object value, Type type) + => Expression.Constant(value, type); + + public Expression ConvertChecked(Expression expression, Type type) + => Expression.ConvertChecked(expression, type); + + public Expression ConvertChecked(Expression expression) + => this.ConvertChecked(expression, typeof(T)); + + public LabelTarget CreateLabelTarget(string name) + { + var labelTarget = NewLabelTarget(name); + this.labelTargets.Add(name, labelTarget); + return labelTarget; + + LabelTarget NewLabelTarget(string name) + { + if (this.Parent is null) + { + if (this.labelTargets.ContainsKey(name)) + { + throw new InvalidOperationException($"A label target for name '{name}' was already added."); + } + + return Expression.Label(name); + } + + if (this.labelTargets.ContainsKey(name)) + { + throw new InvalidOperationException($"A label target for name '{name}' under scope '{this.ScopeName}' was already added."); + } + + string prefixedName = $"{this.ScopeName}_{name}"; + return this.Parent.CreateLabelTarget(prefixedName); + } + } + + public ParameterExpression CreateVariable(string name, Type type) + { + var variableExpression = NewVariable(name, type); + this.variables.Add(name, variableExpression); + return variableExpression; + + ParameterExpression NewVariable(string name, Type type) + { + if (this.Parent is null) + { + if (this.variables.ContainsKey(name)) + { + throw new InvalidOperationException($"A variable for name '{name}' was already added."); + } + + return Expression.Variable(type, name); + } + + if (this.variables.ContainsKey(name)) + { + throw new InvalidOperationException($"A variable for name '{name}' under scope '{this.ScopeName}' was already added."); + } + + string prefixedName = $"{this.ScopeName}_{name}"; + return this.Parent.CreateVariable(prefixedName, type); + } + } + + public ParameterExpression CreateVariable(string name) + => this.CreateVariable(name, typeof(T)); + + public Expression Empty() + => Expression.Empty(); + + public Expression Equal(Expression left, Expression right) + => Expression.Equal(left, right); + + public LabelTarget GetLabelTarget(string name) + { + if (this.labelTargets.TryGetValue(name, out var labelTarget)) + { + return labelTarget; + } + + throw new KeyNotFoundException($"A label target with name '{name}' was not found."); + } + + public ParameterExpression GetParameter(string name) + { + if (this.expressionConfiguration.Parameters.TryGetValue(name, out var parameter)) + { + return parameter; + } + + throw new KeyNotFoundException($"A parameter with name '{name}' was not found."); + } + + public ParameterExpression GetVariable(string name) + { + if (this.variables.TryGetValue(name, out var variable)) + { + return variable; + } + + throw new KeyNotFoundException($"A variable with name '{name}' was not found."); + } + + public void Goto(LabelTarget labelTarget) + { + var expression = Expression.Goto(labelTarget); + this.AddExpression(expression); + } + + public Expression GreaterThan(Expression left, Expression right) + => Expression.GreaterThan(left, right); + + public Expression GreaterThanOrEqual(Expression left, Expression right) + => Expression.GreaterThanOrEqual(left, right); + + public void If( + Func testExpressionBuilder, + Func thenExpressionBuilder) + => this.If(testExpressionBuilder, thenExpressionBuilder, elseExpressionBuilder: null); + + public void If( + Func testExpressionBuilder, + Func thenExpressionBuilder, + Func elseExpressionBuilder) + { + if (testExpressionBuilder is null) + { + throw new ArgumentNullException(nameof(testExpressionBuilder)); + } + + if (thenExpressionBuilder is null) + { + throw new ArgumentNullException(nameof(thenExpressionBuilder)); + } + + var testExpression = testExpressionBuilder.Invoke(this); + var thenExpression = thenExpressionBuilder.Invoke(this); + + Expression expression; + if (elseExpressionBuilder is not null) + { + var elseExpression = elseExpressionBuilder.Invoke(this); + expression = Expression.IfThenElse(testExpression, thenExpression, elseExpression); + } + else + { + expression = Expression.IfThen(testExpression, thenExpression); + } + + this.AddExpression(expression); + } + + public void Label(LabelTarget labelTarget) + { + var expression = Expression.Label(labelTarget); + this.AddExpression(expression); + } + + public Expression LessThan(Expression left, Expression right) + => Expression.LessThan(left, right); + + public Expression LessThanOrEqual(Expression left, Expression right) + => Expression.LessThanOrEqual(left, right); + + public Expression Not(Expression expression) + => Expression.Not(expression); + + public Expression NotEqual(Expression left, Expression right) + => Expression.NotEqual(left, right); + + public Expression OrElse(Expression left, Expression right) + => Expression.OrElse(left, right); + + public Expression OrElse(IEnumerable expressions) + { + if (expressions is null) + { + throw new ArgumentNullException(nameof(expressions)); + } + + if (!expressions.Any()) + { + throw new ArgumentException( + "A empty enumeration of expressions was provided and it should contain at least one expression", + nameof(expressions)); + } + + return expressions.Aggregate((left, right) => this.OrElse(left, right)); + } + + public void Return(Expression returnValueExpression) + { + if (returnValueExpression is null) + { + throw new ArgumentNullException(nameof(returnValueExpression)); + } + + var returnExpression = Expression.Return(this.expressionConfiguration.ReturnLabelTarget, returnValueExpression); + this.AddExpression(returnExpression); + } + + public void Switch( + Expression switchExpressionValue, + Action switchExpressionBuilderAction) + { + if (switchExpressionValue is null) + { + throw new ArgumentNullException(nameof(switchExpressionValue)); + } + + if (switchExpressionBuilderAction is null) + { + throw new ArgumentNullException(nameof(switchExpressionBuilderAction)); + } + + var switchExpressionBuilder = new SwitchExpressionBuilder(this); + switchExpressionBuilderAction.Invoke(switchExpressionBuilder); + var expression = switchExpressionBuilder.CreateSwitchExpression(switchExpressionValue); + this.AddExpression(expression); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/NamedExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/NamedExpressionBuilder.cs new file mode 100644 index 00000000..8b8a32a2 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/NamedExpressionBuilder.cs @@ -0,0 +1,39 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + + internal sealed class NamedExpressionBuilder : INamedExpressionBuilder + { + private readonly ExpressionConfiguration expressionConfiguration; + + public NamedExpressionBuilder(ExpressionConfiguration expressionConfiguration) + { + this.expressionConfiguration = expressionConfiguration; + } + + public IParameterizedExpressionBuilder WithoutParameters() + { + this.expressionConfiguration.Parameters = new Dictionary(StringComparer.Ordinal); + + return new ParameterizedExpressionBuilder(this.expressionConfiguration); + } + + public IParameterizedExpressionBuilder WithParameters( + Action parametersConfigurationAction) + { + if (parametersConfigurationAction is null) + { + throw new ArgumentNullException(nameof(parametersConfigurationAction)); + } + + var expressionBuilderParametersConfiguration = new ExpressionParametersConfiguration(); + parametersConfigurationAction.Invoke(expressionBuilderParametersConfiguration); + + this.expressionConfiguration.Parameters = expressionBuilderParametersConfiguration.Parameters; + + return new ParameterizedExpressionBuilder(this.expressionConfiguration); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ParameterizedExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ParameterizedExpressionBuilder.cs new file mode 100644 index 00000000..86930183 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ParameterizedExpressionBuilder.cs @@ -0,0 +1,33 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +{ + using System; + using System.Linq.Expressions; + + internal sealed class ParameterizedExpressionBuilder : IParameterizedExpressionBuilder + { + private readonly ExpressionConfiguration expressionConfiguration; + + public ParameterizedExpressionBuilder(ExpressionConfiguration expressionConfiguration) + { + this.expressionConfiguration = expressionConfiguration; + } + + public IConfiguredSignatureExpressionBuilder HavingReturn(Type type, object defaultValue) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } + + this.expressionConfiguration.ReturnType = type; + this.expressionConfiguration.ReturnDefaultValue = defaultValue; + this.expressionConfiguration.ReturnLabelTarget + = Expression.Label(type, $"{this.expressionConfiguration.ExpressionName}_ReturnLabel"); + + return new ConfiguredSignatureExpressionBuilder(expressionConfiguration); + } + + public IConfiguredSignatureExpressionBuilder HavingReturn(object defaultValue) + => this.HavingReturn(typeof(T), defaultValue); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/SwitchExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/SwitchExpressionBuilder.cs new file mode 100644 index 00000000..5632e130 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/SwitchExpressionBuilder.cs @@ -0,0 +1,65 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + + internal sealed class SwitchExpressionBuilder : ISwitchExpressionBuilder + { + private readonly IImplementationExpressionBuilder implementationExpressionBuilder; + private readonly List switchCases; + private Expression defaultExpression; + + public SwitchExpressionBuilder(IImplementationExpressionBuilder implementationExpressionBuilder) + { + this.defaultExpression = null; + this.implementationExpressionBuilder = implementationExpressionBuilder; + this.switchCases = new List(); + } + + public ISwitchExpressionBuilder Case( + Expression caseExpression, + Func caseBodyExpressionBuilder) + { + if (caseExpression is null) + { + throw new ArgumentNullException(nameof(caseExpression)); + } + + return this.Case(new[] { caseExpression }, caseBodyExpressionBuilder); + } + + public ISwitchExpressionBuilder Case( + IEnumerable caseExpressions, + Func caseBodyExpressionBuilder) + { + if (caseExpressions is null) + { + throw new ArgumentNullException(nameof(caseExpressions)); + } + + if (caseBodyExpressionBuilder is null) + { + throw new ArgumentNullException(nameof(caseBodyExpressionBuilder)); + } + + var expression = caseBodyExpressionBuilder.Invoke(this.implementationExpressionBuilder); + var switchCaseExpression = Expression.SwitchCase(expression, caseExpressions); + this.switchCases.Add(switchCaseExpression); + return this; + } + + public SwitchExpression CreateSwitchExpression(Expression switchValueExpression) + => Expression.Switch(switchValueExpression, this.defaultExpression, this.switchCases.ToArray()); + + public void Default(Func defaultBodyExpressionBuilder) + { + if (defaultBodyExpressionBuilder is null) + { + throw new ArgumentNullException(nameof(defaultBodyExpressionBuilder)); + } + + defaultExpression = defaultBodyExpressionBuilder.Invoke(this.implementationExpressionBuilder); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/IConditionsTreeCompiler.cs b/src/Rules.Framework/Evaluation/Compiled/IConditionsTreeCompiler.cs deleted file mode 100644 index 86c7056d..00000000 --- a/src/Rules.Framework/Evaluation/Compiled/IConditionsTreeCompiler.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Rules.Framework.Evaluation.Compiled -{ - using Rules.Framework.Core; - using System; - using System.Collections.Generic; - using System.Text; - - internal interface IConditionsTreeCompiler - { - void Compile(IConditionNode conditionNode); - } -} diff --git a/src/Rules.Framework/Evaluation/Compiled/IRuleConditionsExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/IRuleConditionsExpressionBuilder.cs new file mode 100644 index 00000000..55cc7fc3 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/IRuleConditionsExpressionBuilder.cs @@ -0,0 +1,11 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using System; + using System.Linq.Expressions; + using Rules.Framework.Core; + + internal interface IRuleConditionsExpressionBuilder + { + Expression, bool>> BuildExpression(IConditionNode rootConditionNode); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeCompiler.cs b/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeCompiler.cs deleted file mode 100644 index ecdbe806..00000000 --- a/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeCompiler.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Rules.Framework.Evaluation.Compiled -{ - using Rules.Framework.Core.ConditionNodes; - using System; - using System.Collections.Generic; - using System.Linq.Expressions; - using System.Text; - - internal interface IValueConditionNodeCompiler - { - Func, bool> Compile( - ValueConditionNode valueConditionNode, - ParameterExpression parameterExpression); - } -} diff --git a/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeCompilerProvider.cs b/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeCompilerProvider.cs deleted file mode 100644 index 80fbaa52..00000000 --- a/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeCompilerProvider.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Rules.Framework.Evaluation.Compiled -{ - using Rules.Framework.Core; - using System; - using System.Collections.Generic; - using System.Text; - - internal interface IValueConditionNodeCompilerProvider - { - IValueConditionNodeCompiler GetValueConditionNodeCompiler(string multiplicity); - } -} diff --git a/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeExpressionBuilder.cs new file mode 100644 index 00000000..90e2076c --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeExpressionBuilder.cs @@ -0,0 +1,11 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + + internal interface IValueConditionNodeExpressionBuilder + { + void Build( + IImplementationExpressionBuilder builder, + BuildValueConditionNodeExpressionArgs args); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeExpressionBuilderProvider.cs b/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeExpressionBuilderProvider.cs new file mode 100644 index 00000000..55b71d6b --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeExpressionBuilderProvider.cs @@ -0,0 +1,7 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + internal interface IValueConditionNodeExpressionBuilderProvider + { + IValueConditionNodeExpressionBuilder GetExpressionBuilder(string multiplicity); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ManyToManyValueConditionNodeCompiler.cs b/src/Rules.Framework/Evaluation/Compiled/ManyToManyValueConditionNodeCompiler.cs deleted file mode 100644 index d7af8ffc..00000000 --- a/src/Rules.Framework/Evaluation/Compiled/ManyToManyValueConditionNodeCompiler.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace Rules.Framework.Evaluation.Compiled -{ - using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; - using System; - using System.Collections.Generic; - using System.Linq.Expressions; - using System.Text; - - internal sealed class ManyToManyValueConditionNodeCompiler : ValueConditionNodeCompilerBase, IValueConditionNodeCompiler - { - private readonly IConditionExpressionBuilderProvider conditionExpressionBuilderProvider; - - public ManyToManyValueConditionNodeCompiler( - IConditionExpressionBuilderProvider conditionExpressionBuilderProvider, - IDataTypesConfigurationProvider dataTypesConfigurationProvider) - : base(dataTypesConfigurationProvider) - { - this.conditionExpressionBuilderProvider = conditionExpressionBuilderProvider; - } - - public Func, bool> Compile( - ValueConditionNode valueConditionNode, - ParameterExpression parameterExpression) - { - DataTypeConfiguration dataTypeConfiguration = this.GetDataTypeConfiguration(valueConditionNode.DataType); - - var getConditionExpression = CreateGetConditionExpression(valueConditionNode.ConditionType, parameterExpression, dataTypeConfiguration); - - var leftOperandExpression = CreateConvertedArrayExpression(getConditionExpression, dataTypeConfiguration.Type); - - var rightOperandExpression = CreateConvertedArrayExpression(valueConditionNode.Operand, dataTypeConfiguration.Type); - - var conditionExpressionBuilder = this.conditionExpressionBuilderProvider.GetConditionExpressionBuilderFor(valueConditionNode.Operator, Multiplicities.ManyToMany); - var conditionExpression = conditionExpressionBuilder.BuildConditionExpression(leftOperandExpression, rightOperandExpression, dataTypeConfiguration); - - return Expression.Lambda, bool>>(conditionExpression, parameterExpression).Compile(); - } - } -} diff --git a/src/Rules.Framework/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilder.cs new file mode 100644 index 00000000..503a9527 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilder.cs @@ -0,0 +1,67 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using System; + using System.Collections.Generic; + using System.Reflection; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + + internal sealed class ManyToManyValueConditionNodeExpressionBuilder : IValueConditionNodeExpressionBuilder + { + private static readonly MethodInfo changeTypeMethodInfo = typeof(Convert) + .GetMethod( + nameof(Convert.ChangeType), + new[] { typeof(object), typeof(Type), typeof(IFormatProvider) }); + + private static readonly Type enumerableType = typeof(IEnumerable<>); + private readonly IConditionExpressionBuilderProvider conditionExpressionBuilderProvider; + + public ManyToManyValueConditionNodeExpressionBuilder(IConditionExpressionBuilderProvider conditionExpressionBuilderProvider) + { + this.conditionExpressionBuilderProvider = conditionExpressionBuilderProvider; + } + + public void Build( + IImplementationExpressionBuilder builder, + BuildValueConditionNodeExpressionArgs args) + { + var enumerableOfDataType = enumerableType.MakeGenericType(args.DataTypeConfiguration.Type); + var coalescedLeftOperandExpression = builder.CreateVariable("coalescedLeftOperand"); + var convertedLeftOperandExpression = builder.CreateVariable("convertedLeftOperand", enumerableOfDataType); + var convertedRightOperandExpression = builder.CreateVariable("convertedRightOperand", enumerableOfDataType); + + // Line 1. + var fallbackExpression = builder.Constant(value: null); + builder.If( + test => test.NotEqual(args.LeftOperandVariableExpression, fallbackExpression), + then => then.Block(block => block.Assign(coalescedLeftOperandExpression, args.LeftOperandVariableExpression)), + @else => @else.Block(block => block.Assign(coalescedLeftOperandExpression, block.Constant(args.DataTypeConfiguration.Default)))); + + // line 2. + builder.Assign( + convertedLeftOperandExpression, + builder.ConvertChecked(coalescedLeftOperandExpression, enumerableOfDataType)); + + // Line 3. + builder.Assign( + convertedRightOperandExpression, + builder.ConvertChecked(args.RightOperandVariableExpression, enumerableOfDataType)); + + // Line 4. + var conditionExpressionBuilder = this.conditionExpressionBuilderProvider + .GetConditionExpressionBuilderFor(args.Operator, Multiplicities.ManyToMany); + var buildConditionExpressionArgs = new BuildConditionExpressionArgs + { + DataTypeConfiguration = args.DataTypeConfiguration, + LeftHandOperand = convertedLeftOperandExpression, + RightHandOperand = convertedRightOperandExpression, + }; + builder.Assign( + args.ResultVariableExpression, + conditionExpressionBuilder.BuildConditionExpression(builder, buildConditionExpressionArgs)); + + // Line 5. + builder.AddExpression(builder.Empty()); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ManyToOneValueConditionNodeCompiler.cs b/src/Rules.Framework/Evaluation/Compiled/ManyToOneValueConditionNodeCompiler.cs deleted file mode 100644 index 1e82ef2a..00000000 --- a/src/Rules.Framework/Evaluation/Compiled/ManyToOneValueConditionNodeCompiler.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace Rules.Framework.Evaluation.Compiled -{ - using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; - using System; - using System.Collections.Generic; - using System.Linq.Expressions; - using System.Text; - - internal sealed class ManyToOneValueConditionNodeCompiler : ValueConditionNodeCompilerBase, IValueConditionNodeCompiler - { - private readonly IConditionExpressionBuilderProvider conditionExpressionBuilderProvider; - - public ManyToOneValueConditionNodeCompiler( - IConditionExpressionBuilderProvider conditionExpressionBuilderProvider, - IDataTypesConfigurationProvider dataTypesConfigurationProvider) - : base(dataTypesConfigurationProvider) - { - this.conditionExpressionBuilderProvider = conditionExpressionBuilderProvider; - } - - public Func, bool> Compile( - ValueConditionNode valueConditionNode, - ParameterExpression parameterExpression) - { - DataTypeConfiguration dataTypeConfiguration = this.GetDataTypeConfiguration(valueConditionNode.DataType); - - var getConditionExpression = CreateGetConditionExpression(valueConditionNode.ConditionType, parameterExpression, dataTypeConfiguration); - - var leftOperandExpression = CreateConvertedArrayExpression(getConditionExpression, dataTypeConfiguration.Type); - - var rightOperandExpression = CreateConvertedObjectExpression(valueConditionNode.Operand, dataTypeConfiguration.Type, dataTypeConfiguration.Default); - - var conditionExpressionBuilder = this.conditionExpressionBuilderProvider.GetConditionExpressionBuilderFor(valueConditionNode.Operator, Multiplicities.ManyToOne); - var conditionExpression = conditionExpressionBuilder.BuildConditionExpression(leftOperandExpression, rightOperandExpression, dataTypeConfiguration); - - return Expression.Lambda, bool>>(conditionExpression, parameterExpression).Compile(); - } - } -} diff --git a/src/Rules.Framework/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilder.cs new file mode 100644 index 00000000..ec9e1eb0 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilder.cs @@ -0,0 +1,67 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using System; + using System.Collections.Generic; + using System.Reflection; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + + internal sealed class ManyToOneValueConditionNodeExpressionBuilder : IValueConditionNodeExpressionBuilder + { + private static readonly MethodInfo changeTypeMethodInfo = typeof(Convert) + .GetMethod( + nameof(Convert.ChangeType), + new[] { typeof(object), typeof(Type), typeof(IFormatProvider) }); + + private static readonly Type enumerableType = typeof(IEnumerable<>); + private readonly IConditionExpressionBuilderProvider conditionExpressionBuilderProvider; + + public ManyToOneValueConditionNodeExpressionBuilder(IConditionExpressionBuilderProvider conditionExpressionBuilderProvider) + { + this.conditionExpressionBuilderProvider = conditionExpressionBuilderProvider; + } + + public void Build( + IImplementationExpressionBuilder builder, + BuildValueConditionNodeExpressionArgs args) + { + var enumerableOfDataType = enumerableType.MakeGenericType(args.DataTypeConfiguration.Type); + var coalescedLeftOperandExpression = builder.CreateVariable("coalescedLeftOperand"); + var convertedLeftOperandExpression = builder.CreateVariable("convertedLeftOperand", enumerableOfDataType); + var convertedRightOperandExpression = builder.CreateVariable("convertedRightOperand", args.DataTypeConfiguration.Type); + + // Line 1. + var fallbackExpression = builder.Constant(value: null); + builder.If( + test => test.NotEqual(args.LeftOperandVariableExpression, fallbackExpression), + then => then.Block(block => block.Assign(coalescedLeftOperandExpression, args.LeftOperandVariableExpression)), + @else => @else.Block(block => block.Assign(coalescedLeftOperandExpression, block.Constant(args.DataTypeConfiguration.Default)))); + + // line 2. + builder.Assign( + convertedLeftOperandExpression, + builder.ConvertChecked(coalescedLeftOperandExpression, enumerableOfDataType)); + + // Line 3. + builder.Assign( + convertedRightOperandExpression, + builder.ConvertChecked(args.RightOperandVariableExpression, args.DataTypeConfiguration.Type)); + + // Line 4. + var conditionExpressionBuilder = this.conditionExpressionBuilderProvider + .GetConditionExpressionBuilderFor(args.Operator, Multiplicities.ManyToOne); + var buildConditionExpressionArgs = new BuildConditionExpressionArgs + { + DataTypeConfiguration = args.DataTypeConfiguration, + LeftHandOperand = convertedLeftOperandExpression, + RightHandOperand = convertedRightOperandExpression, + }; + builder.Assign( + args.ResultVariableExpression, + conditionExpressionBuilder.BuildConditionExpression(builder, buildConditionExpressionArgs)); + + // Line 5. + builder.AddExpression(builder.Empty()); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/OneToManyValueConditionNodeCompiler.cs b/src/Rules.Framework/Evaluation/Compiled/OneToManyValueConditionNodeCompiler.cs deleted file mode 100644 index cf3e906d..00000000 --- a/src/Rules.Framework/Evaluation/Compiled/OneToManyValueConditionNodeCompiler.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace Rules.Framework.Evaluation.Compiled -{ - using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Evaluation; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq.Expressions; - using System.Reflection; - using System.Text; - - internal sealed class OneToManyValueConditionNodeCompiler : ValueConditionNodeCompilerBase, IValueConditionNodeCompiler - { - private readonly IConditionExpressionBuilderProvider conditionExpressionBuilderProvider; - - public OneToManyValueConditionNodeCompiler( - IConditionExpressionBuilderProvider conditionExpressionBuilderProvider, - IDataTypesConfigurationProvider dataTypesConfigurationProvider) - : base(dataTypesConfigurationProvider) - { - this.conditionExpressionBuilderProvider = conditionExpressionBuilderProvider; - } - - public Func, bool> Compile( - ValueConditionNode valueConditionNode, - ParameterExpression parameterExpression) - { - DataTypeConfiguration dataTypeConfiguration = this.GetDataTypeConfiguration(valueConditionNode.DataType); - - var getConditionExpression = CreateGetConditionExpression(valueConditionNode.ConditionType, parameterExpression, dataTypeConfiguration); - - var leftOperandExpression = CreateConvertedObjectExpression(getConditionExpression, dataTypeConfiguration.Type, dataTypeConfiguration.Default); - - var rightOperandExpression = CreateConvertedArrayExpression(valueConditionNode.Operand, dataTypeConfiguration.Type); - - var conditionExpressionBuilder = this.conditionExpressionBuilderProvider.GetConditionExpressionBuilderFor(valueConditionNode.Operator, Multiplicities.OneToMany); - var conditionExpression = conditionExpressionBuilder.BuildConditionExpression(leftOperandExpression, rightOperandExpression, dataTypeConfiguration); - - return Expression.Lambda, bool>>(conditionExpression, parameterExpression).Compile(); - } - } -} diff --git a/src/Rules.Framework/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilder.cs new file mode 100644 index 00000000..7d02f1c4 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilder.cs @@ -0,0 +1,81 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq.Expressions; + using System.Reflection; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + + internal sealed class OneToManyValueConditionNodeExpressionBuilder : IValueConditionNodeExpressionBuilder + { + private static readonly MethodInfo changeTypeMethodInfo = typeof(Convert) + .GetMethod( + nameof(Convert.ChangeType), + new[] { typeof(object), typeof(Type), typeof(IFormatProvider) }); + + private static readonly Type enumerableType = typeof(IEnumerable<>); + private readonly IConditionExpressionBuilderProvider conditionExpressionBuilderProvider; + + public OneToManyValueConditionNodeExpressionBuilder(IConditionExpressionBuilderProvider conditionExpressionBuilderProvider) + { + this.conditionExpressionBuilderProvider = conditionExpressionBuilderProvider; + } + + public void Build( + IImplementationExpressionBuilder builder, + BuildValueConditionNodeExpressionArgs args) + { + var enumerableOfDataType = enumerableType.MakeGenericType(args.DataTypeConfiguration.Type); + var coalescedLeftOperandExpression = builder.CreateVariable("coalescedLeftOperand"); + var convertedLeftOperandExpression = builder.CreateVariable("convertedLeftOperand", args.DataTypeConfiguration.Type); + var convertedRightOperandExpression = builder.CreateVariable("convertedRightOperand", enumerableOfDataType); + + // Line 1. + var fallbackExpression = builder.Constant(value: null); + builder.If( + test => test.NotEqual(args.LeftOperandVariableExpression, fallbackExpression), + then => then.Block(block => block.Assign(coalescedLeftOperandExpression, args.LeftOperandVariableExpression)), + @else => @else.Block(block => block.Assign(coalescedLeftOperandExpression, block.Constant(args.DataTypeConfiguration.Default)))); + + // line 2. + var convertLeftOperandExpression = builder.ConvertChecked( + builder.Call( + instance: null, + changeTypeMethodInfo, + new Expression[] + { + coalescedLeftOperandExpression, + builder.Constant(args.DataTypeConfiguration.Type), + builder.Constant(CultureInfo.InvariantCulture), + }), + args.DataTypeConfiguration.Type); + builder.Assign( + convertedLeftOperandExpression, + convertLeftOperandExpression); + + // Line 3. + builder.Assign( + convertedRightOperandExpression, + builder.ConvertChecked(args.RightOperandVariableExpression, enumerableOfDataType)); + + // Line 4. + var conditionExpressionBuilder = this.conditionExpressionBuilderProvider + .GetConditionExpressionBuilderFor(args.Operator, Multiplicities.OneToMany); + var buildConditionExpressionArgs = new BuildConditionExpressionArgs + { + DataTypeConfiguration = args.DataTypeConfiguration, + LeftHandOperand = convertedLeftOperandExpression, + RightHandOperand = convertedRightOperandExpression, + }; + builder.Assign( + args.ResultVariableExpression, + conditionExpressionBuilder.BuildConditionExpression(builder, buildConditionExpressionArgs)); + + // Line 5. + builder.AddExpression(builder.Empty()); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/OneToOneValueConditionNodeCompiler.cs b/src/Rules.Framework/Evaluation/Compiled/OneToOneValueConditionNodeCompiler.cs deleted file mode 100644 index 8610ac59..00000000 --- a/src/Rules.Framework/Evaluation/Compiled/OneToOneValueConditionNodeCompiler.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace Rules.Framework.Evaluation.Compiled -{ - using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Evaluation; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; - using System; - using System.Collections.Generic; - using System.Linq.Expressions; - - internal sealed class OneToOneValueConditionNodeCompiler : ValueConditionNodeCompilerBase, IValueConditionNodeCompiler - { - private readonly IConditionExpressionBuilderProvider conditionExpressionBuilderProvider; - - public OneToOneValueConditionNodeCompiler( - IConditionExpressionBuilderProvider conditionExpressionBuilderProvider, - IDataTypesConfigurationProvider dataTypesConfigurationProvider) - : base(dataTypesConfigurationProvider) - { - this.conditionExpressionBuilderProvider = conditionExpressionBuilderProvider; - } - public Func, bool> Compile( - ValueConditionNode valueConditionNode, - ParameterExpression parameterExpression) - { - DataTypeConfiguration dataTypeConfiguration = this.GetDataTypeConfiguration(valueConditionNode.DataType); - - var getConditionExpression = CreateGetConditionExpression(valueConditionNode.ConditionType, parameterExpression, dataTypeConfiguration); - - var leftOperandExpression = CreateConvertedObjectExpression(getConditionExpression, dataTypeConfiguration.Type, dataTypeConfiguration.Default); - - var rightOperandExpression = CreateConvertedObjectExpression(valueConditionNode.Operand, dataTypeConfiguration.Type, dataTypeConfiguration.Default); - - var conditionExpressionBuilder = this.conditionExpressionBuilderProvider.GetConditionExpressionBuilderFor(valueConditionNode.Operator, Multiplicities.OneToOne); - var conditionExpression = conditionExpressionBuilder.BuildConditionExpression(leftOperandExpression, rightOperandExpression, dataTypeConfiguration); - - return Expression.Lambda, bool>>(conditionExpression, parameterExpression).Compile(); - } - } -} diff --git a/src/Rules.Framework/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilder.cs new file mode 100644 index 00000000..fc86d09a --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilder.cs @@ -0,0 +1,80 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq.Expressions; + using System.Reflection; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + + internal sealed class OneToOneValueConditionNodeExpressionBuilder : IValueConditionNodeExpressionBuilder + { + private static readonly MethodInfo changeTypeMethodInfo = typeof(Convert) + .GetMethod( + nameof(Convert.ChangeType), + new[] { typeof(object), typeof(Type), typeof(IFormatProvider) }); + + private static readonly Type enumerableType = typeof(IEnumerable<>); + private readonly IConditionExpressionBuilderProvider conditionExpressionBuilderProvider; + + public OneToOneValueConditionNodeExpressionBuilder(IConditionExpressionBuilderProvider conditionExpressionBuilderProvider) + { + this.conditionExpressionBuilderProvider = conditionExpressionBuilderProvider; + } + + public void Build( + IImplementationExpressionBuilder builder, + BuildValueConditionNodeExpressionArgs args) + { + var coalescedLeftOperandExpression = builder.CreateVariable("coalescedLeftOperand"); + var convertedLeftOperandExpression = builder.CreateVariable("convertedLeftOperand", args.DataTypeConfiguration.Type); + var convertedRightOperandExpression = builder.CreateVariable("convertedRightOperand", args.DataTypeConfiguration.Type); + + // Line 1. + var fallbackExpression = builder.Constant(value: null); + builder.If( + test => test.NotEqual(args.LeftOperandVariableExpression, fallbackExpression), + then => then.Block(block => block.Assign(coalescedLeftOperandExpression, args.LeftOperandVariableExpression)), + @else => @else.Block(block => block.Assign(coalescedLeftOperandExpression, block.Constant(args.DataTypeConfiguration.Default)))); + + // line 2. + var convertLeftOperandExpression = builder.ConvertChecked( + builder.Call( + instance: null, + changeTypeMethodInfo, + new Expression[] + { + coalescedLeftOperandExpression, + builder.Constant(args.DataTypeConfiguration.Type), + builder.Constant(CultureInfo.InvariantCulture), + }), + args.DataTypeConfiguration.Type); + builder.Assign( + convertedLeftOperandExpression, + convertLeftOperandExpression); + + // Line 3. + builder.Assign( + convertedRightOperandExpression, + builder.ConvertChecked(args.RightOperandVariableExpression, args.DataTypeConfiguration.Type)); + + // Line 4. + var conditionExpressionBuilder = this.conditionExpressionBuilderProvider + .GetConditionExpressionBuilderFor(args.Operator, Multiplicities.OneToOne); + var buildConditionExpressionArgs = new BuildConditionExpressionArgs + { + DataTypeConfiguration = args.DataTypeConfiguration, + LeftHandOperand = convertedLeftOperandExpression, + RightHandOperand = convertedRightOperandExpression, + }; + builder.Assign( + args.ResultVariableExpression, + conditionExpressionBuilder.BuildConditionExpression(builder, buildConditionExpressionArgs)); + + // Line 5. + builder.AddExpression(builder.Empty()); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/RuleConditionsExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/RuleConditionsExpressionBuilder.cs new file mode 100644 index 00000000..5b731a1d --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/RuleConditionsExpressionBuilder.cs @@ -0,0 +1,195 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using Rules.Framework.Core; + using Rules.Framework.Core.ConditionNodes; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + + internal sealed class RuleConditionsExpressionBuilder : IRuleConditionsExpressionBuilder + { + private static readonly MethodInfo conditionsGetterMethod = typeof(EvaluationContext) + .GetProperty("Conditions") + .GetGetMethod(); + + private static readonly MethodInfo evaluationContextMatchModeGetterMethod = typeof(EvaluationContext).GetProperty("MatchMode").GetGetMethod(); + private static readonly MethodInfo evaluationContextMissingConditionsBehaviorGetterMethod = typeof(EvaluationContext).GetProperty("MissingConditionBehavior").GetGetMethod(); + + private static readonly MethodInfo getValueOrDefaultMethod = typeof(ConditionsValueLookupExtension) + .GetMethod(nameof(ConditionsValueLookupExtension.GetValueOrDefault)) + .MakeGenericMethod(typeof(TConditionType)); + + private static readonly MethodInfo multiplicityEvaluateMethod = typeof(MultiplicityEvaluator) + .GetMethod(nameof(MultiplicityEvaluator.Evaluate)); + + private readonly IDataTypesConfigurationProvider dataTypesConfigurationProvider; + private readonly IValueConditionNodeExpressionBuilderProvider valueConditionNodeExpressionBuilderProvider; + + public RuleConditionsExpressionBuilder( + IValueConditionNodeExpressionBuilderProvider valueConditionNodeExpressionBuilderProvider, + IDataTypesConfigurationProvider dataTypesConfigurationProvider) + { + this.valueConditionNodeExpressionBuilderProvider = valueConditionNodeExpressionBuilderProvider; + this.dataTypesConfigurationProvider = dataTypesConfigurationProvider; + } + + public Expression, bool>> BuildExpression(IConditionNode rootConditionNode) + { + var expressionResult = ExpressionBuilder.NewExpression("EvaluateConditions") + .WithParameters(p => + { + p.CreateParameter>("evaluationContext"); + }) + .HavingReturn(defaultValue: false) + .SetImplementation(x => + { + var resultVariableExpression = x.CreateVariable("result"); + + this.BuildExpression(rootConditionNode, x); + + x.Return(resultVariableExpression); + }) + .Build(); + + return Expression.Lambda, bool>>( + body: expressionResult.Implementation, + parameters: expressionResult.Parameters); + } + + private static void BuildExpressionForBehaviorOnNullLeftOperand(IImplementationExpressionBuilder builder) + { + var leftOperandVariableExpression = builder.GetVariable("leftOperand"); + var resultVariableExpression = builder.GetVariable("result"); + var jumpToLabelTarget = builder.GetLabelTarget("Label_EndValueConditionNode"); + var parameterExpression = builder.GetParameter("evaluationContext"); + + builder.If( + test => test.Equal(leftOperandVariableExpression, test.Constant(value: null)), + then => then.Block(block1 => + { + block1.If( + test => test.Equal(test.Call(parameterExpression, evaluationContextMissingConditionsBehaviorGetterMethod), test.Constant(MissingConditionBehaviors.Discard)), + then => then.Block(block2 => + { + block2.Assign(resultVariableExpression, block2.Constant(value: false)); + block2.Goto(jumpToLabelTarget); + })); + block1.If( + test => test.Equal(test.Call(parameterExpression, evaluationContextMatchModeGetterMethod), test.Constant(MatchModes.Search)), + then => then.Block(block3 => + { + block3.Assign(resultVariableExpression, then.Constant(value: true)); + block3.Goto(jumpToLabelTarget); + })); + })); + } + + private void BuildExpression(IConditionNode conditionNode, IImplementationExpressionBuilder builder) + { + switch (conditionNode) + { + case ComposedConditionNode composedConditionNode: + var conditionExpressions = new List(composedConditionNode.ChildConditionNodes.Count()); + var counter = 0; + foreach (var childConditionNode in composedConditionNode.ChildConditionNodes) + { + string scopeName = $"{builder.ScopeName}_C{counter}"; + var blockExpression = builder.Block(scopeName, x => + { + var childResultVariableExpression = x.CreateVariable("result"); + this.BuildExpression(childConditionNode, x); + conditionExpressions.Add(childResultVariableExpression); + }); + builder.AddExpression(blockExpression); + counter++; + } + + var conditionExpression = composedConditionNode.LogicalOperator switch + { + LogicalOperators.And => builder.AndAlso(conditionExpressions), + LogicalOperators.Or => builder.OrElse(conditionExpressions), + _ => throw new NotSupportedException() + }; + var composedResultVariableExpression = builder.GetVariable("result"); + builder.Assign(composedResultVariableExpression, conditionExpression); + break; + + case ValueConditionNode valueConditionNode: + // Variables, constants, and labels. + var leftOperandVariableExpression = builder.CreateVariable("leftOperand"); + var rightOperandVariableExpression = builder.CreateVariable("rightOperand"); + var resultVariableExpression = builder.GetVariable("result"); + var jumpToLabelTarget = builder.CreateLabelTarget("Label_EndValueConditionNode"); + var parameterExpression = builder.GetParameter("evaluationContext"); + + // Line 1. + var getConditionsCallExpression = builder.Call(parameterExpression, conditionsGetterMethod); + var getConditionValueCallExpression = builder + .Call(instance: null, getValueOrDefaultMethod, new Expression[] { getConditionsCallExpression, builder.Constant(valueConditionNode.ConditionType) }); + builder.Assign(leftOperandVariableExpression, getConditionValueCallExpression); + // Line 2. + builder.Assign(rightOperandVariableExpression, builder.Constant(valueConditionNode.Operand)); + // Line 3. + BuildExpressionForBehaviorOnNullLeftOperand(builder); + // Lines 4 and 5. + this.BuildFetchAndSwitchOverMultiplicity(builder, valueConditionNode); + // Line 6. + builder.Label(jumpToLabelTarget); + break; + + default: + throw new NotSupportedException($"Unsupported condition node: '{conditionNode.GetType().Name}'."); + } + } + + private void BuildFetchAndSwitchOverMultiplicity( + IImplementationExpressionBuilder builder, + ValueConditionNode valueConditionNode) + { + var operatorConstantExpression = builder.Constant(valueConditionNode.Operator); + var multiplicityVariableExpression = builder.CreateVariable("multiplicity", typeof(string)); + var leftOperandVariableExpression = builder.GetVariable("leftOperand"); + var rightOperandVariableExpression = builder.GetVariable("rightOperand"); + var resultVariableExpression = builder.GetVariable("result"); + // Line 4. + builder.Assign(multiplicityVariableExpression, builder.Call( + instance: null, + multiplicityEvaluateMethod, + new Expression[] { leftOperandVariableExpression, operatorConstantExpression, rightOperandVariableExpression })); + // Line 5. + builder.Switch(multiplicityVariableExpression, @switch => + { + var dataTypeConfiguration = this.dataTypesConfigurationProvider.GetDataTypeConfiguration(valueConditionNode.DataType); + var operatorMetadata = OperatorsMetadata.AllByOperator[valueConditionNode.Operator]; + + foreach (var multiplicity in operatorMetadata.SupportedMultiplicities) + { + string scopeName = $"{valueConditionNode.ConditionType}_{multiplicity.Replace('-', '_')}"; + @switch.Case( + builder.Constant(multiplicity), + caseBuilder => caseBuilder.Block(scopeName, block => + { + var valueConditionNodeExpressionBuilder = this.valueConditionNodeExpressionBuilderProvider + .GetExpressionBuilder(multiplicity); + var args = new BuildValueConditionNodeExpressionArgs + { + DataTypeConfiguration = dataTypeConfiguration, + LeftOperandVariableExpression = leftOperandVariableExpression, + Operator = operatorMetadata.Operator, + ResultVariableExpression = resultVariableExpression, + RightOperandVariableExpression = rightOperandVariableExpression, + }; + valueConditionNodeExpressionBuilder.Build( + block, + args); + })); + } + @switch.Default(defaultBuilder => defaultBuilder.Empty()); + }); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerBase.cs b/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerBase.cs deleted file mode 100644 index 7f6eaddb..00000000 --- a/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerBase.cs +++ /dev/null @@ -1,124 +0,0 @@ -namespace Rules.Framework.Evaluation.Compiled -{ - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Evaluation; - using System; - using System.Collections; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using System.Linq.Expressions; - using System.Reflection; - using System.Text; - - internal class ValueConditionNodeCompilerBase - { - protected static readonly MethodInfo arrayEmptyMethodInfo = typeof(Array) - .GetMethod( - nameof(Array.Empty), - Array.Empty()); - protected static readonly MethodInfo changeTypeMethodInfo = typeof(Convert) - .GetMethod( - nameof(Convert.ChangeType), - new[] { typeof(object), typeof(Type), typeof(IFormatProvider) }); - protected static readonly Type enumerableType = typeof(IEnumerable<>); - protected static readonly Type systemType = typeof(Type); - protected static readonly Type formatProviderType = typeof(IFormatProvider); - protected static readonly Type objectType = typeof(object); - protected readonly IDataTypesConfigurationProvider dataTypesConfigurationProvider; - - protected ValueConditionNodeCompilerBase(IDataTypesConfigurationProvider dataTypesConfigurationProvider) - { - this.dataTypesConfigurationProvider = dataTypesConfigurationProvider; - } - - protected static object ConvertToDataType(object operand, string paramName, Type dataType, object dataDefault) - { - try - { - return Convert.ChangeType(operand - ?? dataDefault, dataType, CultureInfo.InvariantCulture); - } - catch (Exception ex) - { - throw new ArgumentException($"Parameter value or contained value is not convertible to {dataType.Name}.", paramName, ex); - } - } - - protected static Expression CreateGetConditionExpression(TConditionType conditionType, ParameterExpression parameterExpression, DataTypeConfiguration dataTypeConfiguration) - { - var returnTargetExpression = Expression.Label(objectType); - var outVariableExpression = Expression.Variable(objectType, "conditionValue"); - var dictionaryTryGetValueMethodInfo = typeof(IDictionary).GetMethod("TryGetValue"); - var conditionTypeConstantExpression = Expression.Constant(conditionType, typeof(TConditionType)); - var testExpression = Expression.Call(parameterExpression, dictionaryTryGetValueMethodInfo, conditionTypeConstantExpression, outVariableExpression); - var defaultValueExpression = Expression.Constant(dataTypeConfiguration.Default, objectType); - var ifThenExpression = Expression.IfThen(testExpression, Expression.Return(returnTargetExpression, outVariableExpression)); - return Expression.Block(new[] { outVariableExpression }, new Expression[] { ifThenExpression, Expression.Label(returnTargetExpression, defaultValueExpression) }); - } - - protected static Expression CreateConvertedArrayExpression(Expression operandExpression, Type dataType) - { - var testExpression = Expression.NotEqual(operandExpression, Expression.Constant(null)); - var returnTargetLabelExpression = Expression.Label(objectType); - var defaultValueExpression = Expression.Constant( - arrayEmptyMethodInfo - .MakeGenericMethod(dataType) - .Invoke(obj: null, parameters: null), - objectType); - var ifThenElseExpression = Expression.IfThenElse( - testExpression, - Expression.Return(returnTargetLabelExpression, operandExpression), - Expression.Return(returnTargetLabelExpression, defaultValueExpression)); - var nullCoalesceBlock = Expression.Block(ifThenElseExpression, Expression.Label(returnTargetLabelExpression, defaultValueExpression)); - - return Expression.ConvertChecked(nullCoalesceBlock, enumerableType.MakeGenericType(dataType)); - } - - protected static Expression CreateConvertedArrayExpression(object operand, Type dataType) - { - IEnumerable operandAsTypedEnumerable = ConvertToTypedEnumerable(operand, nameof(operand), dataType); - IEnumerable arrayInitializerExpressions = operandAsTypedEnumerable.Select(o => Expression.Constant(o, dataType)); - return Expression.NewArrayInit(dataType, arrayInitializerExpressions); - } - - protected static Expression CreateConvertedObjectExpression(Expression operandExpression, Type dataType, object dataTypeDefault) - { - var testExpression = Expression.NotEqual(operandExpression, Expression.Constant(null)); - var returnTargetLabelExpression = Expression.Label(objectType); - var defaultValueExpression = Expression.Constant(dataTypeDefault, objectType); - var ifThenElseExpression = Expression.IfThenElse( - testExpression, - Expression.Return(returnTargetLabelExpression, operandExpression), - Expression.Return(returnTargetLabelExpression, defaultValueExpression)); - var nullCoalesceBlock = Expression.Block(ifThenElseExpression, Expression.Label(returnTargetLabelExpression, defaultValueExpression)); - - var operandConvertExpression = Expression.Call( - changeTypeMethodInfo, - nullCoalesceBlock, - Expression.Constant(dataType, systemType), - Expression.Constant(CultureInfo.InvariantCulture, formatProviderType)); - return Expression.ConvertChecked(operandConvertExpression, dataType); - } - - protected static Expression CreateConvertedObjectExpression(object operand, Type dataType, object dataTypeDefault) - { - var rightOperandConverted = ConvertToDataType(operand, "operand", dataType, dataTypeDefault); - return Expression.ConvertChecked(Expression.Constant(rightOperandConverted), dataType); - } - - protected static IEnumerable ConvertToTypedEnumerable(object operand, string paramName, Type dataType) - { - if (operand is not string && operand is IEnumerable enumerable) - { - return enumerable.Cast().Select(o => ConvertToDataType(o, paramName, dataType, null)); - } - - throw new ArgumentException($"Parameter must be of type {nameof(IEnumerable)}.", paramName); - } - - protected DataTypeConfiguration GetDataTypeConfiguration(DataTypes dataType) - => this.dataTypesConfigurationProvider.GetDataTypeConfiguration(dataType); - } -} diff --git a/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerProvider.cs b/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerProvider.cs deleted file mode 100644 index 70611003..00000000 --- a/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeCompilerProvider.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace Rules.Framework.Evaluation.Compiled -{ - using Rules.Framework.Core; - using Rules.Framework.Evaluation; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; - using System; - using System.Collections; - using System.Collections.Generic; - using System.Linq; - using System.Text; - - internal sealed class ValueConditionNodeCompilerProvider : IValueConditionNodeCompilerProvider - { - private readonly Dictionary compilers; - - public ValueConditionNodeCompilerProvider( - IConditionExpressionBuilderProvider conditionExpressionBuilderProvider, - IDataTypesConfigurationProvider dataTypesConfigurationProvider) - { - this.compilers = new Dictionary(StringComparer.Ordinal) - { - { Multiplicities.OneToOne, new OneToOneValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider) }, - { Multiplicities.OneToMany, new OneToManyValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider) }, - { Multiplicities.ManyToOne, new ManyToOneValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider) }, - { Multiplicities.ManyToMany, new ManyToManyValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider) } - }; - } - - public IValueConditionNodeCompiler GetValueConditionNodeCompiler(string multiplicity) - { - if (this.compilers.TryGetValue(multiplicity, out var compiler)) - { - return compiler; - } - - throw new NotSupportedException($"No compiler for multiplicity '{multiplicity}' defined."); - } - } -} diff --git a/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeExpressionBuilderProvider.cs b/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeExpressionBuilderProvider.cs new file mode 100644 index 00000000..92d931fd --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeExpressionBuilderProvider.cs @@ -0,0 +1,34 @@ +namespace Rules.Framework.Evaluation.Compiled +{ + using System; + using System.Collections.Generic; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + + internal sealed class ValueConditionNodeExpressionBuilderProvider : IValueConditionNodeExpressionBuilderProvider + { + private readonly Dictionary compilers; + + public ValueConditionNodeExpressionBuilderProvider( + IConditionExpressionBuilderProvider conditionExpressionBuilderProvider) + { + this.compilers = new Dictionary(StringComparer.Ordinal) + { + { Multiplicities.OneToOne, new OneToOneValueConditionNodeExpressionBuilder(conditionExpressionBuilderProvider) }, + { Multiplicities.OneToMany, new OneToManyValueConditionNodeExpressionBuilder(conditionExpressionBuilderProvider) }, + { Multiplicities.ManyToOne, new ManyToOneValueConditionNodeExpressionBuilder(conditionExpressionBuilderProvider) }, + { Multiplicities.ManyToMany, new ManyToManyValueConditionNodeExpressionBuilder(conditionExpressionBuilderProvider) }, + }; + } + + public IValueConditionNodeExpressionBuilder GetExpressionBuilder(string multiplicity) + { + if (this.compilers.TryGetValue(multiplicity, out var compiler)) + { + return compiler; + } + + throw new NotSupportedException($"No compiler for multiplicity '{multiplicity}' defined."); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/ConditionsTreeAnalyzer.cs b/src/Rules.Framework/Evaluation/ConditionsTreeAnalyzer.cs index 88b38051..8ff589a7 100644 --- a/src/Rules.Framework/Evaluation/ConditionsTreeAnalyzer.cs +++ b/src/Rules.Framework/Evaluation/ConditionsTreeAnalyzer.cs @@ -1,7 +1,5 @@ namespace Rules.Framework.Evaluation { - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; using System; using System.Collections.Generic; using System.Linq; diff --git a/src/Rules.Framework/Evaluation/MultiplicityEvaluator.cs b/src/Rules.Framework/Evaluation/MultiplicityEvaluator.cs index 7428801a..c3c7c945 100644 --- a/src/Rules.Framework/Evaluation/MultiplicityEvaluator.cs +++ b/src/Rules.Framework/Evaluation/MultiplicityEvaluator.cs @@ -1,12 +1,12 @@ namespace Rules.Framework.Evaluation { - using Rules.Framework.Core; using System; using System.Collections; + using Rules.Framework.Core; internal sealed class MultiplicityEvaluator : IMultiplicityEvaluator { - public string EvaluateMultiplicity(object leftOperand, Operators @operator, object rightOperand) => leftOperand switch + public static string Evaluate(object leftOperand, Operators @operator, object rightOperand) => leftOperand switch { IEnumerable when leftOperand is not string && rightOperand is IEnumerable && rightOperand is not string => Multiplicities.ManyToMany, IEnumerable when leftOperand is not string => Multiplicities.ManyToOne, @@ -17,6 +17,8 @@ null when OperatorSupportsOneMultiplicityLeftOperand(@operator) => Multiplicitie _ => throw new NotSupportedException($"Could not evaluate multiplicity. [Left Operand: {leftOperand} | Operator: {@operator} | Right Operand: {rightOperand}]") }; + public string EvaluateMultiplicity(object leftOperand, Operators @operator, object rightOperand) => Evaluate(leftOperand, @operator, rightOperand); + private static bool OperatorSupportsOneMultiplicityLeftOperand(Operators @operator) { if (!OperatorsMetadata.AllByOperator.TryGetValue(@operator, out var operatorMetadata)) diff --git a/src/Rules.Framework/Rules.Framework.csproj b/src/Rules.Framework/Rules.Framework.csproj index f78bc4f7..010f970f 100644 --- a/src/Rules.Framework/Rules.Framework.csproj +++ b/src/Rules.Framework/Rules.Framework.csproj @@ -33,7 +33,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj b/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj index 1cc2b2bb..d6a52a60 100644 --- a/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj +++ b/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj @@ -5,12 +5,12 @@ net48;net6.0 enable enable - 10.0 + 10.0 - - + + diff --git a/tests/Rules.Framework.IntegrationTests/Rules.Framework.IntegrationTests.csproj b/tests/Rules.Framework.IntegrationTests/Rules.Framework.IntegrationTests.csproj index 8aa0683e..4257d010 100644 --- a/tests/Rules.Framework.IntegrationTests/Rules.Framework.IntegrationTests.csproj +++ b/tests/Rules.Framework.IntegrationTests/Rules.Framework.IntegrationTests.csproj @@ -7,12 +7,13 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + all diff --git a/tests/Rules.Framework.IntegrationTests/RulesFromJsonFile.cs b/tests/Rules.Framework.IntegrationTests/RulesFromJsonFile.cs index 9ffe6cf1..14284351 100644 --- a/tests/Rules.Framework.IntegrationTests/RulesFromJsonFile.cs +++ b/tests/Rules.Framework.IntegrationTests/RulesFromJsonFile.cs @@ -2,6 +2,7 @@ namespace Rules.Framework.IntegrationTests { using System; using System.Collections.Generic; + using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -20,20 +21,20 @@ internal class RulesFromJsonFile public async Task> FromJsonFileAsync(string filePath, bool serializedContent = true) where TContentType : new() { - JsonContentSerializationProvider serializationProvider = new JsonContentSerializationProvider(); + var serializationProvider = new JsonContentSerializationProvider(); - using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) - using (StreamReader streamReader = new StreamReader(fileStream)) + using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) + using (var streamReader = new StreamReader(fileStream)) { - string contents = await streamReader.ReadToEndAsync(); - IEnumerable ruleDataModels = await Task.Run(() => JsonConvert.DeserializeObject>(contents)); + var contents = await streamReader.ReadToEndAsync(); + var ruleDataModels = await Task.Run(() => JsonConvert.DeserializeObject>(contents)); - List> rules = new List>(ruleDataModels.Count()); - foreach (RuleDataModel ruleDataModel in ruleDataModels) + var rules = new List>(ruleDataModels.Count()); + foreach (var ruleDataModel in ruleDataModels) { - TContentType contentType = GetContentType(ruleDataModel.ContentTypeCode); + var contentType = GetContentType(ruleDataModel.ContentTypeCode); - IRuleBuilder ruleBuilder = RuleBuilder.NewRule() + var ruleBuilder = RuleBuilder.NewRule() .WithName(ruleDataModel.Name) .WithDatesInterval(ruleDataModel.DateBegin, ruleDataModel.DateEnd); @@ -51,7 +52,7 @@ public async Task> FromJsonFileAs ruleBuilder.WithContentContainer(new ContentContainer(contentType, (t) => RulesFromJsonFile.Parse(ruleDataModel.Content, t))); } - RuleBuilderResult ruleBuilderResult = ruleBuilder.Build(); + var ruleBuilderResult = ruleBuilder.Build(); if (ruleBuilderResult.IsSuccess) { @@ -76,21 +77,21 @@ private static T Parse(string value) => (T)Parse(value, typeof(T)); private static object Parse(string value, Type type) - => type.IsEnum ? Enum.Parse(type, value) : Convert.ChangeType(value, type); + => type.IsEnum ? Enum.Parse(type, value) : Convert.ChangeType(value, type, CultureInfo.InvariantCulture); private IConditionNode ConvertConditionNode(IConditionNodeBuilder conditionNodeBuilder, ConditionNodeDataModel conditionNodeDataModel) { - LogicalOperators logicalOperator = RulesFromJsonFile.Parse(conditionNodeDataModel.LogicalOperator); + var logicalOperator = RulesFromJsonFile.Parse(conditionNodeDataModel.LogicalOperator); if (logicalOperator == LogicalOperators.Eval) { return this.CreateValueConditionNode(conditionNodeBuilder, conditionNodeDataModel); } else { - IComposedConditionNodeBuilder composedConditionNodeBuilder = conditionNodeBuilder.AsComposed() + var composedConditionNodeBuilder = conditionNodeBuilder.AsComposed() .WithLogicalOperator(logicalOperator); - foreach (ConditionNodeDataModel child in conditionNodeDataModel.ChildConditionNodes) + foreach (var child in conditionNodeDataModel.ChildConditionNodes) { composedConditionNodeBuilder.AddCondition(cnb => this.ConvertConditionNode(cnb, child)); } @@ -101,23 +102,23 @@ private IConditionNode ConvertConditionNode(ICon private IConditionNode CreateValueConditionNode(IConditionNodeBuilder conditionNodeBuilder, ConditionNodeDataModel conditionNodeDataModel) { - DataTypes dataType = RulesFromJsonFile.Parse(conditionNodeDataModel.DataType); - TConditionType integrationTestsConditionType = RulesFromJsonFile.Parse(conditionNodeDataModel.ConditionType); - Operators @operator = RulesFromJsonFile.Parse(conditionNodeDataModel.Operator); + var dataType = RulesFromJsonFile.Parse(conditionNodeDataModel.DataType); + var integrationTestsConditionType = RulesFromJsonFile.Parse(conditionNodeDataModel.ConditionType); + var @operator = RulesFromJsonFile.Parse(conditionNodeDataModel.Operator); switch (dataType) { case DataTypes.Integer: return conditionNodeBuilder.AsValued(integrationTestsConditionType) .OfDataType() .WithComparisonOperator(@operator) - .SetOperand(Convert.ToInt32(conditionNodeDataModel.Operand)) + .SetOperand(Convert.ToInt32(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture)) .Build(); case DataTypes.Decimal: return conditionNodeBuilder.AsValued(integrationTestsConditionType) .OfDataType() .WithComparisonOperator(@operator) - .SetOperand(Convert.ToDecimal(conditionNodeDataModel.Operand)) + .SetOperand(Convert.ToDecimal(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture)) .Build(); case DataTypes.String: @@ -131,7 +132,7 @@ private IConditionNode CreateValueConditionNode( return conditionNodeBuilder.AsValued(integrationTestsConditionType) .OfDataType() .WithComparisonOperator(@operator) - .SetOperand(Convert.ToBoolean(conditionNodeDataModel.Operand)) + .SetOperand(Convert.ToBoolean(conditionNodeDataModel.Operand, CultureInfo.InvariantCulture)) .Build(); default: diff --git a/tests/Rules.Framework.IntegrationTests/Tests/Scenario1/BodyMassIndexTests.cs b/tests/Rules.Framework.IntegrationTests/Tests/Scenario1/BodyMassIndexTests.cs index 1a9ade3c..0239c10f 100644 --- a/tests/Rules.Framework.IntegrationTests/Tests/Scenario1/BodyMassIndexTests.cs +++ b/tests/Rules.Framework.IntegrationTests/Tests/Scenario1/BodyMassIndexTests.cs @@ -119,7 +119,7 @@ public async Task AddRule_AddingNewRuleWithAgeConditionAtPriority1AndNewRuleAtPr .Build()) .Build(); - Rule newRule1 = newRuleResult1.Rule; + Rule expectedRule1 = newRuleResult1.Rule; RuleAddPriorityOption ruleAddPriorityOption1 = RuleAddPriorityOption.ByPriorityNumber(1); RuleBuilderResult ruleBuilderResult2 = RuleBuilder.NewRule() @@ -132,12 +132,12 @@ public async Task AddRule_AddingNewRuleWithAgeConditionAtPriority1AndNewRuleAtPr })) .Build(); - Rule newRule2 = ruleBuilderResult2.Rule; + Rule expectedRule2 = ruleBuilderResult2.Rule; RuleAddPriorityOption ruleAddPriorityOption2 = RuleAddPriorityOption.ByPriorityNumber(4); // Act - RuleOperationResult ruleOperationResult1 = await rulesEngine.AddRuleAsync(newRule1, ruleAddPriorityOption1).ConfigureAwait(false); - RuleOperationResult ruleOperationResult2 = await rulesEngine.AddRuleAsync(newRule2, ruleAddPriorityOption2).ConfigureAwait(false); + RuleOperationResult ruleOperationResult1 = await rulesEngine.AddRuleAsync(expectedRule1, ruleAddPriorityOption1).ConfigureAwait(false); + RuleOperationResult ruleOperationResult2 = await rulesEngine.AddRuleAsync(expectedRule2, ruleAddPriorityOption2).ConfigureAwait(false); // Assert ruleOperationResult1.Should().NotBeNull(); @@ -148,9 +148,9 @@ public async Task AddRule_AddingNewRuleWithAgeConditionAtPriority1AndNewRuleAtPr IEnumerable> rules = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()).ConfigureAwait(false); rules.Should().NotBeNull().And.HaveCount(3); - rules.Should().ContainEquivalentOf(newRule1); - newRule1.Priority.Should().Be(1, "rule should to priority 1 if inserted at priority 1"); - newRule2.Priority.Should().Be(3, "rule should have priority 3 if inserted at priority 3, given that last rule after insert was at priority 2."); + rules.Should().ContainEquivalentOf(expectedRule1); + expectedRule1.Priority.Should().Be(1, "rule should to priority 1 if inserted at priority 1"); + expectedRule2.Priority.Should().Be(3, "rule should have priority 3 if inserted at priority 3, given that last rule after insert was at priority 2."); } [Theory] @@ -188,14 +188,14 @@ public async Task AddRule_AddingNewRuleWithAgeConditionOnTop_NewRuleIsInsertedAn .Build()) .Build(); - Rule newRule = newRuleResult.Rule; + Rule expectedRule = newRuleResult.Rule; RuleAddPriorityOption ruleAddPriorityOption = new RuleAddPriorityOption { PriorityOption = PriorityOptions.AtTop }; // Act - RuleOperationResult ruleOperationResult = await rulesEngine.AddRuleAsync(newRule, ruleAddPriorityOption).ConfigureAwait(false); + RuleOperationResult ruleOperationResult = await rulesEngine.AddRuleAsync(expectedRule, ruleAddPriorityOption).ConfigureAwait(false); // Assert ruleOperationResult.Should().NotBeNull(); @@ -203,8 +203,8 @@ public async Task AddRule_AddingNewRuleWithAgeConditionOnTop_NewRuleIsInsertedAn IEnumerable> rules = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()).ConfigureAwait(false); rules.Should().NotBeNull().And.HaveCount(2); - rules.Should().ContainEquivalentOf(newRule); - newRule.Priority.Should().Be(1, "rule should to priority 1 if inserted at top."); + rules.Should().ContainEquivalentOf(expectedRule, o => o.Excluding(x => x.RootCondition.Properties)); + expectedRule.Priority.Should().Be(1, "rule should to priority 1 if inserted at top."); } [Theory] diff --git a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Rules.Framework.Providers.InMemory.IntegrationTests.csproj b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Rules.Framework.Providers.InMemory.IntegrationTests.csproj index 59a795a1..07f572a8 100644 --- a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Rules.Framework.Providers.InMemory.IntegrationTests.csproj +++ b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Rules.Framework.Providers.InMemory.IntegrationTests.csproj @@ -7,15 +7,15 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/tests/Rules.Framework.Providers.InMemory.Tests/Rules.Framework.Providers.InMemory.Tests.csproj b/tests/Rules.Framework.Providers.InMemory.Tests/Rules.Framework.Providers.InMemory.Tests.csproj index 9d03d986..e77f80dc 100644 --- a/tests/Rules.Framework.Providers.InMemory.Tests/Rules.Framework.Providers.InMemory.Tests.csproj +++ b/tests/Rules.Framework.Providers.InMemory.Tests/Rules.Framework.Providers.InMemory.Tests.csproj @@ -9,17 +9,17 @@ - + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Rules.Framework.Providers.MongoDb.IntegrationTests.csproj b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Rules.Framework.Providers.MongoDb.IntegrationTests.csproj index 7597442a..c0f1631c 100644 --- a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Rules.Framework.Providers.MongoDb.IntegrationTests.csproj +++ b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Rules.Framework.Providers.MongoDb.IntegrationTests.csproj @@ -13,13 +13,13 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/tests/Rules.Framework.Providers.MongoDb.Tests/Rules.Framework.Providers.MongoDb.Tests.csproj b/tests/Rules.Framework.Providers.MongoDb.Tests/Rules.Framework.Providers.MongoDb.Tests.csproj index 3a0c2b32..5ea90126 100644 --- a/tests/Rules.Framework.Providers.MongoDb.Tests/Rules.Framework.Providers.MongoDb.Tests.csproj +++ b/tests/Rules.Framework.Providers.MongoDb.Tests/Rules.Framework.Providers.MongoDb.Tests.csproj @@ -9,14 +9,14 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + all diff --git a/tests/Rules.Framework.Tests/Core/ConditionNodePropertiesTests.cs b/tests/Rules.Framework.Tests/Core/ConditionNodePropertiesTests.cs deleted file mode 100644 index 44c0dab2..00000000 --- a/tests/Rules.Framework.Tests/Core/ConditionNodePropertiesTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace Rules.Framework.Tests.Core -{ - using FluentAssertions; - using Rules.Framework.Core; - using System; - using Xunit; - - public class ConditionNodePropertiesTests - { - [Fact] - public void GetCompiledDelegateKey_GivenMultiplicityString_ReturnsString() - { - // Arrange - string multiplicity = "test"; - string expected = "_compilation_compiled_test"; - - // Act - string actual = ConditionNodeProperties.GetCompiledDelegateKey(multiplicity); - - // Assert - actual.Should().Be(expected); - } - - [Fact] - public void GetCompiledDelegateKey_GivenEmptyMultiplicityString_ReturnsString() - { - // Arrange - string multiplicity = ""; - - // Act - ArgumentException argumentException = Assert.Throws(() => ConditionNodeProperties.GetCompiledDelegateKey(multiplicity)); - - // Assert - argumentException.Should().NotBeNull(); - argumentException.ParamName.Should().Be("multiplicity"); - } - } -} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilderTests.cs index e4504056..5b735cf0 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilderTests.cs @@ -1,72 +1,88 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders { + using System; + using System.Linq.Expressions; using FluentAssertions; + using Moq; using Rules.Framework.Core; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Linq.Expressions; - using System.Reflection; - using System.Runtime.CompilerServices; - using System.Text; - using System.Threading.Tasks; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class CaseInsensitiveEndsWithOneToOneConditionExpressionBuilderTests { [Fact] - public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() { // Arrange + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null), + LeftHandOperand = Expression.Constant(1), + RightHandOperand = Expression.Constant(2), + }; - ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); - Expression rightHandExpression = Expression.Constant("fox", typeof(string)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); - - CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder caseInsensitiveEndsWithOneToOneConditionExpressionBuilder + var builder = Mock.Of(); + + var caseInsensitiveEndsWithOneToOneConditionExpressionBuilder = new CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder(); // Act - Expression actualExpression = caseInsensitiveEndsWithOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + var notSupportedException = Assert.Throws(() => caseInsensitiveEndsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args)); // Assert - actualExpression.Should().NotBeNull(); - - var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); - var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); - var notNullLeftHandValueResult2 = compiledExpression.Invoke("The quick brown Fox"); - var nullLeftHandValueResult = compiledExpression.Invoke(null); - - notNullLeftHandValueResult1.Should().BeTrue(); - notNullLeftHandValueResult2.Should().BeTrue(); - nullLeftHandValueResult.Should().BeFalse(); + notSupportedException.Should().NotBeNull(); + notSupportedException.Message + .Should() + .Contain(Operators.CaseInsensitiveEndsWith.ToString()) + .And + .Contain(DataTypes.Integer.ToString()); } [Fact] - public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() { // Arrange - Expression leftHandExpression = Expression.Constant(1, typeof(int)); - Expression rightHandExpression = Expression.Constant(2, typeof(int)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null); - - CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder caseInsensitiveEndsWithOneToOneConditionExpressionBuilder + var caseInsensitiveEndsWithOneToOneConditionExpressionBuilder = new CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder(); // Act - NotSupportedException notSupportedException = Assert.Throws(() => caseInsensitiveEndsWithOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + var expressionResult = ExpressionBuilder.NewExpression("TestCondition") + .WithParameters(p => + { + p.CreateParameter("leftHand"); + }) + .HavingReturn(typeof(bool), false) + .SetImplementation(builder => + { + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null), + LeftHandOperand = builder.GetParameter("leftHand"), + RightHandOperand = builder.Constant("fox"), + }; + var conditionExpression = caseInsensitiveEndsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args); + + builder.Return(conditionExpression); + }) + .Build(); // Assert - notSupportedException.Should().NotBeNull(); - notSupportedException.Message - .Should() - .Contain(Operators.CaseInsensitiveEndsWith.ToString()) - .And - .Contain(DataTypes.Integer.ToString()); + var actualExpression = expressionResult.Implementation; + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, expressionResult.Parameters).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); + var notNullLeftHandValueResult2 = compiledExpression.Invoke("The quick brown Fox"); + var nullLeftHandValueResult = compiledExpression.Invoke(null); + + notNullLeftHandValueResult1.Should().BeTrue(); + notNullLeftHandValueResult2.Should().BeTrue(); + nullLeftHandValueResult.Should().BeFalse(); } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilderTests.cs index 1bf81ad4..2c1aeaa9 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilderTests.cs @@ -1,70 +1,88 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders { + using System; + using System.Linq.Expressions; using FluentAssertions; + using Moq; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Linq.Expressions; - using System.Text; - using System.Threading.Tasks; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class CaseInsensitiveStartsWithOneToOneConditionExpressionBuilderTests { [Fact] - public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() { // Arrange + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null), + LeftHandOperand = Expression.Constant(1), + RightHandOperand = Expression.Constant(2), + }; - ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); - Expression rightHandExpression = Expression.Constant("the", typeof(string)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + var builder = Mock.Of(); - CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder caseInsensitiveEndsWithOneToOneConditionExpressionBuilder + var caseInsensitiveStartsWithOneToOneConditionExpressionBuilder = new CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder(); // Act - Expression actualExpression = caseInsensitiveEndsWithOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + var notSupportedException = Assert.Throws(() => caseInsensitiveStartsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args)); // Assert - actualExpression.Should().NotBeNull(); - - var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); - var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); - var notNullLeftHandValueResult2 = compiledExpression.Invoke("the quick brown fox"); - var nullLeftHandValueResult = compiledExpression.Invoke(null); - - notNullLeftHandValueResult1.Should().BeTrue(); - notNullLeftHandValueResult2.Should().BeTrue(); - nullLeftHandValueResult.Should().BeFalse(); + notSupportedException.Should().NotBeNull(); + notSupportedException.Message + .Should() + .Contain(Operators.CaseInsensitiveStartsWith.ToString()) + .And + .Contain(DataTypes.Integer.ToString()); } [Fact] - public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() { // Arrange - Expression leftHandExpression = Expression.Constant(1, typeof(int)); - Expression rightHandExpression = Expression.Constant(2, typeof(int)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null); - - CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder caseInsensitiveEndsWithOneToOneConditionExpressionBuilder + var caseInsensitiveStartsWithOneToOneConditionExpressionBuilder = new CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder(); // Act - NotSupportedException notSupportedException = Assert.Throws(() => caseInsensitiveEndsWithOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + var expressionResult = ExpressionBuilder.NewExpression("TestCondition") + .WithParameters(p => + { + p.CreateParameter("leftHand", typeof(string)); + }) + .HavingReturn(typeof(bool), false) + .SetImplementation(builder => + { + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null), + LeftHandOperand = builder.GetParameter("leftHand"), + RightHandOperand = builder.Constant("the"), + }; + var conditionExpression = caseInsensitiveStartsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args); + + builder.Return(conditionExpression); + }) + .Build(); // Assert - notSupportedException.Should().NotBeNull(); - notSupportedException.Message - .Should() - .Contain(Operators.CaseInsensitiveStartsWith.ToString()) - .And - .Contain(DataTypes.Integer.ToString()); + var actualExpression = expressionResult.Implementation; + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, expressionResult.Parameters).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); + var notNullLeftHandValueResult2 = compiledExpression.Invoke("the quick brown fox"); + var nullLeftHandValueResult = compiledExpression.Invoke(null); + + notNullLeftHandValueResult1.Should().BeTrue(); + notNullLeftHandValueResult2.Should().BeTrue(); + nullLeftHandValueResult.Should().BeFalse(); } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilderTests.cs index 21f30a8c..5f335371 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilderTests.cs @@ -1,69 +1,88 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders { + using System; + using System.Linq.Expressions; using FluentAssertions; + using Moq; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Linq.Expressions; - using System.Text; - using System.Threading.Tasks; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class ContainsOneToOneConditionExpressionBuilderTests { [Fact] - public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() { // Arrange - ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); - Expression rightHandExpression = Expression.Constant("quick", typeof(string)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null), + LeftHandOperand = Expression.Constant(1), + RightHandOperand = Expression.Constant(2), + }; + + var builder = Mock.Of(); - ContainsOneToOneConditionExpressionBuilder containsOneToOneConditionExpressionBuilder + var containsOneToOneConditionExpressionBuilder = new ContainsOneToOneConditionExpressionBuilder(); // Act - Expression actualExpression = containsOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + var notSupportedException = Assert.Throws(() => containsOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args)); // Assert - actualExpression.Should().NotBeNull(); - - var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); - var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); - var notNullLeftHandValueResult2 = compiledExpression.Invoke("The Quick brown fox"); - var nullLeftHandValueResult = compiledExpression.Invoke(null); - - notNullLeftHandValueResult1.Should().BeTrue(); - notNullLeftHandValueResult2.Should().BeFalse(); - nullLeftHandValueResult.Should().BeFalse(); + notSupportedException.Should().NotBeNull(); + notSupportedException.Message + .Should() + .Contain(Operators.Contains.ToString()) + .And + .Contain(DataTypes.Integer.ToString()); } [Fact] - public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() { // Arrange - Expression leftHandExpression = Expression.Constant(1, typeof(int)); - Expression rightHandExpression = Expression.Constant(2, typeof(int)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null); - - ContainsOneToOneConditionExpressionBuilder containsOneToOneConditionExpressionBuilder + var containsOneToOneConditionExpressionBuilder = new ContainsOneToOneConditionExpressionBuilder(); // Act - NotSupportedException notSupportedException = Assert.Throws(() => containsOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + var expressionResult = ExpressionBuilder.NewExpression("TestCondition") + .WithParameters(p => + { + p.CreateParameter("leftHand", typeof(string)); + }) + .HavingReturn(typeof(bool), false) + .SetImplementation(builder => + { + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null), + LeftHandOperand = builder.GetParameter("leftHand"), + RightHandOperand = builder.Constant("quick"), + }; + var conditionExpression = containsOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args); + + builder.Return(conditionExpression); + }) + .Build(); // Assert - notSupportedException.Should().NotBeNull(); - notSupportedException.Message - .Should() - .Contain(Operators.Contains.ToString()) - .And - .Contain(DataTypes.Integer.ToString()); + var actualExpression = expressionResult.Implementation; + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, expressionResult.Parameters).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); + var notNullLeftHandValueResult2 = compiledExpression.Invoke("The Quick brown fox"); + var nullLeftHandValueResult = compiledExpression.Invoke(null); + + notNullLeftHandValueResult1.Should().BeTrue(); + notNullLeftHandValueResult2.Should().BeFalse(); + nullLeftHandValueResult.Should().BeFalse(); } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilderTests.cs index a5a81bfe..9c989bd9 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilderTests.cs @@ -1,66 +1,88 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders { + using System; + using System.Linq.Expressions; using FluentAssertions; + using Moq; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation; - using System; - using System.Linq.Expressions; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class EndsWithOneToOneConditionExpressionBuilderTests { [Fact] - public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() { // Arrange + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null), + LeftHandOperand = Expression.Constant(1), + RightHandOperand = Expression.Constant(2), + }; - ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); - Expression rightHandExpression = Expression.Constant("fox", typeof(string)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + var builder = Mock.Of(); - EndsWithOneToOneConditionExpressionBuilder endsWithOneToOneConditionExpressionBuilder + var endsWithOneToOneConditionExpressionBuilder = new EndsWithOneToOneConditionExpressionBuilder(); // Act - Expression actualExpression = endsWithOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + var notSupportedException = Assert.Throws(() => endsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args)); // Assert - actualExpression.Should().NotBeNull(); - - var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); - var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); - var notNullLeftHandValueResult2 = compiledExpression.Invoke("The quick brown Fox"); - var nullLeftHandValueResult = compiledExpression.Invoke(null); - - notNullLeftHandValueResult1.Should().BeTrue(); - notNullLeftHandValueResult2.Should().BeFalse(); - nullLeftHandValueResult.Should().BeFalse(); + notSupportedException.Should().NotBeNull(); + notSupportedException.Message + .Should() + .Contain(Operators.EndsWith.ToString()) + .And + .Contain(DataTypes.Integer.ToString()); } [Fact] - public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() { // Arrange - Expression leftHandExpression = Expression.Constant(1, typeof(int)); - Expression rightHandExpression = Expression.Constant(2, typeof(int)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null); - - EndsWithOneToOneConditionExpressionBuilder endsWithOneToOneConditionExpressionBuilder + var endsWithOneToOneConditionExpressionBuilder = new EndsWithOneToOneConditionExpressionBuilder(); // Act - NotSupportedException notSupportedException = Assert.Throws(() => endsWithOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + var expressionResult = ExpressionBuilder.NewExpression("TestCondition") + .WithParameters(p => + { + p.CreateParameter("leftHand", typeof(string)); + }) + .HavingReturn(typeof(bool), false) + .SetImplementation(builder => + { + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null), + LeftHandOperand = builder.GetParameter("leftHand"), + RightHandOperand = builder.Constant("fox"), + }; + var conditionExpression = endsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args); + + builder.Return(conditionExpression); + }) + .Build(); // Assert - notSupportedException.Should().NotBeNull(); - notSupportedException.Message - .Should() - .Contain(Operators.EndsWith.ToString()) - .And - .Contain(DataTypes.Integer.ToString()); + var actualExpression = expressionResult.Implementation; + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, expressionResult.Parameters).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); + var notNullLeftHandValueResult2 = compiledExpression.Invoke("The quick brown Fox"); + var nullLeftHandValueResult = compiledExpression.Invoke(null); + + notNullLeftHandValueResult1.Should().BeTrue(); + notNullLeftHandValueResult2.Should().BeFalse(); + nullLeftHandValueResult.Should().BeFalse(); } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilderTests.cs index d761812f..3ceff59b 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilderTests.cs @@ -1,12 +1,13 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders { - using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; - using Rules.Framework.Evaluation; using System; using System.Linq.Expressions; - using Xunit; using FluentAssertions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Xunit; public class EqualOneToOneConditionExpressionBuilderTests { @@ -14,22 +15,36 @@ public class EqualOneToOneConditionExpressionBuilderTests public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() { // Arrange - - ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); - Expression rightHandExpression = Expression.Constant("The quick brown fox", typeof(string)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); - - EqualOneToOneConditionExpressionBuilder equalOneToOneConditionExpressionBuilder + var equalOneToOneConditionExpressionBuilder = new EqualOneToOneConditionExpressionBuilder(); // Act - Expression actualExpression = equalOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + var expressionResult = ExpressionBuilder.NewExpression("TestCondition") + .WithParameters(p => + { + p.CreateParameter("leftHand", typeof(string)); + }) + .HavingReturn(typeof(bool), false) + .SetImplementation(builder => + { + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null), + LeftHandOperand = builder.GetParameter("leftHand"), + RightHandOperand = builder.Constant("The quick brown fox"), + }; + var conditionExpression = equalOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args); + + builder.Return(conditionExpression); + }) + .Build(); // Assert + var actualExpression = expressionResult.Implementation; actualExpression.Should().NotBeNull(); - var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var compiledExpression = Expression.Lambda>(actualExpression, expressionResult.Parameters).Compile(true); var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); var notNullLeftHandValueResult2 = compiledExpression.Invoke("The quick brown Fox"); var nullLeftHandValueResult = compiledExpression.Invoke(null); @@ -39,4 +54,4 @@ EqualOneToOneConditionExpressionBuilder equalOneToOneConditionExpressionBuilder nullLeftHandValueResult.Should().BeFalse(); } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilderTests.cs index 3e095796..92ea854e 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilderTests.cs @@ -1,16 +1,15 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders { - using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; - using Rules.Framework.Evaluation; using System; - using System.Collections.Generic; - using System.Linq; using System.Linq.Expressions; - using System.Text; - using System.Threading.Tasks; - using Xunit; using FluentAssertions; + using Moq; + using Rules.Framework.Core; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Xunit; public class GreaterThanOneToOneConditionExpressionBuilderTests { @@ -18,21 +17,36 @@ public class GreaterThanOneToOneConditionExpressionBuilderTests public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() { // Arrange - ParameterExpression leftHandExpression = Expression.Parameter(typeof(int), "leftHand"); - Expression rightHandExpression = Expression.Constant(1, typeof(int)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0); - - GreaterThanOneToOneConditionExpressionBuilder greaterThanOneToOneConditionExpressionBuilder + var greaterThanOneToOneConditionExpressionBuilder = new GreaterThanOneToOneConditionExpressionBuilder(); // Act - Expression actualExpression = greaterThanOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + var expressionResult = ExpressionBuilder.NewExpression("TestCondition") + .WithParameters(p => + { + p.CreateParameter("leftHand", typeof(int)); + }) + .HavingReturn(typeof(bool), false) + .SetImplementation(builder => + { + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0), + LeftHandOperand = builder.GetParameter("leftHand"), + RightHandOperand = builder.Constant(1), + }; + var conditionExpression = greaterThanOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args); + + builder.Return(conditionExpression); + }) + .Build(); // Assert + var actualExpression = expressionResult.Implementation; actualExpression.Should().NotBeNull(); - var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var compiledExpression = Expression.Lambda>(actualExpression, expressionResult.Parameters).Compile(true); var notNullLeftHandValueResult1 = compiledExpression.Invoke(2); var notNullLeftHandValueResult2 = compiledExpression.Invoke(0); @@ -44,16 +58,21 @@ GreaterThanOneToOneConditionExpressionBuilder greaterThanOneToOneConditionExpres public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ThrowsNotSupportedException() { // Arrange - ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); - Expression rightHandExpression = Expression.Constant("test", typeof(string)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null), + LeftHandOperand = Expression.Parameter(typeof(string), "leftHand"), + RightHandOperand = Expression.Constant("test", typeof(string)), + }; + + var builder = Mock.Of(); - GreaterThanOneToOneConditionExpressionBuilder greaterThanOneToOneConditionExpressionBuilder + var greaterThanOneToOneConditionExpressionBuilder = new GreaterThanOneToOneConditionExpressionBuilder(); // Act - NotSupportedException notSupportedException = Assert.Throws(() => greaterThanOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + var notSupportedException = Assert.Throws(() => greaterThanOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args)); // Assert notSupportedException.Should().NotBeNull(); @@ -63,4 +82,4 @@ GreaterThanOneToOneConditionExpressionBuilder greaterThanOneToOneConditionExpres .Contain(Operators.GreaterThan.ToString()); } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilderTests.cs index 43b95260..75146744 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilderTests.cs @@ -1,15 +1,14 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders { + using System; + using System.Linq.Expressions; using FluentAssertions; + using Moq; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Linq.Expressions; - using System.Text; - using System.Threading.Tasks; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class GreaterThanOrEqualOneToOneConditionExpressionBuilderTests @@ -18,21 +17,36 @@ public class GreaterThanOrEqualOneToOneConditionExpressionBuilderTests public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() { // Arrange - ParameterExpression leftHandExpression = Expression.Parameter(typeof(int), "leftHand"); - Expression rightHandExpression = Expression.Constant(1, typeof(int)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0); - - GreaterThanOrEqualOneToOneConditionExpressionBuilder greaterThanOrEqualOneToOneConditionExpressionBuilder + var greaterThanOrEqualOneToOneConditionExpressionBuilder = new GreaterThanOrEqualOneToOneConditionExpressionBuilder(); // Act - Expression actualExpression = greaterThanOrEqualOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + var expressionResult = ExpressionBuilder.NewExpression("TestCondition") + .WithParameters(p => + { + p.CreateParameter("leftHand", typeof(int)); + }) + .HavingReturn(typeof(bool), false) + .SetImplementation(builder => + { + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0), + LeftHandOperand = builder.GetParameter("leftHand"), + RightHandOperand = builder.Constant(1), + }; + var conditionExpression = greaterThanOrEqualOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args); + + builder.Return(conditionExpression); + }) + .Build(); // Assert + var actualExpression = expressionResult.Implementation; actualExpression.Should().NotBeNull(); - var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var compiledExpression = Expression.Lambda>(actualExpression, expressionResult.Parameters).Compile(true); var notNullLeftHandValueResult1 = compiledExpression.Invoke(2); var notNullLeftHandValueResult2 = compiledExpression.Invoke(0); @@ -44,16 +58,21 @@ GreaterThanOrEqualOneToOneConditionExpressionBuilder greaterThanOrEqualOneToOneC public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ThrowsNotSupportedException() { // Arrange - ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); - Expression rightHandExpression = Expression.Constant("test", typeof(string)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null), + LeftHandOperand = Expression.Parameter(typeof(string), "leftHand"), + RightHandOperand = Expression.Constant("test", typeof(string)), + }; + + var builder = Mock.Of(); - GreaterThanOrEqualOneToOneConditionExpressionBuilder greaterThanOrEqualOneToOneConditionExpressionBuilder + var greaterThanOrEqualOneToOneConditionExpressionBuilder = new GreaterThanOrEqualOneToOneConditionExpressionBuilder(); // Act - NotSupportedException notSupportedException = Assert.Throws(() => greaterThanOrEqualOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + var notSupportedException = Assert.Throws(() => greaterThanOrEqualOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args)); // Assert notSupportedException.Should().NotBeNull(); @@ -63,4 +82,4 @@ GreaterThanOrEqualOneToOneConditionExpressionBuilder greaterThanOrEqualOneToOneC .Contain(Operators.GreaterThan.ToString()); } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilderTests.cs index 158bc791..e6b83ce2 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilderTests.cs @@ -1,15 +1,13 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders { - using FluentAssertions; - using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; - using Rules.Framework.Evaluation; using System; using System.Collections.Generic; - using System.Linq; using System.Linq.Expressions; - using System.Text; - using System.Threading.Tasks; + using FluentAssertions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; using Xunit; public class InOneToManyConditionExpressionBuilderTests @@ -18,21 +16,36 @@ public class InOneToManyConditionExpressionBuilderTests public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() { // Arrange - ParameterExpression leftHandExpression = Expression.Parameter(typeof(int), "leftHand"); - Expression rightHandExpression = Expression.Constant(new int[] { 1, 2, 3 }, typeof(IEnumerable)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0); - - InOneToManyConditionExpressionBuilder inOneToManyConditionExpressionBuilder + var inOneToManyConditionExpressionBuilder = new InOneToManyConditionExpressionBuilder(); // Act - Expression actualExpression = inOneToManyConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + var expressionResult = ExpressionBuilder.NewExpression("TestCondition") + .WithParameters(p => + { + p.CreateParameter("leftHand", typeof(int)); + }) + .HavingReturn(typeof(bool), false) + .SetImplementation(builder => + { + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0), + LeftHandOperand = builder.GetParameter("leftHand"), + RightHandOperand = builder.Constant>(new int[] { 1, 2, 3 }), + }; + var conditionExpression = inOneToManyConditionExpressionBuilder + .BuildConditionExpression(builder, args); + + builder.Return(conditionExpression); + }) + .Build(); // Assert + var actualExpression = expressionResult.Implementation; actualExpression.Should().NotBeNull(); - var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var compiledExpression = Expression.Lambda>(actualExpression, expressionResult.Parameters).Compile(true); var notNullLeftHandValueResult1 = compiledExpression.Invoke(2); var notNullLeftHandValueResult2 = compiledExpression.Invoke(0); @@ -40,4 +53,4 @@ InOneToManyConditionExpressionBuilder inOneToManyConditionExpressionBuilder notNullLeftHandValueResult2.Should().BeFalse(); } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilderTests.cs index 05a67683..8f94f50a 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilderTests.cs @@ -1,15 +1,14 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders { + using System; + using System.Linq.Expressions; using FluentAssertions; + using Moq; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Linq.Expressions; - using System.Text; - using System.Threading.Tasks; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class LesserThanOneToOneConditionExpressionBuilderTests @@ -18,21 +17,36 @@ public class LesserThanOneToOneConditionExpressionBuilderTests public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() { // Arrange - ParameterExpression leftHandExpression = Expression.Parameter(typeof(int), "leftHand"); - Expression rightHandExpression = Expression.Constant(1, typeof(int)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0); - - LesserThanOneToOneConditionExpressionBuilder lesserThanOneToOneConditionExpressionBuilder + var lesserThanOneToOneConditionExpressionBuilder = new LesserThanOneToOneConditionExpressionBuilder(); // Act - Expression actualExpression = lesserThanOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + var expressionResult = ExpressionBuilder.NewExpression("TestCondition") + .WithParameters(p => + { + p.CreateParameter("leftHand", typeof(int)); + }) + .HavingReturn(typeof(bool), false) + .SetImplementation(builder => + { + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0), + LeftHandOperand = builder.GetParameter("leftHand"), + RightHandOperand = builder.Constant(1), + }; + var conditionExpression = lesserThanOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args); + + builder.Return(conditionExpression); + }) + .Build(); // Assert + var actualExpression = expressionResult.Implementation; actualExpression.Should().NotBeNull(); - var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var compiledExpression = Expression.Lambda>(actualExpression, expressionResult.Parameters).Compile(true); var notNullLeftHandValueResult1 = compiledExpression.Invoke(2); var notNullLeftHandValueResult2 = compiledExpression.Invoke(0); @@ -44,16 +58,21 @@ LesserThanOneToOneConditionExpressionBuilder lesserThanOneToOneConditionExpressi public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ThrowsNotSupportedException() { // Arrange - ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); - Expression rightHandExpression = Expression.Constant("test", typeof(string)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null), + LeftHandOperand = Expression.Parameter(typeof(string), "leftHand"), + RightHandOperand = Expression.Constant("test", typeof(string)), + }; + + var builder = Mock.Of(); - LesserThanOneToOneConditionExpressionBuilder lesserThanOneToOneConditionExpressionBuilder + var lesserThanOneToOneConditionExpressionBuilder = new LesserThanOneToOneConditionExpressionBuilder(); // Act - NotSupportedException notSupportedException = Assert.Throws(() => lesserThanOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + var notSupportedException = Assert.Throws(() => lesserThanOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args)); // Assert notSupportedException.Should().NotBeNull(); @@ -63,4 +82,4 @@ LesserThanOneToOneConditionExpressionBuilder lesserThanOneToOneConditionExpressi .Contain(Operators.LesserThan.ToString()); } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilderTests.cs index 9286ea7b..1113b7e1 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilderTests.cs @@ -1,15 +1,14 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders { + using System; + using System.Linq.Expressions; using FluentAssertions; + using Moq; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Linq.Expressions; - using System.Text; - using System.Threading.Tasks; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class LesserThanOrEqualOneToOneConditionExpressionBuilderTests @@ -18,21 +17,36 @@ public class LesserThanOrEqualOneToOneConditionExpressionBuilderTests public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() { // Arrange - ParameterExpression leftHandExpression = Expression.Parameter(typeof(int), "leftHand"); - Expression rightHandExpression = Expression.Constant(1, typeof(int)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0); - - LesserThanOrEqualOneToOneConditionExpressionBuilder lesserThanOrEqualOneToOneConditionExpressionBuilder + var lesserThanOrEqualOneToOneConditionExpressionBuilder = new LesserThanOrEqualOneToOneConditionExpressionBuilder(); // Act - Expression actualExpression = lesserThanOrEqualOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + var expressionResult = ExpressionBuilder.NewExpression("TestCondition") + .WithParameters(p => + { + p.CreateParameter("leftHand", typeof(int)); + }) + .HavingReturn(typeof(bool), false) + .SetImplementation(builder => + { + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0), + LeftHandOperand = builder.GetParameter("leftHand"), + RightHandOperand = builder.Constant(1), + }; + var conditionExpression = lesserThanOrEqualOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args); + + builder.Return(conditionExpression); + }) + .Build(); // Assert + var actualExpression = expressionResult.Implementation; actualExpression.Should().NotBeNull(); - var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var compiledExpression = Expression.Lambda>(actualExpression, expressionResult.Parameters).Compile(true); var notNullLeftHandValueResult1 = compiledExpression.Invoke(2); var notNullLeftHandValueResult2 = compiledExpression.Invoke(0); @@ -44,16 +58,21 @@ LesserThanOrEqualOneToOneConditionExpressionBuilder lesserThanOrEqualOneToOneCon public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ThrowsNotSupportedException() { // Arrange - ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); - Expression rightHandExpression = Expression.Constant("test", typeof(string)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null), + LeftHandOperand = Expression.Parameter(typeof(string), "leftHand"), + RightHandOperand = Expression.Constant("test", typeof(string)), + }; + + var builder = Mock.Of(); - LesserThanOrEqualOneToOneConditionExpressionBuilder lesserThanOrEqualOneToOneConditionExpressionBuilder + var lesserThanOrEqualOneToOneConditionExpressionBuilder = new LesserThanOrEqualOneToOneConditionExpressionBuilder(); // Act - NotSupportedException notSupportedException = Assert.Throws(() => lesserThanOrEqualOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + var notSupportedException = Assert.Throws(() => lesserThanOrEqualOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args)); // Assert notSupportedException.Should().NotBeNull(); @@ -63,4 +82,4 @@ LesserThanOrEqualOneToOneConditionExpressionBuilder lesserThanOrEqualOneToOneCon .Contain(Operators.LesserThanOrEqual.ToString()); } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilderTests.cs index 32e1e4bf..a9b123d2 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilderTests.cs @@ -1,69 +1,88 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders { + using System; + using System.Linq.Expressions; using FluentAssertions; + using Moq; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Linq.Expressions; - using System.Text; - using System.Threading.Tasks; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class NotContainsOneToOneConditionExpressionBuilderTests { [Fact] - public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() { // Arrange - ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); - Expression rightHandExpression = Expression.Constant("quick", typeof(string)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null), + LeftHandOperand = Expression.Parameter(typeof(string), "leftHand"), + RightHandOperand = Expression.Constant("test", typeof(string)), + }; + + var builder = Mock.Of(); - NotContainsOneToOneConditionExpressionBuilder containsOneToOneConditionExpressionBuilder + var containsOneToOneConditionExpressionBuilder = new NotContainsOneToOneConditionExpressionBuilder(); // Act - Expression actualExpression = containsOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + var notSupportedException = Assert.Throws(() => containsOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args)); // Assert - actualExpression.Should().NotBeNull(); - - var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); - var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); - var notNullLeftHandValueResult2 = compiledExpression.Invoke("The Quick brown fox"); - var nullLeftHandValueResult = compiledExpression.Invoke(null); - - notNullLeftHandValueResult1.Should().BeFalse(); - notNullLeftHandValueResult2.Should().BeTrue(); - nullLeftHandValueResult.Should().BeFalse(); + notSupportedException.Should().NotBeNull(); + notSupportedException.Message + .Should() + .Contain(Operators.NotContains.ToString()) + .And + .Contain(DataTypes.Integer.ToString()); } [Fact] - public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() { // Arrange - Expression leftHandExpression = Expression.Constant(1, typeof(int)); - Expression rightHandExpression = Expression.Constant(2, typeof(int)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null); - - NotContainsOneToOneConditionExpressionBuilder containsOneToOneConditionExpressionBuilder + var containsOneToOneConditionExpressionBuilder = new NotContainsOneToOneConditionExpressionBuilder(); // Act - NotSupportedException notSupportedException = Assert.Throws(() => containsOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + var expressionResult = ExpressionBuilder.NewExpression("TestCondition") + .WithParameters(p => + { + p.CreateParameter("leftHand", typeof(string)); + }) + .HavingReturn(typeof(bool), false) + .SetImplementation(builder => + { + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null), + LeftHandOperand = builder.GetParameter("leftHand"), + RightHandOperand = builder.Constant("quick"), + }; + var conditionExpression = containsOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args); + + builder.Return(conditionExpression); + }) + .Build(); // Assert - notSupportedException.Should().NotBeNull(); - notSupportedException.Message - .Should() - .Contain(Operators.NotContains.ToString()) - .And - .Contain(DataTypes.Integer.ToString()); + var actualExpression = expressionResult.Implementation; + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, expressionResult.Parameters).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); + var notNullLeftHandValueResult2 = compiledExpression.Invoke("The Quick brown fox"); + var nullLeftHandValueResult = compiledExpression.Invoke(null); + + notNullLeftHandValueResult1.Should().BeFalse(); + notNullLeftHandValueResult2.Should().BeTrue(); + nullLeftHandValueResult.Should().BeFalse(); } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilderTests.cs index 54b9a992..c9e624f7 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilderTests.cs @@ -1,66 +1,88 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders { + using System; + using System.Linq.Expressions; using FluentAssertions; + using Moq; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation; - using System; - using System.Linq.Expressions; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class NotEndsWithOneToOneConditionExpressionBuilderTests { [Fact] - public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() { // Arrange + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null), + LeftHandOperand = Expression.Parameter(typeof(int), "leftHand"), + RightHandOperand = Expression.Constant(2, typeof(int)), + }; - ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); - Expression rightHandExpression = Expression.Constant("fox", typeof(string)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + var builder = Mock.Of(); - NotEndsWithOneToOneConditionExpressionBuilder notEndsWithOneToOneConditionExpressionBuilder + var notEndsWithOneToOneConditionExpressionBuilder = new NotEndsWithOneToOneConditionExpressionBuilder(); // Act - Expression actualExpression = notEndsWithOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + var notSupportedException = Assert.Throws(() => notEndsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args)); // Assert - actualExpression.Should().NotBeNull(); - - var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); - var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); - var notNullLeftHandValueResult2 = compiledExpression.Invoke("The quick brown Fox"); - var nullLeftHandValueResult = compiledExpression.Invoke(null); - - notNullLeftHandValueResult1.Should().BeFalse(); - notNullLeftHandValueResult2.Should().BeTrue(); - nullLeftHandValueResult.Should().BeFalse(); + notSupportedException.Should().NotBeNull(); + notSupportedException.Message + .Should() + .Contain(Operators.NotEndsWith.ToString()) + .And + .Contain(DataTypes.Integer.ToString()); } [Fact] - public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() { // Arrange - Expression leftHandExpression = Expression.Constant(1, typeof(int)); - Expression rightHandExpression = Expression.Constant(2, typeof(int)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null); - - NotEndsWithOneToOneConditionExpressionBuilder notEndsWithOneToOneConditionExpressionBuilder + var notEndsWithOneToOneConditionExpressionBuilder = new NotEndsWithOneToOneConditionExpressionBuilder(); // Act - NotSupportedException notSupportedException = Assert.Throws(() => notEndsWithOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + var expressionResult = ExpressionBuilder.NewExpression("TestCondition") + .WithParameters(p => + { + p.CreateParameter("leftHand", typeof(string)); + }) + .HavingReturn(typeof(bool), false) + .SetImplementation(builder => + { + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null), + LeftHandOperand = builder.GetParameter("leftHand"), + RightHandOperand = builder.Constant("fox"), + }; + var conditionExpression = notEndsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args); + + builder.Return(conditionExpression); + }) + .Build(); // Assert - notSupportedException.Should().NotBeNull(); - notSupportedException.Message - .Should() - .Contain(Operators.NotEndsWith.ToString()) - .And - .Contain(DataTypes.Integer.ToString()); + var actualExpression = expressionResult.Implementation; + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, expressionResult.Parameters).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); + var notNullLeftHandValueResult2 = compiledExpression.Invoke("The quick brown Fox"); + var nullLeftHandValueResult = compiledExpression.Invoke(null); + + notNullLeftHandValueResult1.Should().BeFalse(); + notNullLeftHandValueResult2.Should().BeTrue(); + nullLeftHandValueResult.Should().BeFalse(); } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilderTests.cs index bd39a813..cf0849fe 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilderTests.cs @@ -1,12 +1,13 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders { - using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; - using Rules.Framework.Evaluation; using System; using System.Linq.Expressions; - using Xunit; using FluentAssertions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Xunit; public class NotEqualOneToOneConditionExpressionBuilderTests { @@ -14,22 +15,36 @@ public class NotEqualOneToOneConditionExpressionBuilderTests public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() { // Arrange - - ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); - Expression rightHandExpression = Expression.Constant("The quick brown fox", typeof(string)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); - - NotEqualOneToOneConditionExpressionBuilder notEqualOneToOneConditionExpressionBuilder + var notEqualOneToOneConditionExpressionBuilder = new NotEqualOneToOneConditionExpressionBuilder(); // Act - Expression actualExpression = notEqualOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + var expressionResult = ExpressionBuilder.NewExpression("TestCondition") + .WithParameters(p => + { + p.CreateParameter("leftHand", typeof(string)); + }) + .HavingReturn(typeof(bool), false) + .SetImplementation(builder => + { + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null), + LeftHandOperand = builder.GetParameter("leftHand"), + RightHandOperand = builder.Constant("The quick brown fox"), + }; + var conditionExpression = notEqualOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args); + + builder.Return(conditionExpression); + }) + .Build(); // Assert + var actualExpression = expressionResult.Implementation; actualExpression.Should().NotBeNull(); - var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); + var compiledExpression = Expression.Lambda>(actualExpression, expressionResult.Parameters).Compile(true); var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); var notNullLeftHandValueResult2 = compiledExpression.Invoke("The quick brown Fox"); var nullLeftHandValueResult = compiledExpression.Invoke(null); @@ -39,4 +54,4 @@ NotEqualOneToOneConditionExpressionBuilder notEqualOneToOneConditionExpressionBu nullLeftHandValueResult.Should().BeTrue(); } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilderTests.cs index 6565cba0..5cd62d69 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilderTests.cs @@ -1,70 +1,88 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders { + using System; + using System.Linq.Expressions; using FluentAssertions; + using Moq; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Linq.Expressions; - using System.Text; - using System.Threading.Tasks; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class NotStartsWithOneToOneConditionExpressionBuilderTests { [Fact] - public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() { // Arrange + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null), + LeftHandOperand = Expression.Parameter(typeof(int), "leftHand"), + RightHandOperand = Expression.Constant(2, typeof(int)), + }; - ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); - Expression rightHandExpression = Expression.Constant("The", typeof(string)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + var builder = Mock.Of(); - NotStartsWithOneToOneConditionExpressionBuilder notStartsWithOneToOneConditionExpressionBuilder + var notStartsWithOneToOneConditionExpressionBuilder = new NotStartsWithOneToOneConditionExpressionBuilder(); // Act - Expression actualExpression = notStartsWithOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + var notSupportedException = Assert.Throws(() => notStartsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args)); // Assert - actualExpression.Should().NotBeNull(); - - var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); - var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); - var notNullLeftHandValueResult2 = compiledExpression.Invoke("the quick brown fox"); - var nullLeftHandValueResult = compiledExpression.Invoke(null); - - notNullLeftHandValueResult1.Should().BeFalse(); - notNullLeftHandValueResult2.Should().BeTrue(); - nullLeftHandValueResult.Should().BeFalse(); + notSupportedException.Should().NotBeNull(); + notSupportedException.Message + .Should() + .Contain(Operators.NotStartsWith.ToString()) + .And + .Contain(DataTypes.Integer.ToString()); } [Fact] - public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() { // Arrange - Expression leftHandExpression = Expression.Constant(1, typeof(int)); - Expression rightHandExpression = Expression.Constant(2, typeof(int)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null); - - NotStartsWithOneToOneConditionExpressionBuilder notStartsWithOneToOneConditionExpressionBuilder + var notStartsWithOneToOneConditionExpressionBuilder = new NotStartsWithOneToOneConditionExpressionBuilder(); // Act - NotSupportedException notSupportedException = Assert.Throws(() => notStartsWithOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + var expressionResult = ExpressionBuilder.NewExpression("TestCondition") + .WithParameters(p => + { + p.CreateParameter("leftHand", typeof(string)); + }) + .HavingReturn(typeof(bool), false) + .SetImplementation(builder => + { + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null), + LeftHandOperand = builder.GetParameter("leftHand"), + RightHandOperand = builder.Constant("The"), + }; + var conditionExpression = notStartsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args); + + builder.Return(conditionExpression); + }) + .Build(); // Assert - notSupportedException.Should().NotBeNull(); - notSupportedException.Message - .Should() - .Contain(Operators.NotStartsWith.ToString()) - .And - .Contain(DataTypes.Integer.ToString()); + var actualExpression = expressionResult.Implementation; + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, expressionResult.Parameters).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); + var notNullLeftHandValueResult2 = compiledExpression.Invoke("the quick brown fox"); + var nullLeftHandValueResult = compiledExpression.Invoke(null); + + notNullLeftHandValueResult1.Should().BeFalse(); + notNullLeftHandValueResult2.Should().BeTrue(); + nullLeftHandValueResult.Should().BeFalse(); } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilderTests.cs index f1d8a290..7cd0929f 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilderTests.cs @@ -1,70 +1,93 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders { + using System; + using System.Linq.Expressions; using FluentAssertions; + using Moq; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Linq.Expressions; - using System.Text; - using System.Threading.Tasks; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class StartsWithOneToOneConditionExpressionBuilderTests { [Fact] - public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() { // Arrange + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null), + LeftHandOperand = Expression.Parameter(typeof(int), "leftHand"), + RightHandOperand = Expression.Constant(2, typeof(int)), + }; - ParameterExpression leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); - Expression rightHandExpression = Expression.Constant("The", typeof(string)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + var builder = Mock.Of(); - StartsWithOneToOneConditionExpressionBuilder startsWithOneToOneConditionExpressionBuilder + var endsWithOneToOneConditionExpressionBuilder = new StartsWithOneToOneConditionExpressionBuilder(); // Act - Expression actualExpression = startsWithOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration); + var notSupportedException = Assert.Throws(() => endsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args)); // Assert - actualExpression.Should().NotBeNull(); - - var compiledExpression = Expression.Lambda>(actualExpression, leftHandExpression).Compile(true); - var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); - var notNullLeftHandValueResult2 = compiledExpression.Invoke("the quick brown fox"); - var nullLeftHandValueResult = compiledExpression.Invoke(null); - - notNullLeftHandValueResult1.Should().BeTrue(); - notNullLeftHandValueResult2.Should().BeFalse(); - nullLeftHandValueResult.Should().BeFalse(); + notSupportedException.Should().NotBeNull(); + notSupportedException.Message + .Should() + .Contain(Operators.StartsWith.ToString()) + .And + .Contain(DataTypes.Integer.ToString()); } [Fact] - public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() { // Arrange - Expression leftHandExpression = Expression.Constant(1, typeof(int)); - Expression rightHandExpression = Expression.Constant(2, typeof(int)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), null); - StartsWithOneToOneConditionExpressionBuilder endsWithOneToOneConditionExpressionBuilder + var leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); + var rightHandExpression = Expression.Constant("The", typeof(string)); + var dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); + + var startsWithOneToOneConditionExpressionBuilder = new StartsWithOneToOneConditionExpressionBuilder(); // Act - NotSupportedException notSupportedException = Assert.Throws(() => endsWithOneToOneConditionExpressionBuilder - .BuildConditionExpression(leftHandExpression, rightHandExpression, dataTypeConfiguration)); + var expressionResult = ExpressionBuilder.NewExpression("TestCondition") + .WithParameters(p => + { + p.CreateParameter("leftHand", typeof(string)); + }) + .HavingReturn(typeof(bool), false) + .SetImplementation(builder => + { + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null), + LeftHandOperand = builder.GetParameter("leftHand"), + RightHandOperand = builder.Constant("The"), + }; + var conditionExpression = startsWithOneToOneConditionExpressionBuilder + .BuildConditionExpression(builder, args); + + builder.Return(conditionExpression); + }) + .Build(); // Assert - notSupportedException.Should().NotBeNull(); - notSupportedException.Message - .Should() - .Contain(Operators.StartsWith.ToString()) - .And - .Contain(DataTypes.Integer.ToString()); + var actualExpression = expressionResult.Implementation; + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, expressionResult.Parameters).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke("The quick brown fox"); + var notNullLeftHandValueResult2 = compiledExpression.Invoke("the quick brown fox"); + var nullLeftHandValueResult = compiledExpression.Invoke(null); + + notNullLeftHandValueResult1.Should().BeTrue(); + notNullLeftHandValueResult2.Should().BeFalse(); + nullLeftHandValueResult.Should().BeFalse(); } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionsTreeCompilerTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionsTreeCompilerTests.cs deleted file mode 100644 index 6782d069..00000000 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionsTreeCompilerTests.cs +++ /dev/null @@ -1,83 +0,0 @@ -namespace Rules.Framework.Tests.Evaluation.Compiled -{ - using FluentAssertions; - using Moq; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Evaluation; - using Rules.Framework.Evaluation.Compiled; - using Rules.Framework.Tests.TestStubs; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Linq.Expressions; - using System.Text; - using System.Threading.Tasks; - using Xunit; - - public class ConditionsTreeCompilerTests - { - [Fact] - public void Compile_GivenComposedConditionNodeWith2ChildValueConditionNodes_CompilesConditionAndStoresExpressionAsProperty() - { - // Arrange - ValueConditionNode valueConditionNode1 - = new ValueConditionNode(DataTypes.Integer, ConditionType.NumberOfSales, Operators.Equal, 100); - ValueConditionNode valueConditionNode2 - = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "GB"); - - ComposedConditionNode composedConditionNode - = new ComposedConditionNode(LogicalOperators.And, new[] { valueConditionNode1, valueConditionNode2 }); - Func, bool> expectedCompiledFunc = (c) => true; - - IValueConditionNodeCompiler valueConditionNodeCompiler = Mock.Of(); - Mock.Get(valueConditionNodeCompiler) - .Setup(x => x.Compile(It.IsAny>(), It.IsAny())) - .Returns(expectedCompiledFunc); - - IValueConditionNodeCompilerProvider valueConditionNodeCompilerProvider = Mock.Of(); - Mock.Get(valueConditionNodeCompilerProvider) - .Setup(x => x.GetValueConditionNodeCompiler(Multiplicities.OneToOne)) - .Returns(valueConditionNodeCompiler); - - ConditionsTreeCompiler conditionsTreeCompiler = new ConditionsTreeCompiler(valueConditionNodeCompilerProvider); - - // Act - conditionsTreeCompiler.Compile(composedConditionNode); - - // Assert - valueConditionNode1.Properties.TryGetValue(ConditionNodeProperties.GetCompiledDelegateKey(Multiplicities.OneToOne), out var compiledExpression1); - valueConditionNode2.Properties.TryGetValue(ConditionNodeProperties.GetCompiledDelegateKey(Multiplicities.OneToOne), out var compiledExpression2); - compiledExpression1.Should() - .NotBeNull() - .And - .BeOfType, bool>>() - .And - .BeSameAs(expectedCompiledFunc); - compiledExpression2.Should() - .NotBeNull() - .And - .BeOfType, bool>>() - .And - .BeSameAs(expectedCompiledFunc); - } - - [Fact] - public void Compile_GivenUnknownConditionNode_ThrowsNotSupportedException() - { - // Arrange - StubConditionNode stubConditionNode = new StubConditionNode(); - - IValueConditionNodeCompilerProvider valueConditionNodeCompilerProvider = Mock.Of(); - - ConditionsTreeCompiler conditionsTreeCompiler = new ConditionsTreeCompiler(valueConditionNodeCompilerProvider); - - // Act - NotSupportedException notSupportedException = Assert.Throws(() => conditionsTreeCompiler.Compile(stubConditionNode)); - - // Assert - notSupportedException.Should().NotBeNull(); - notSupportedException.Message.Should().Contain(nameof(StubConditionNode)); - } - } -} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeCompilerTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeCompilerTests.cs deleted file mode 100644 index dd753d22..00000000 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeCompilerTests.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace Rules.Framework.Tests.Evaluation.Compiled -{ - using FluentAssertions; - using Moq; - using Rules.Framework.Core; - using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Evaluation; - using Rules.Framework.Evaluation.Compiled; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; - using Rules.Framework.Tests.TestStubs; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Linq.Expressions; - using System.Text; - using System.Threading.Tasks; - using Xunit; - - public class ManyToManyValueConditionNodeCompilerTests - { - [Fact] - public void Compile_GivenValueConditionNodeAndParameterExpression_ReturnsCompiledLambda() - { - // Arrange - ValueConditionNode valueConditionNode = new ValueConditionNode( - DataTypes.String, - ConditionType.IsoCountryCode, - Operators.Contains, - new[] { "PT", "ES" }); - ParameterExpression parameterExpression = Expression.Parameter(typeof(IDictionary)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); - Expression actualLeftExpression = null; - Expression actualRightExpression = null; - - Expression conditionExpression = Expression.Constant(true, typeof(bool)); // For testing purposes, does not need to stay true to the scenario - IConditionExpressionBuilder conditionExpressionBuilder = Mock.Of(); - Mock.Get(conditionExpressionBuilder) - .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((e1, e2, dtc) => - { - actualLeftExpression = e1; - actualRightExpression = e2; - }) - .Returns(conditionExpression); - IConditionExpressionBuilderProvider conditionExpressionBuilderProvider = Mock.Of(); - Mock.Get(conditionExpressionBuilderProvider) - .Setup(x => x.GetConditionExpressionBuilderFor(Operators.Contains, Multiplicities.ManyToMany)) - .Returns(conditionExpressionBuilder); - IDataTypesConfigurationProvider dataTypesConfigurationProvider = Mock.Of(); - Mock.Get(dataTypesConfigurationProvider) - .Setup(x => x.GetDataTypeConfiguration(DataTypes.String)) - .Returns(dataTypeConfiguration); - - ManyToManyValueConditionNodeCompiler manyToManyValueConditionNodeCompiler = - new ManyToManyValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); - - // Act - var compiledValueConditionNode = manyToManyValueConditionNodeCompiler.Compile(valueConditionNode, parameterExpression); - - // Assert - compiledValueConditionNode.Should().NotBeNull(); - actualLeftExpression.Should().NotBeNull(); - actualLeftExpression.Type.Should().BeAssignableTo>(); - actualRightExpression.Should().NotBeNull(); - actualRightExpression.Type.Should().BeAssignableTo>(); - } - } -} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilderTests.GoldenFile1.csx b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilderTests.GoldenFile1.csx new file mode 100644 index 00000000..01a3cb4c --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilderTests.GoldenFile1.csx @@ -0,0 +1,18 @@ +public bool Main(object leftOperand, object rightOperand) +{ + object coalescedLeftOperand; + + if (leftOperand != null) + { + coalescedLeftOperand = leftOperand; + } + else + { + coalescedLeftOperand = null; + } + IEnumerable convertedLeftOperand = (IEnumerable)coalescedLeftOperand; + IEnumerable convertedRightOperand = (IEnumerable)rightOperand; + bool result = true && true; + return result; + +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilderTests.cs new file mode 100644 index 00000000..15137b25 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilderTests.cs @@ -0,0 +1,105 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq.Expressions; + using System.Reflection; + using DiffPlex.DiffBuilder; + using ExpressionDebugger; + using FluentAssertions; + using Moq; + using Rules.Framework.Core; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Xunit; + + public class ManyToManyValueConditionNodeExpressionBuilderTests + { + [Fact] + public void Build_GivenLeftHandOperatorRightHandAndDataTypeConfiguration_ReturnsExpression() + { + // Arrange + string expectedScript; + using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Rules.Framework.Tests.Evaluation.Compiled.ManyToManyValueConditionNodeExpressionBuilderTests.GoldenFile1.csx")) + using (var streamReader = new StreamReader(stream)) + { + expectedScript = streamReader.ReadToEnd(); + } + Expression actualLeftExpression = null; + Expression actualRightExpression = null; + + var conditionExpression = Expression.AndAlso(Expression.Constant(true, typeof(bool)), Expression.Constant(true, typeof(bool))); // For testing purposes, does not need to stay true to the scenario + var conditionExpressionBuilder = Mock.Of(); + Mock.Get(conditionExpressionBuilder) + .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny())) + .Callback((b, args) => + { + actualLeftExpression = args.LeftHandOperand; + actualRightExpression = args.RightHandOperand; + }) + .Returns(conditionExpression); + var conditionExpressionBuilderProvider = Mock.Of(); + Mock.Get(conditionExpressionBuilderProvider) + .Setup(x => x.GetConditionExpressionBuilderFor(Operators.Contains, Multiplicities.ManyToMany)) + .Returns(conditionExpressionBuilder); + + var manyToManyValueConditionNodeCompiler = + new ManyToManyValueConditionNodeExpressionBuilder(conditionExpressionBuilderProvider); + + // Act + var expressionResult = ExpressionBuilder.NewExpression("TestDummy") + .WithParameters(p => + { + p.CreateParameter("leftOperand", typeof(object)); + p.CreateParameter("rightOperand", typeof(object)); + }) + .HavingReturn(typeof(bool), false) + .SetImplementation(builder => + { + var resultVariableExpression = builder.CreateVariable("result", typeof(bool)); + var args = new BuildValueConditionNodeExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null), + LeftOperandVariableExpression = builder.GetParameter("leftOperand"), + Operator = Operators.Contains, + ResultVariableExpression = resultVariableExpression, + RightOperandVariableExpression = builder.GetParameter("rightOperand"), + }; + + manyToManyValueConditionNodeCompiler.Build(builder, args); + + builder.Return(resultVariableExpression); + }) + .Build(); + + // Assert + expressionResult.Should().NotBeNull(); + expressionResult.Implementation.Should().NotBeNull(); + + // Purely for the effect of comparing the generated lambda expression code with the + // golden file. + var lambdaExpression = Expression.Lambda>(expressionResult.Implementation, expressionResult.Parameters); + var actualScript = lambdaExpression.ToScript(); + var diffResult = SideBySideDiffBuilder.Diff(expectedScript, actualScript, ignoreWhiteSpace: true); + diffResult.NewText.HasDifferences.Should().BeFalse(); + Func compiledLambdaExpression = null; + FluentActions.Invoking(() => compiledLambdaExpression = lambdaExpression.Compile()) + .Should() + .NotThrow("expression should be compilable"); + FluentActions.Invoking(() => compiledLambdaExpression.Invoke(null, new string[] { "dummy" })) + .Should() + .NotThrow("compiled expression should be executable"); + FluentActions.Invoking(() => compiledLambdaExpression.Invoke(new string[] { "abc" }, new string[] { "dummy" })) + .Should() + .NotThrow("compiled expression should be able to deal with null left operand"); + actualLeftExpression.Should().NotBeNull(); + actualLeftExpression.Type.Should().BeAssignableTo>(); + actualRightExpression.Should().NotBeNull(); + actualRightExpression.Type.Should().BeAssignableTo>(); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeCompilerTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeCompilerTests.cs deleted file mode 100644 index 6723ffb8..00000000 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeCompilerTests.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace Rules.Framework.Tests.Evaluation.Compiled -{ - using Moq; - using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; - using Rules.Framework.Evaluation.Compiled; - using Rules.Framework.Evaluation; - using Rules.Framework.Tests.TestStubs; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Linq.Expressions; - using System.Text; - using System.Threading.Tasks; - using Xunit; - using FluentAssertions; - - public class ManyToOneValueConditionNodeCompilerTests - { - [Fact] - public void Compile_GivenValueConditionNodeAndParameterExpression_ReturnsCompiledLambda() - { - // Arrange - ValueConditionNode valueConditionNode = new ValueConditionNode( - DataTypes.String, - ConditionType.IsoCountryCode, - Operators.Contains, - "ES"); - ParameterExpression parameterExpression = Expression.Parameter(typeof(IDictionary)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); - Expression actualLeftExpression = null; - Expression actualRightExpression = null; - - Expression conditionExpression = Expression.Constant(true, typeof(bool)); // For testing purposes, does not need to stay true to the scenario - IConditionExpressionBuilder conditionExpressionBuilder = Mock.Of(); - Mock.Get(conditionExpressionBuilder) - .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((e1, e2, dtc) => - { - actualLeftExpression = e1; - actualRightExpression = e2; - }) - .Returns(conditionExpression); - IConditionExpressionBuilderProvider conditionExpressionBuilderProvider = Mock.Of(); - Mock.Get(conditionExpressionBuilderProvider) - .Setup(x => x.GetConditionExpressionBuilderFor(Operators.Contains, Multiplicities.ManyToOne)) - .Returns(conditionExpressionBuilder); - IDataTypesConfigurationProvider dataTypesConfigurationProvider = Mock.Of(); - Mock.Get(dataTypesConfigurationProvider) - .Setup(x => x.GetDataTypeConfiguration(DataTypes.String)) - .Returns(dataTypeConfiguration); - - ManyToOneValueConditionNodeCompiler manyToManyValueConditionNodeCompiler = - new ManyToOneValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); - - // Act - var compiledValueConditionNode = manyToManyValueConditionNodeCompiler.Compile(valueConditionNode, parameterExpression); - - // Assert - compiledValueConditionNode.Should().NotBeNull(); - actualLeftExpression.Should().NotBeNull(); - actualLeftExpression.Type.Should().BeAssignableTo>(); - actualRightExpression.Should().NotBeNull(); - actualRightExpression.Type.Should().Be(); - } - } -} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilderTests.GoldenFile1.csx b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilderTests.GoldenFile1.csx new file mode 100644 index 00000000..d4e1c271 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilderTests.GoldenFile1.csx @@ -0,0 +1,18 @@ +public bool Main(object leftOperand, object rightOperand) +{ + object coalescedLeftOperand; + + if (leftOperand != null) + { + coalescedLeftOperand = leftOperand; + } + else + { + coalescedLeftOperand = null; + } + IEnumerable convertedLeftOperand = (IEnumerable)coalescedLeftOperand; + string convertedRightOperand = (string)rightOperand; + bool result = true && true; + return result; + +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilderTests.cs new file mode 100644 index 00000000..659f6201 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilderTests.cs @@ -0,0 +1,105 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq.Expressions; + using System.Reflection; + using DiffPlex.DiffBuilder; + using ExpressionDebugger; + using FluentAssertions; + using Moq; + using Rules.Framework.Core; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Xunit; + + public class ManyToOneValueConditionNodeExpressionBuilderTests + { + [Fact] + public void Build_GivenLeftHandOperatorRightHandDataTypeConfiguration_ReturnsExpression() + { + // Arrange + string expectedScript; + using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Rules.Framework.Tests.Evaluation.Compiled.ManyToOneValueConditionNodeExpressionBuilderTests.GoldenFile1.csx")) + using (var streamReader = new StreamReader(stream)) + { + expectedScript = streamReader.ReadToEnd(); + } + Expression actualLeftExpression = null; + Expression actualRightExpression = null; + + var conditionExpression = Expression.AndAlso(Expression.Constant(true, typeof(bool)), Expression.Constant(true, typeof(bool))); // For testing purposes, does not need to stay true to the scenario + var conditionExpressionBuilder = Mock.Of(); + Mock.Get(conditionExpressionBuilder) + .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny())) + .Callback((b, args) => + { + actualLeftExpression = args.LeftHandOperand; + actualRightExpression = args.RightHandOperand; + }) + .Returns(conditionExpression); + var conditionExpressionBuilderProvider = Mock.Of(); + Mock.Get(conditionExpressionBuilderProvider) + .Setup(x => x.GetConditionExpressionBuilderFor(Operators.Contains, Multiplicities.ManyToOne)) + .Returns(conditionExpressionBuilder); + + var manyToManyValueConditionNodeCompiler = + new ManyToOneValueConditionNodeExpressionBuilder(conditionExpressionBuilderProvider); + + // Act + var expressionResult = ExpressionBuilder.NewExpression("TestDummy") + .WithParameters(p => + { + p.CreateParameter("leftOperand", typeof(object)); + p.CreateParameter("rightOperand", typeof(object)); + }) + .HavingReturn(typeof(bool), false) + .SetImplementation(builder => + { + var resultVariableExpression = builder.CreateVariable("result", typeof(bool)); + var args = new BuildValueConditionNodeExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null), + LeftOperandVariableExpression = builder.GetParameter("leftOperand"), + Operator = Operators.Contains, + ResultVariableExpression = resultVariableExpression, + RightOperandVariableExpression = builder.GetParameter("rightOperand"), + }; + + manyToManyValueConditionNodeCompiler.Build(builder, args); + + builder.Return(resultVariableExpression); + }) + .Build(); + + // Assert + expressionResult.Should().NotBeNull(); + expressionResult.Implementation.Should().NotBeNull(); + + // Purely for the effect of comparing the generated lambda expression code with the + // golden file. + var lambdaExpression = Expression.Lambda>(expressionResult.Implementation, expressionResult.Parameters); + var actualScript = lambdaExpression.ToScript(); + var diffResult = SideBySideDiffBuilder.Diff(expectedScript, actualScript, ignoreWhiteSpace: true); + diffResult.NewText.HasDifferences.Should().BeFalse(); + Func compiledLambdaExpression = null; + FluentActions.Invoking(() => compiledLambdaExpression = lambdaExpression.Compile()) + .Should() + .NotThrow("expression should be compilable"); + FluentActions.Invoking(() => compiledLambdaExpression.Invoke(null, "dummy")) + .Should() + .NotThrow("compiled expression should be executable"); + FluentActions.Invoking(() => compiledLambdaExpression.Invoke(new string[] { "abc" }, "dummy")) + .Should() + .NotThrow("compiled expression should be able to deal with null left operand"); + actualLeftExpression.Should().NotBeNull(); + actualLeftExpression.Type.Should().BeAssignableTo>(); + actualRightExpression.Should().NotBeNull(); + actualRightExpression.Type.Should().Be(); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeCompilerTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeCompilerTests.cs deleted file mode 100644 index 9e301181..00000000 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeCompilerTests.cs +++ /dev/null @@ -1,109 +0,0 @@ -namespace Rules.Framework.Tests.Evaluation.Compiled -{ - using Moq; - using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; - using Rules.Framework.Evaluation.Compiled; - using Rules.Framework.Evaluation; - using Rules.Framework.Tests.TestStubs; - using System.Collections.Generic; - using System.Linq.Expressions; - using Xunit; - using FluentAssertions; - using System; - - public class OneToManyValueConditionNodeCompilerTests - { - [Fact] - public void Compile_GivenValueConditionNodeAndParameterExpression_ReturnsCompiledLambda() - { - // Arrange - ValueConditionNode valueConditionNode = new ValueConditionNode( - DataTypes.String, - ConditionType.IsoCountryCode, - Operators.In, - new[] { "PT", "ES" }); - ParameterExpression parameterExpression = Expression.Parameter(typeof(IDictionary)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); - Expression actualLeftExpression = null; - Expression actualRightExpression = null; - - Expression conditionExpression = Expression.Constant(true, typeof(bool)); // For testing purposes, does not need to stay true to the scenario - IConditionExpressionBuilder conditionExpressionBuilder = Mock.Of(); - Mock.Get(conditionExpressionBuilder) - .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((e1, e2, dtc) => - { - actualLeftExpression = e1; - actualRightExpression = e2; - }) - .Returns(conditionExpression); - IConditionExpressionBuilderProvider conditionExpressionBuilderProvider = Mock.Of(); - Mock.Get(conditionExpressionBuilderProvider) - .Setup(x => x.GetConditionExpressionBuilderFor(Operators.In, Multiplicities.OneToMany)) - .Returns(conditionExpressionBuilder); - IDataTypesConfigurationProvider dataTypesConfigurationProvider = Mock.Of(); - Mock.Get(dataTypesConfigurationProvider) - .Setup(x => x.GetDataTypeConfiguration(DataTypes.String)) - .Returns(dataTypeConfiguration); - - OneToManyValueConditionNodeCompiler manyToManyValueConditionNodeCompiler = - new OneToManyValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); - - // Act - var compiledValueConditionNode = manyToManyValueConditionNodeCompiler.Compile(valueConditionNode, parameterExpression); - - // Assert - compiledValueConditionNode.Should().NotBeNull(); - actualLeftExpression.Should().NotBeNull(); - actualLeftExpression.Type.Should().Be(); - actualRightExpression.Should().NotBeNull(); - actualRightExpression.Type.Should().BeAssignableTo>(); - } - - [Fact] - public void Compile_GivenValueConditionNodeWithValueNonArrayTypeAndParameterExpression_ThrowsArgumentException() - { - // Arrange - ValueConditionNode valueConditionNode = new ValueConditionNode( - DataTypes.String, - ConditionType.IsoCountryCode, - Operators.In, - "PT"); - ParameterExpression parameterExpression = Expression.Parameter(typeof(IDictionary)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); - Expression actualLeftExpression = null; - Expression actualRightExpression = null; - - Expression conditionExpression = Expression.Constant(true, typeof(bool)); // For testing purposes, does not need to stay true to the scenario - IConditionExpressionBuilder conditionExpressionBuilder = Mock.Of(); - Mock.Get(conditionExpressionBuilder) - .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((e1, e2, dtc) => - { - actualLeftExpression = e1; - actualRightExpression = e2; - }) - .Returns(conditionExpression); - IConditionExpressionBuilderProvider conditionExpressionBuilderProvider = Mock.Of(); - Mock.Get(conditionExpressionBuilderProvider) - .Setup(x => x.GetConditionExpressionBuilderFor(Operators.In, Multiplicities.OneToMany)) - .Returns(conditionExpressionBuilder); - IDataTypesConfigurationProvider dataTypesConfigurationProvider = Mock.Of(); - Mock.Get(dataTypesConfigurationProvider) - .Setup(x => x.GetDataTypeConfiguration(DataTypes.String)) - .Returns(dataTypeConfiguration); - - OneToManyValueConditionNodeCompiler manyToManyValueConditionNodeCompiler = - new OneToManyValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); - - // Act - ArgumentException argumentException = Assert.Throws(() => manyToManyValueConditionNodeCompiler.Compile(valueConditionNode, parameterExpression)); - - // Assert - argumentException.Should().NotBeNull(); - argumentException.ParamName.Should().Be("operand"); - } - } -} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilderTests.GoldenFile1.csx b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilderTests.GoldenFile1.csx new file mode 100644 index 00000000..fceb824d --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilderTests.GoldenFile1.csx @@ -0,0 +1,20 @@ +private CultureInfo CultureInfo1; + +public bool Main(object leftOperand, object rightOperand) +{ + object coalescedLeftOperand; + + if (leftOperand != null) + { + coalescedLeftOperand = leftOperand; + } + else + { + coalescedLeftOperand = null; + } + string convertedLeftOperand = (string)Convert.ChangeType(coalescedLeftOperand, typeof(string), CultureInfo1); + IEnumerable convertedRightOperand = (IEnumerable)rightOperand; + bool result = true && true; + return result; + +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilderTests.cs new file mode 100644 index 00000000..a317d2a7 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilderTests.cs @@ -0,0 +1,105 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq.Expressions; + using System.Reflection; + using DiffPlex.DiffBuilder; + using ExpressionDebugger; + using FluentAssertions; + using Moq; + using Rules.Framework.Core; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Xunit; + + public class OneToManyValueConditionNodeExpressionBuilderTests + { + [Fact] + public void Build_GivenLefthandOperatorRightHandAndDataTypeConfiguration_ReturnsExpression() + { + // Arrange + string expectedScript; + using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Rules.Framework.Tests.Evaluation.Compiled.OneToManyValueConditionNodeExpressionBuilderTests.GoldenFile1.csx")) + using (var streamReader = new StreamReader(stream)) + { + expectedScript = streamReader.ReadToEnd(); + } + Expression actualLeftExpression = null; + Expression actualRightExpression = null; + + var conditionExpression = Expression.AndAlso(Expression.Constant(true, typeof(bool)), Expression.Constant(true, typeof(bool))); // For testing purposes, does not need to stay true to the scenario + var conditionExpressionBuilder = Mock.Of(); + Mock.Get(conditionExpressionBuilder) + .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny())) + .Callback((b, args) => + { + actualLeftExpression = args.LeftHandOperand; + actualRightExpression = args.RightHandOperand; + }) + .Returns(conditionExpression); + var conditionExpressionBuilderProvider = Mock.Of(); + Mock.Get(conditionExpressionBuilderProvider) + .Setup(x => x.GetConditionExpressionBuilderFor(Operators.In, Multiplicities.OneToMany)) + .Returns(conditionExpressionBuilder); + + var manyToManyValueConditionNodeCompiler = + new OneToManyValueConditionNodeExpressionBuilder(conditionExpressionBuilderProvider); + + // Act + var expressionResult = ExpressionBuilder.NewExpression("TestDummy") + .WithParameters(p => + { + p.CreateParameter("leftOperand", typeof(object)); + p.CreateParameter("rightOperand", typeof(object)); + }) + .HavingReturn(typeof(bool), false) + .SetImplementation(builder => + { + var resultVariableExpression = builder.CreateVariable("result", typeof(bool)); + var args = new BuildValueConditionNodeExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null), + LeftOperandVariableExpression = builder.GetParameter("leftOperand"), + Operator = Operators.In, + ResultVariableExpression = resultVariableExpression, + RightOperandVariableExpression = builder.GetParameter("rightOperand"), + }; + + manyToManyValueConditionNodeCompiler.Build(builder, args); + + builder.Return(resultVariableExpression); + }) + .Build(); + + // Assert + expressionResult.Should().NotBeNull(); + expressionResult.Implementation.Should().NotBeNull(); + + // Purely for the effect of comparing the generated lambda expression code with the + // golden file. + var lambdaExpression = Expression.Lambda>(expressionResult.Implementation, expressionResult.Parameters); + var actualScript = lambdaExpression.ToScript(); + var diffResult = SideBySideDiffBuilder.Diff(expectedScript, actualScript, ignoreWhiteSpace: true); + diffResult.NewText.HasDifferences.Should().BeFalse(); + Func compiledLambdaExpression = null; + FluentActions.Invoking(() => compiledLambdaExpression = lambdaExpression.Compile()) + .Should() + .NotThrow("expression should be compilable"); + FluentActions.Invoking(() => compiledLambdaExpression.Invoke(null, new string[] { "dummy" })) + .Should() + .NotThrow("compiled expression should be executable"); + FluentActions.Invoking(() => compiledLambdaExpression.Invoke("abc", new string[] { "dummy" })) + .Should() + .NotThrow("compiled expression should be able to deal with null left operand"); + actualLeftExpression.Should().NotBeNull(); + actualLeftExpression.Type.Should().Be(); + actualRightExpression.Should().NotBeNull(); + actualRightExpression.Type.Should().BeAssignableTo>(); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeCompilerTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeCompilerTests.cs deleted file mode 100644 index c7f9adef..00000000 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeCompilerTests.cs +++ /dev/null @@ -1,159 +0,0 @@ -namespace Rules.Framework.Tests.Evaluation.Compiled -{ - using Moq; - using Rules.Framework.Core.ConditionNodes; - using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; - using Rules.Framework.Evaluation.Compiled; - using Rules.Framework.Evaluation; - using Rules.Framework.Tests.TestStubs; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Linq.Expressions; - using System.Text; - using System.Threading.Tasks; - using Xunit; - using FluentAssertions; - - public class OneToOneValueConditionNodeCompilerTests - { - [Fact] - public void Compile_GivenValueConditionNodeAndParameterExpression_ReturnsCompiledLambda() - { - // Arrange - ValueConditionNode valueConditionNode = new ValueConditionNode( - DataTypes.String, - ConditionType.IsoCountryCode, - Operators.Equal, - "ES"); - ParameterExpression parameterExpression = Expression.Parameter(typeof(IDictionary)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); - Expression actualLeftExpression = null; - Expression actualRightExpression = null; - - Expression conditionExpression = Expression.Constant(true, typeof(bool)); // For testing purposes, does not need to stay true to the scenario - IConditionExpressionBuilder conditionExpressionBuilder = Mock.Of(); - Mock.Get(conditionExpressionBuilder) - .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((e1, e2, dtc) => - { - actualLeftExpression = e1; - actualRightExpression = e2; - }) - .Returns(conditionExpression); - IConditionExpressionBuilderProvider conditionExpressionBuilderProvider = Mock.Of(); - Mock.Get(conditionExpressionBuilderProvider) - .Setup(x => x.GetConditionExpressionBuilderFor(Operators.Equal, Multiplicities.OneToOne)) - .Returns(conditionExpressionBuilder); - IDataTypesConfigurationProvider dataTypesConfigurationProvider = Mock.Of(); - Mock.Get(dataTypesConfigurationProvider) - .Setup(x => x.GetDataTypeConfiguration(DataTypes.String)) - .Returns(dataTypeConfiguration); - - OneToOneValueConditionNodeCompiler manyToManyValueConditionNodeCompiler = - new OneToOneValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); - - // Act - var compiledValueConditionNode = manyToManyValueConditionNodeCompiler.Compile(valueConditionNode, parameterExpression); - - // Assert - compiledValueConditionNode.Should().NotBeNull(); - actualLeftExpression.Should().NotBeNull(); - actualLeftExpression.Type.Should().Be(); - actualRightExpression.Should().NotBeNull(); - actualRightExpression.Type.Should().Be(); - } - - [Fact] - public void Compile_GivenValueConditionNodeWithNullOperandAndParameterExpression_ReturnsCompiledLambdaUsingDataTypeDefault() - { - // Arrange - ValueConditionNode valueConditionNode = new ValueConditionNode( - DataTypes.Integer, - ConditionType.NumberOfSales, - Operators.Equal, - null); - ParameterExpression parameterExpression = Expression.Parameter(typeof(IDictionary)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0); - Expression actualLeftExpression = null; - Expression actualRightExpression = null; - - Expression conditionExpression = Expression.Constant(true, typeof(bool)); // For testing purposes, does not need to stay true to the scenario - IConditionExpressionBuilder conditionExpressionBuilder = Mock.Of(); - Mock.Get(conditionExpressionBuilder) - .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((e1, e2, dtc) => - { - actualLeftExpression = e1; - actualRightExpression = e2; - }) - .Returns(conditionExpression); - IConditionExpressionBuilderProvider conditionExpressionBuilderProvider = Mock.Of(); - Mock.Get(conditionExpressionBuilderProvider) - .Setup(x => x.GetConditionExpressionBuilderFor(Operators.Equal, Multiplicities.OneToOne)) - .Returns(conditionExpressionBuilder); - IDataTypesConfigurationProvider dataTypesConfigurationProvider = Mock.Of(); - Mock.Get(dataTypesConfigurationProvider) - .Setup(x => x.GetDataTypeConfiguration(DataTypes.Integer)) - .Returns(dataTypeConfiguration); - - OneToOneValueConditionNodeCompiler oneToOneValueConditionNodeCompiler = - new OneToOneValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); - - // Act - var compiledValueConditionNode = oneToOneValueConditionNodeCompiler.Compile(valueConditionNode, parameterExpression); - - // Assert - compiledValueConditionNode.Should().NotBeNull(); - actualLeftExpression.Should().NotBeNull(); - actualLeftExpression.Type.Should().Be(); - actualRightExpression.Should().NotBeNull(); - actualRightExpression.Type.Should().Be(); - } - - [Fact] - public void Compile_GivenValueConditionNodeWithWrongTypeOperandAndParameterExpression_ThrowsArgumentException() - { - // Arrange - ValueConditionNode valueConditionNode = new ValueConditionNode( - DataTypes.Integer, - ConditionType.NumberOfSales, - Operators.Equal, - "abc"); - ParameterExpression parameterExpression = Expression.Parameter(typeof(IDictionary)); - DataTypeConfiguration dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0); - Expression actualLeftExpression = null; - Expression actualRightExpression = null; - - Expression conditionExpression = Expression.Constant(true, typeof(bool)); // For testing purposes, does not need to stay true to the scenario - IConditionExpressionBuilder conditionExpressionBuilder = Mock.Of(); - Mock.Get(conditionExpressionBuilder) - .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((e1, e2, dtc) => - { - actualLeftExpression = e1; - actualRightExpression = e2; - }) - .Returns(conditionExpression); - IConditionExpressionBuilderProvider conditionExpressionBuilderProvider = Mock.Of(); - Mock.Get(conditionExpressionBuilderProvider) - .Setup(x => x.GetConditionExpressionBuilderFor(Operators.Equal, Multiplicities.OneToOne)) - .Returns(conditionExpressionBuilder); - IDataTypesConfigurationProvider dataTypesConfigurationProvider = Mock.Of(); - Mock.Get(dataTypesConfigurationProvider) - .Setup(x => x.GetDataTypeConfiguration(DataTypes.Integer)) - .Returns(dataTypeConfiguration); - - OneToOneValueConditionNodeCompiler oneToOneValueConditionNodeCompiler = - new OneToOneValueConditionNodeCompiler(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); - - // Act - ArgumentException argumentException = Assert.Throws(() => oneToOneValueConditionNodeCompiler.Compile(valueConditionNode, parameterExpression)); - - // Assert - argumentException.Should().NotBeNull(); - argumentException.ParamName.Should().Be("operand"); - } - } -} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilderTests.GoldenFile1.csx b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilderTests.GoldenFile1.csx new file mode 100644 index 00000000..0e6fee62 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilderTests.GoldenFile1.csx @@ -0,0 +1,20 @@ +private CultureInfo CultureInfo1; + +public bool Main(object leftOperand, object rightOperand) +{ + object coalescedLeftOperand; + + if (leftOperand != null) + { + coalescedLeftOperand = leftOperand; + } + else + { + coalescedLeftOperand = null; + } + string convertedLeftOperand = (string)Convert.ChangeType(coalescedLeftOperand, typeof(string), CultureInfo1); + string convertedRightOperand = (string)rightOperand; + bool result = true && true; + return result; + +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilderTests.cs new file mode 100644 index 00000000..4020bff4 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilderTests.cs @@ -0,0 +1,104 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled +{ + using System; + using System.IO; + using System.Linq.Expressions; + using System.Reflection; + using DiffPlex.DiffBuilder; + using ExpressionDebugger; + using FluentAssertions; + using Moq; + using Rules.Framework.Core; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Xunit; + + public class OneToOneValueConditionNodeExpressionBuilderTests + { + [Fact] + public void Build_GivenExpressionBuilderAndArgs_BuildsExpression() + { + // Arrange + string expectedScript; + using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Rules.Framework.Tests.Evaluation.Compiled.OneToOneValueConditionNodeExpressionBuilderTests.GoldenFile1.csx")) + using (var streamReader = new StreamReader(stream)) + { + expectedScript = streamReader.ReadToEnd(); + } + Expression actualLeftExpression = null; + Expression actualRightExpression = null; + + var conditionExpression = Expression.AndAlso(Expression.Constant(true, typeof(bool)), Expression.Constant(true, typeof(bool))); // For testing purposes, does not need to stay true to the scenario + var conditionExpressionBuilder = Mock.Of(); + Mock.Get(conditionExpressionBuilder) + .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny())) + .Callback((b, args) => + { + actualLeftExpression = args.LeftHandOperand; + actualRightExpression = args.RightHandOperand; + }) + .Returns(conditionExpression); + var conditionExpressionBuilderProvider = Mock.Of(); + Mock.Get(conditionExpressionBuilderProvider) + .Setup(x => x.GetConditionExpressionBuilderFor(Operators.Equal, Multiplicities.OneToOne)) + .Returns(conditionExpressionBuilder); + + var manyToManyValueConditionNodeCompiler = + new OneToOneValueConditionNodeExpressionBuilder(conditionExpressionBuilderProvider); + + // Act + var expressionResult = ExpressionBuilder.NewExpression("TestDummy") + .WithParameters(p => + { + p.CreateParameter("leftOperand", typeof(object)); + p.CreateParameter("rightOperand", typeof(object)); + }) + .HavingReturn(typeof(bool), false) + .SetImplementation(builder => + { + var resultVariableExpression = builder.CreateVariable("result", typeof(bool)); + var args = new BuildValueConditionNodeExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null), + LeftOperandVariableExpression = builder.GetParameter("leftOperand"), + Operator = Operators.Equal, + ResultVariableExpression = resultVariableExpression, + RightOperandVariableExpression = builder.GetParameter("rightOperand"), + }; + + manyToManyValueConditionNodeCompiler.Build(builder, args); + + builder.Return(resultVariableExpression); + }) + .Build(); + + // Assert + expressionResult.Should().NotBeNull(); + expressionResult.Implementation.Should().NotBeNull(); + + // Purely for the effect of comparing the generated lambda expression code with the + // golden file. + var lambdaExpression = Expression.Lambda>(expressionResult.Implementation, expressionResult.Parameters); + var actualScript = lambdaExpression.ToScript(); + var diffResult = SideBySideDiffBuilder.Diff(expectedScript, actualScript, ignoreWhiteSpace: true); + diffResult.NewText.HasDifferences.Should().BeFalse(); + Func compiledLambdaExpression = null; + FluentActions.Invoking(() => compiledLambdaExpression = lambdaExpression.Compile()) + .Should() + .NotThrow("expression should be compilable"); + FluentActions.Invoking(() => compiledLambdaExpression.Invoke(null, "dummy")) + .Should() + .NotThrow("compiled expression should be executable"); + FluentActions.Invoking(() => compiledLambdaExpression.Invoke("abc", "dummy")) + .Should() + .NotThrow("compiled expression should be able to deal with null left operand"); + actualLeftExpression.Should().NotBeNull(); + actualLeftExpression.Type.Should().Be(); + actualRightExpression.Should().NotBeNull(); + actualRightExpression.Type.Should().Be(); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.GoldenFile1.csx b/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.GoldenFile1.csx new file mode 100644 index 00000000..6c31afd6 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.GoldenFile1.csx @@ -0,0 +1,75 @@ +private Func Evaluate1; +private Func, ConditionType, object> GetValueOrDefault1; + +internal bool Main(EvaluationContext evaluationContext) +{ + bool _C0_result; + object _C0_leftOperand; + object _C0_rightOperand; + string _C0_multiplicity; + bool _C1_result; + object _C1_leftOperand; + object _C1_rightOperand; + string _C1_multiplicity; + + _C0_leftOperand = GetValueOrDefault1.Invoke(evaluationContext.get_Conditions(), ConditionType.NumberOfSales); + _C0_rightOperand = 100; + + if (_C0_leftOperand == null) + { + if (evaluationContext.get_MissingConditionBehavior() == MissingConditionBehaviors.Discard) + { + _C0_result = false; + goto _C0_Label_EndValueConditionNode; + } + + if (evaluationContext.get_MatchMode() == MatchModes.Search) + { + _C0_result = true; + goto _C0_Label_EndValueConditionNode; + } + } + _C0_multiplicity = Evaluate1.Invoke(_C0_leftOperand, Operators.Equal, _C0_rightOperand); + + switch (_C0_multiplicity) + { + case "one-to-one": + _C0_result = true; + break; + default: + break; + } +_C0_Label_EndValueConditionNode: + + _C1_leftOperand = GetValueOrDefault1.Invoke(evaluationContext.get_Conditions(), ConditionType.IsoCountryCode); + _C1_rightOperand = "GB"; + + if (_C1_leftOperand == null) + { + if (evaluationContext.get_MissingConditionBehavior() == MissingConditionBehaviors.Discard) + { + _C1_result = false; + goto _C1_Label_EndValueConditionNode; + } + + if (evaluationContext.get_MatchMode() == MatchModes.Search) + { + _C1_result = true; + goto _C1_Label_EndValueConditionNode; + } + } + _C1_multiplicity = Evaluate1.Invoke(_C1_leftOperand, Operators.Equal, _C1_rightOperand); + + switch (_C1_multiplicity) + { + case "one-to-one": + _C1_result = true; + break; + default: + break; + } +_C1_Label_EndValueConditionNode: + bool result = _C0_result && _C1_result; + return result; + +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.cs new file mode 100644 index 00000000..85f3a45c --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.cs @@ -0,0 +1,174 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Reflection; + using DiffPlex.DiffBuilder; + using ExpressionDebugger; + using FluentAssertions; + using Moq; + using Rules.Framework.Core; + using Rules.Framework.Core.ConditionNodes; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Tests.TestStubs; + using Xunit; + + public class RuleConditionsExpressionBuilderTests + { + internal IEnumerable<(string, EvaluationContext, bool)> Scenarios => new[] + { + ( + "Scenario 1 - MissingConditionsBehavior = 'Discard', MatchMode = 'Exact', and only contains condition for 'NumberOfSales'", + new EvaluationContext( + new Dictionary + { + { ConditionType.NumberOfSales, 500 }, + }, + MatchModes.Exact, + MissingConditionBehaviors.Discard), + false + ), + ( + "Scenario 2 - MissingConditionsBehavior = 'Discard', MatchMode = 'Exact', and both needed conditions", + new EvaluationContext( + new Dictionary + { + { ConditionType.NumberOfSales, 500 }, + { ConditionType.IsoCountryCode, "PT" }, + }, + MatchModes.Exact, + MissingConditionBehaviors.Discard), + true + ), + ( + "Scenario 3 - MissingConditionsBehavior = 'UseDataTypeDefault', MatchMode = 'Exact', and both needed conditions", + new EvaluationContext( + new Dictionary + { + { ConditionType.NumberOfSales, 500 }, + { ConditionType.IsoCountryCode, "PT" }, + }, + MatchModes.Exact, + MissingConditionBehaviors.UseDataTypeDefault), + true + ), + ( + "Scenario 4 - MissingConditionsBehavior = 'UseDataTypeDefault', MatchMode = 'Search', and both needed conditions", + new EvaluationContext( + new Dictionary + { + { ConditionType.NumberOfSales, 500 }, + { ConditionType.IsoCountryCode, "PT" }, + }, + MatchModes.Search, + MissingConditionBehaviors.UseDataTypeDefault), + true + ), + ( + "Scenario 4 - MissingConditionsBehavior = 'UseDataTypeDefault', MatchMode = 'Search', and only contains condition for 'NumberOfSales'", + new EvaluationContext( + new Dictionary + { + { ConditionType.NumberOfSales, 500 }, + }, + MatchModes.Search, + MissingConditionBehaviors.UseDataTypeDefault), + true + ) + }; + + [Fact] + public void BuildExpression_GivenComposedConditionNodeWith2ChildValueConditionNodes_BuildsLambdaExpression() + { + // Arrange + string expectedScript; + using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Rules.Framework.Tests.Evaluation.Compiled.RuleConditionsExpressionBuilderTests.GoldenFile1.csx")) + using (var streamReader = new StreamReader(stream)) + { + expectedScript = streamReader.ReadToEnd(); + } + var valueConditionNode1 + = new ValueConditionNode(DataTypes.Integer, ConditionType.NumberOfSales, Operators.Equal, 100); + var valueConditionNode2 + = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "GB"); + + var composedConditionNode + = new ComposedConditionNode(LogicalOperators.And, new[] { valueConditionNode1, valueConditionNode2 }); + + var valueConditionNodeExpressionBuilder = Mock.Of(); + Mock.Get(valueConditionNodeExpressionBuilder) + .Setup(x => x.Build(It.IsAny(), It.IsAny())) + .Callback( + (builder, args) => + { + builder.Assign(args.ResultVariableExpression, builder.Constant(true)); + builder.AddExpression(builder.Empty()); + }); + + var valueConditionNodeExpressionBuilderProvider = Mock.Of(); + Mock.Get(valueConditionNodeExpressionBuilderProvider) + .Setup(x => x.GetExpressionBuilder(It.Is(Multiplicities.OneToOne, StringComparer.Ordinal))) + .Returns(valueConditionNodeExpressionBuilder); + + var dataTypeConfigurationProvider = Mock.Of(); + Mock.Get(dataTypeConfigurationProvider) + .Setup(x => x.GetDataTypeConfiguration(DataTypes.String)) + .Returns(DataTypeConfiguration.Create(DataTypes.String, typeof(string), null)); + Mock.Get(dataTypeConfigurationProvider) + .Setup(x => x.GetDataTypeConfiguration(DataTypes.Integer)) + .Returns(DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0)); + + var conditionsTreeCompiler = new RuleConditionsExpressionBuilder( + valueConditionNodeExpressionBuilderProvider, + dataTypeConfigurationProvider); + + // Act + var expression = conditionsTreeCompiler.BuildExpression(composedConditionNode); + + // Assert + expression.Should().NotBeNull(); + var actualScript = expression.ToScript(); + var diffResult = SideBySideDiffBuilder.Diff(expectedScript, actualScript, ignoreWhiteSpace: true); + diffResult.NewText.HasDifferences.Should().BeFalse(); + + Func, bool> compiledLambdaExpression = null; + FluentActions.Invoking(() => compiledLambdaExpression = expression.Compile()) + .Should() + .NotThrow("expression should be compilable"); + + foreach (var scenario in this.Scenarios) + { + bool? result = null; + FluentActions.Invoking(() => result = compiledLambdaExpression.Invoke(scenario.Item2)) + .Should() + .NotThrow($"compiled expression should be executable under scenario: {scenario.Item1}"); + + result.Should().Be(scenario.Item3); + } + } + + [Fact] + public void BuildExpression_GivenUnknownConditionNode_ThrowsNotSupportedException() + { + // Arrange + var stubConditionNode = new StubConditionNode(); + + var valueConditionNodeExpressionBuilderProvider = Mock.Of(); + var dataTypeConfigurationProvider = Mock.Of(); + + var conditionsTreeCompiler = new RuleConditionsExpressionBuilder( + valueConditionNodeExpressionBuilderProvider, + dataTypeConfigurationProvider); + + // Act + var notSupportedException = Assert.Throws(() => conditionsTreeCompiler.BuildExpression(stubConditionNode)); + + // Assert + notSupportedException.Should().NotBeNull(); + notSupportedException.Message.Should().Contain(nameof(StubConditionNode)); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ValueConditionNodeCompilerProviderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ValueConditionNodeCompilerProviderTests.cs deleted file mode 100644 index b91a19d8..00000000 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ValueConditionNodeCompilerProviderTests.cs +++ /dev/null @@ -1,59 +0,0 @@ -namespace Rules.Framework.Tests.Evaluation.Compiled -{ - using FluentAssertions; - using Moq; - using Rules.Framework.Evaluation; - using Rules.Framework.Evaluation.Compiled; - using Rules.Framework.Evaluation.Compiled.ConditionBuilders; - using System; - using System.Collections.Generic; - using Xunit; - - public class ValueConditionNodeCompilerProviderTests - { - public static IEnumerable SuccessScenarios => new[] - { - new object[] { Multiplicities.OneToOne, typeof(OneToOneValueConditionNodeCompiler) }, - new object[] { Multiplicities.OneToMany, typeof(OneToManyValueConditionNodeCompiler) }, - new object[] { Multiplicities.ManyToOne, typeof(ManyToOneValueConditionNodeCompiler) }, - new object[] { Multiplicities.ManyToMany, typeof(ManyToManyValueConditionNodeCompiler) }, - }; - - [Theory] - [MemberData(nameof(SuccessScenarios))] - public void GetValueConditionNodeCompiler_GivenValidMultiplicity_ReturnsMatchingCompiler(string multiplicity, Type compilerType) - { - // Arrange - IConditionExpressionBuilderProvider conditionExpressionBuilderProvider = Mock.Of(); - IDataTypesConfigurationProvider dataTypesConfigurationProvider = Mock.Of(); - - ValueConditionNodeCompilerProvider valueConditionNodeCompilerProvider - = new ValueConditionNodeCompilerProvider(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); - - // Act - var valueConditionNodeCompiler = valueConditionNodeCompilerProvider.GetValueConditionNodeCompiler(multiplicity); - - // Assert - valueConditionNodeCompiler.Should().NotBeNull().And.BeOfType(compilerType); - } - - [Fact] - public void GetValueConditionNodeCompiler_GivenUnknownMultiplicity_ThrowsInvalidOperationException() - { - // Arrange - string multiplicity = "unknown-multiplicity"; - IConditionExpressionBuilderProvider conditionExpressionBuilderProvider = Mock.Of(); - IDataTypesConfigurationProvider dataTypesConfigurationProvider = Mock.Of(); - - ValueConditionNodeCompilerProvider valueConditionNodeCompilerProvider - = new ValueConditionNodeCompilerProvider(conditionExpressionBuilderProvider, dataTypesConfigurationProvider); - - // Act - NotSupportedException notSupportedException = Assert.Throws(() => valueConditionNodeCompilerProvider.GetValueConditionNodeCompiler(multiplicity)); - - // Assert - notSupportedException.Should().NotBeNull(); - notSupportedException.Message.Should().Contain(multiplicity); - } - } -} diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ValueConditionNodeExpressionBuilderProviderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ValueConditionNodeExpressionBuilderProviderTests.cs new file mode 100644 index 00000000..57767421 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ValueConditionNodeExpressionBuilderProviderTests.cs @@ -0,0 +1,57 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled +{ + using System; + using System.Collections.Generic; + using FluentAssertions; + using Moq; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Xunit; + + public class ValueConditionNodeExpressionBuilderProviderTests + { + public static IEnumerable SuccessScenarios => new[] + { + new object[] { Multiplicities.OneToOne, typeof(OneToOneValueConditionNodeExpressionBuilder) }, + new object[] { Multiplicities.OneToMany, typeof(OneToManyValueConditionNodeExpressionBuilder) }, + new object[] { Multiplicities.ManyToOne, typeof(ManyToOneValueConditionNodeExpressionBuilder) }, + new object[] { Multiplicities.ManyToMany, typeof(ManyToManyValueConditionNodeExpressionBuilder) }, + }; + + [Fact] + public void GetExpressionBuilder_GivenUnknownMultiplicity_ThrowsInvalidOperationException() + { + // Arrange + var multiplicity = "unknown-multiplicity"; + var conditionExpressionBuilderProvider = Mock.Of(); + + var valueConditionNodeExpressionBuilderProvider + = new ValueConditionNodeExpressionBuilderProvider(conditionExpressionBuilderProvider); + + // Act + var notSupportedException = Assert.Throws(() => valueConditionNodeExpressionBuilderProvider.GetExpressionBuilder(multiplicity)); + + // Assert + notSupportedException.Should().NotBeNull(); + notSupportedException.Message.Should().Contain(multiplicity); + } + + [Theory] + [MemberData(nameof(SuccessScenarios))] + public void GetExpressionBuilder_GivenValidMultiplicity_ReturnsMatchingBuilder(string multiplicity, Type compilerType) + { + // Arrange + var conditionExpressionBuilderProvider = Mock.Of(); + + var valueConditionNodeExpressionBuilderProvider + = new ValueConditionNodeExpressionBuilderProvider(conditionExpressionBuilderProvider); + + // Act + var valueConditionNodeExpressionBuilder = valueConditionNodeExpressionBuilderProvider.GetExpressionBuilder(multiplicity); + + // Assert + valueConditionNodeExpressionBuilder.Should().NotBeNull().And.BeOfType(compilerType); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Generics/GenericRulesEngineTests.cs b/tests/Rules.Framework.Tests/Generics/GenericRulesEngineTests.cs index 76a4141f..eeef4c14 100644 --- a/tests/Rules.Framework.Tests/Generics/GenericRulesEngineTests.cs +++ b/tests/Rules.Framework.Tests/Generics/GenericRulesEngineTests.cs @@ -101,6 +101,7 @@ public async Task GenericRulesEngine_SearchAsync_Success() { ConditionTypeName = ConditionType.IsoCountryCode.ToString(), DataType = DataTypes.String, + LogicalOperator = LogicalOperators.Eval, Operator = Operators.Equal, Operand = "USA" } diff --git a/tests/Rules.Framework.Tests/Rules.Framework.Tests.csproj b/tests/Rules.Framework.Tests/Rules.Framework.Tests.csproj index 17409342..44ad8315 100644 --- a/tests/Rules.Framework.Tests/Rules.Framework.Tests.csproj +++ b/tests/Rules.Framework.Tests/Rules.Framework.Tests.csproj @@ -8,13 +8,30 @@ - + + + + + + + + + + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive + - - + + all diff --git a/tests/Rules.Framework.WebUI.Tests/Rules.Framework.WebUI.Tests.csproj b/tests/Rules.Framework.WebUI.Tests/Rules.Framework.WebUI.Tests.csproj index dcea3a01..1e6a7be8 100644 --- a/tests/Rules.Framework.WebUI.Tests/Rules.Framework.WebUI.Tests.csproj +++ b/tests/Rules.Framework.WebUI.Tests/Rules.Framework.WebUI.Tests.csproj @@ -2,23 +2,23 @@ netcoreapp3.1 - 9.0 - Full - false + 9.0 + Full + false - + - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive From a401ee03c52da2caa076fd5cbbb365b72cf963b9 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sat, 7 Jan 2023 17:51:16 +0000 Subject: [PATCH 13/54] fix: add missing package on unit tests and fix condition on compiled conditions eval engine --- .../Evaluation/Compiled/CompiledConditionsEvalEngine.cs | 2 +- tests/Rules.Framework.Tests/Rules.Framework.Tests.csproj | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs b/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs index a096d738..953864da 100644 --- a/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs +++ b/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs @@ -27,7 +27,7 @@ public bool Eval(IConditionNode conditionNode, IDictionaryruntime; build; native; contentfiles; analyzers; buildtransitive + From b21ed983e8d41d9439e293564c2f31dc925feaa1 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 19 Feb 2023 00:18:22 +0000 Subject: [PATCH 14/54] refactor: simplify and streamline rules compilation --- .../Builder/ConfiguredRulesEngineBuilder.cs | 2 +- .../CompilationRulesSourceMiddleware.cs | 4 +- .../Compiled/CompiledConditionsEvalEngine.cs | 3 - ...sWithOneToOneConditionExpressionBuilder.cs | 4 +- ...sWithOneToOneConditionExpressionBuilder.cs | 4 +- ...tainsOneToOneConditionExpressionBuilder.cs | 4 +- ...sWithOneToOneConditionExpressionBuilder.cs | 4 +- ...EqualOneToOneConditionExpressionBuilder.cs | 4 +- ...rThanOneToOneConditionExpressionBuilder.cs | 4 +- ...EqualOneToOneConditionExpressionBuilder.cs | 4 +- .../IConditionExpressionBuilder.cs | 4 +- .../InOneToManyConditionExpressionBuilder.cs | 4 +- ...rThanOneToOneConditionExpressionBuilder.cs | 4 +- ...EqualOneToOneConditionExpressionBuilder.cs | 4 +- ...tainsOneToOneConditionExpressionBuilder.cs | 4 +- ...sWithOneToOneConditionExpressionBuilder.cs | 4 +- ...EqualOneToOneConditionExpressionBuilder.cs | 4 +- ...sWithOneToOneConditionExpressionBuilder.cs | 4 +- ...sWithOneToOneConditionExpressionBuilder.cs | 4 +- .../DefaultExpressionBuilderFactory.cs | 48 ++++++++ ...onBuilder.cs => ExpressionBlockBuilder.cs} | 65 ++++++----- .../ExpressionBuilders/ExpressionBuilder.cs | 107 +++++++++++++++++- .../ExpressionConfiguration.cs | 8 +- .../ExpressionParametersConfiguration.cs | 12 +- ...nBuilder.cs => ExpressionSwitchBuilder.cs} | 31 ++--- ...nBuilder.cs => IExpressionBlockBuilder.cs} | 28 +++-- .../IExpressionBuilderFactory.cs | 18 +++ .../IExpressionParametersConfiguration.cs | 5 +- .../IExpressionSwitchBuilder.cs | 23 ++++ .../ConfiguredExpressionBuilder.cs | 34 ------ .../ConfiguredSignatureExpressionBuilder.cs | 35 ------ .../IConfiguredSignatureExpressionBuilder.cs | 9 -- .../IExpressionImplementationBuilder.cs | 10 ++ .../IExpressionParametersBuilder.cs | 12 ++ .../StateMachine/IExpressionReturnBuilder.cs | 11 ++ .../StateMachine/INamedExpressionBuilder.cs | 11 -- .../IParameterizedExpressionBuilder.cs | 11 -- .../StateMachine/ISwitchExpressionBuilder.cs | 19 ---- .../StateMachine/NamedExpressionBuilder.cs | 39 ------- .../ParameterizedExpressionBuilder.cs | 33 ------ .../IValueConditionNodeExpressionBuilder.cs | 4 +- ...ManyValueConditionNodeExpressionBuilder.cs | 4 +- ...oOneValueConditionNodeExpressionBuilder.cs | 4 +- ...ManyValueConditionNodeExpressionBuilder.cs | 4 +- ...oOneValueConditionNodeExpressionBuilder.cs | 4 +- .../RuleConditionsExpressionBuilder.cs | 9 +- 46 files changed, 360 insertions(+), 311 deletions(-) create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/DefaultExpressionBuilderFactory.cs rename src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/{StateMachine/ImplementationExpressionBuilder.cs => ExpressionBlockBuilder.cs} (80%) rename src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/{StateMachine => }/ExpressionConfiguration.cs (65%) rename src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/{StateMachine => }/ExpressionParametersConfiguration.cs (70%) rename src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/{StateMachine/SwitchExpressionBuilder.cs => ExpressionSwitchBuilder.cs} (58%) rename src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/{StateMachine/IImplementationExpressionBuilder.cs => IExpressionBlockBuilder.cs} (67%) create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/IExpressionBuilderFactory.cs rename src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/{StateMachine => }/IExpressionParametersConfiguration.cs (73%) create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/IExpressionSwitchBuilder.cs delete mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ConfiguredExpressionBuilder.cs delete mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ConfiguredSignatureExpressionBuilder.cs delete mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IConfiguredSignatureExpressionBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IExpressionImplementationBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IExpressionParametersBuilder.cs create mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IExpressionReturnBuilder.cs delete mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/INamedExpressionBuilder.cs delete mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IParameterizedExpressionBuilder.cs delete mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ISwitchExpressionBuilder.cs delete mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/NamedExpressionBuilder.cs delete mode 100644 src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ParameterizedExpressionBuilder.cs diff --git a/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs b/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs index 577ee43c..1222ed05 100644 --- a/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs +++ b/src/Rules.Framework/Builder/ConfiguredRulesEngineBuilder.cs @@ -38,7 +38,7 @@ public RulesEngine Build() var conditionExpressionBuilderProvider = new ConditionExpressionBuilderProvider(); var valueConditionNodeCompilerProvider = new ValueConditionNodeExpressionBuilderProvider(conditionExpressionBuilderProvider); var ruleConditionsExpressionBuilder = new RuleConditionsExpressionBuilder(valueConditionNodeCompilerProvider, dataTypesConfigurationProvider); - conditionsEvalEngine = new CompiledConditionsEvalEngine(multiplicityEvaluator, conditionsTreeAnalyzer, this.rulesEngineOptions); + conditionsEvalEngine = new CompiledConditionsEvalEngine(conditionsTreeAnalyzer, this.rulesEngineOptions); // Add conditions compiler middleware to ensure compilation occurs before rules // engine uses the rules, while also ensuring that the compilation result is kept on diff --git a/src/Rules.Framework/Evaluation/Compiled/CompilationRulesSourceMiddleware.cs b/src/Rules.Framework/Evaluation/Compiled/CompilationRulesSourceMiddleware.cs index ffd27b82..ff12eecd 100644 --- a/src/Rules.Framework/Evaluation/Compiled/CompilationRulesSourceMiddleware.cs +++ b/src/Rules.Framework/Evaluation/Compiled/CompilationRulesSourceMiddleware.cs @@ -11,10 +11,10 @@ internal sealed class CompilationRulesSourceMiddleware rulesDataSource; public CompilationRulesSourceMiddleware( - IRuleConditionsExpressionBuilder rulesExpressionBuilder, + IRuleConditionsExpressionBuilder ruleConditionsExpressionBuilder, IRulesDataSource rulesDataSource) { - this.ruleConditionsExpressionBuilder = rulesExpressionBuilder; + this.ruleConditionsExpressionBuilder = ruleConditionsExpressionBuilder; this.rulesDataSource = rulesDataSource; } diff --git a/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs b/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs index 953864da..ab078568 100644 --- a/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs +++ b/src/Rules.Framework/Evaluation/Compiled/CompiledConditionsEvalEngine.cs @@ -7,15 +7,12 @@ namespace Rules.Framework.Evaluation.Compiled internal sealed class CompiledConditionsEvalEngine : IConditionsEvalEngine { private readonly IConditionsTreeAnalyzer conditionsTreeAnalyzer; - private readonly IMultiplicityEvaluator multiplicityEvaluator; private readonly RulesEngineOptions rulesEngineOptions; public CompiledConditionsEvalEngine( - IMultiplicityEvaluator multiplicityEvaluator, IConditionsTreeAnalyzer conditionsTreeAnalyzer, RulesEngineOptions rulesEngineOptions) { - this.multiplicityEvaluator = multiplicityEvaluator; this.conditionsTreeAnalyzer = conditionsTreeAnalyzer; this.rulesEngineOptions = rulesEngineOptions; } diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs index b0bf25c2..6d673c20 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using System.Reflection; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { @@ -13,7 +13,7 @@ internal sealed class CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder private static readonly MethodInfo endsWithMethodInfo = typeof(string) .GetMethod(nameof(string.EndsWith), new[] { typeof(string), typeof(StringComparison) }); - public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) + public Expression BuildConditionExpression(IExpressionBlockBuilder builder, BuildConditionExpressionArgs args) { if (args.DataTypeConfiguration.DataType != DataTypes.String) { diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs index c80aa48c..87beb23a 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using System.Reflection; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { @@ -13,7 +13,7 @@ internal sealed class CaseInsensitiveStartsWithOneToOneConditionExpressionBuilde private static readonly MethodInfo startsWithMethodInfo = typeof(string) .GetMethod(nameof(string.StartsWith), new[] { typeof(string), typeof(StringComparison) }); - public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) + public Expression BuildConditionExpression(IExpressionBlockBuilder builder, BuildConditionExpressionArgs args) { if (args.DataTypeConfiguration.DataType != DataTypes.String) { diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs index 57cb302b..4412c992 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilder.cs @@ -4,13 +4,13 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using System.Reflection; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class ContainsOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { private static readonly MethodInfo stringContainsMethodInfo = typeof(string).GetMethod("Contains", new[] { typeof(string) }); - public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) + public Expression BuildConditionExpression(IExpressionBlockBuilder builder, BuildConditionExpressionArgs args) { if (args.DataTypeConfiguration.DataType != DataTypes.String) { diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs index b3b8fe86..70e0ee57 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilder.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using System.Reflection; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class EndsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { @@ -13,7 +13,7 @@ internal sealed class EndsWithOneToOneConditionExpressionBuilder : IConditionExp private static readonly MethodInfo endsWithMethodInfo = typeof(string) .GetMethod(nameof(string.EndsWith), new[] { typeof(string), typeof(StringComparison) }); - public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) + public Expression BuildConditionExpression(IExpressionBlockBuilder builder, BuildConditionExpressionArgs args) { if (args.DataTypeConfiguration.DataType != DataTypes.String) { diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilder.cs index a4836d84..438c06d2 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/EqualOneToOneConditionExpressionBuilder.cs @@ -1,11 +1,11 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { using System.Linq.Expressions; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class EqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { - public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) + public Expression BuildConditionExpression(IExpressionBlockBuilder builder, BuildConditionExpressionArgs args) { return builder.Equal(args.LeftHandOperand, args.RightHandOperand); } diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs index 190a8b42..40a578c5 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilder.cs @@ -3,11 +3,11 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System; using System.Linq.Expressions; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class GreaterThanOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { - public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) + public Expression BuildConditionExpression(IExpressionBlockBuilder builder, BuildConditionExpressionArgs args) { if (!args.DataTypeConfiguration.Type.HasLanguageOperator(LanguageOperator.GreaterThan)) { diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs index 44ac560e..b658b96a 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilder.cs @@ -3,11 +3,11 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System; using System.Linq.Expressions; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class GreaterThanOrEqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { - public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) + public Expression BuildConditionExpression(IExpressionBlockBuilder builder, BuildConditionExpressionArgs args) { if (!args.DataTypeConfiguration.Type.HasLanguageOperator(LanguageOperator.GreaterThanOrEqual)) { diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilder.cs index da10c0d9..0316f700 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/IConditionExpressionBuilder.cs @@ -1,10 +1,10 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { using System.Linq.Expressions; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal interface IConditionExpressionBuilder { - Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args); + Expression BuildConditionExpression(IExpressionBlockBuilder builder, BuildConditionExpressionArgs args); } } \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilder.cs index 1d648dda..e04ab915 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilder.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq; using System.Linq.Expressions; using System.Reflection; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class InOneToManyConditionExpressionBuilder : IConditionExpressionBuilder { @@ -13,7 +13,7 @@ private static readonly MethodInfo enumerableGenericContains .GetMethods() .FirstOrDefault(m => string.Equals(m.Name, "Contains", StringComparison.Ordinal) && m.GetParameters().Length == 2); - public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) + public Expression BuildConditionExpression(IExpressionBlockBuilder builder, BuildConditionExpressionArgs args) { return builder.Call( instance: null, diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs index a164ac2c..d89e9931 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilder.cs @@ -3,11 +3,11 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System; using System.Linq.Expressions; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class LesserThanOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { - public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) + public Expression BuildConditionExpression(IExpressionBlockBuilder builder, BuildConditionExpressionArgs args) { if (!args.DataTypeConfiguration.Type.HasLanguageOperator(LanguageOperator.LessThan)) { diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs index ced17418..e1597901 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilder.cs @@ -3,11 +3,11 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System; using System.Linq.Expressions; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class LesserThanOrEqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { - public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) + public Expression BuildConditionExpression(IExpressionBlockBuilder builder, BuildConditionExpressionArgs args) { if (!args.DataTypeConfiguration.Type.HasLanguageOperator(LanguageOperator.LessThanOrEqual)) { diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs index 4f85361e..cd6f3dc4 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilder.cs @@ -4,13 +4,13 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using System.Reflection; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class NotContainsOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { private static readonly MethodInfo stringContainsMethodInfo = typeof(string).GetMethod("Contains", new[] { typeof(string) }); - public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) + public Expression BuildConditionExpression(IExpressionBlockBuilder builder, BuildConditionExpressionArgs args) { if (args.DataTypeConfiguration.DataType != DataTypes.String) { diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilder.cs index b6758df6..55b422e2 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilder.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using System.Reflection; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal class NotEndsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { @@ -13,7 +13,7 @@ internal class NotEndsWithOneToOneConditionExpressionBuilder : IConditionExpress private static readonly MethodInfo endsWithMethodInfo = typeof(string) .GetMethod(nameof(string.EndsWith), new[] { typeof(string), typeof(StringComparison) }); - public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) + public Expression BuildConditionExpression(IExpressionBlockBuilder builder, BuildConditionExpressionArgs args) { if (args.DataTypeConfiguration.DataType != DataTypes.String) { diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilder.cs index b1538850..8836e6b3 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotEqualOneToOneConditionExpressionBuilder.cs @@ -1,11 +1,11 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { using System.Linq.Expressions; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class NotEqualOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { - public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) + public Expression BuildConditionExpression(IExpressionBlockBuilder builder, BuildConditionExpressionArgs args) { return builder.NotEqual(args.LeftHandOperand, args.RightHandOperand); } diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilder.cs index b5e748b8..49696d7e 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilder.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using System.Reflection; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal class NotStartsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { @@ -13,7 +13,7 @@ internal class NotStartsWithOneToOneConditionExpressionBuilder : IConditionExpre private static readonly MethodInfo startsWithMethodInfo = typeof(string) .GetMethod(nameof(string.StartsWith), new[] { typeof(string), typeof(StringComparison) }); - public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) + public Expression BuildConditionExpression(IExpressionBlockBuilder builder, BuildConditionExpressionArgs args) { if (args.DataTypeConfiguration.DataType != DataTypes.String) { diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs index c1112ef5..7e5668a0 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilder.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders using System.Linq.Expressions; using System.Reflection; using Rules.Framework.Core; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class StartsWithOneToOneConditionExpressionBuilder : IConditionExpressionBuilder { @@ -13,7 +13,7 @@ internal sealed class StartsWithOneToOneConditionExpressionBuilder : IConditionE private static readonly MethodInfo startsWithMethodInfo = typeof(string) .GetMethod(nameof(string.StartsWith), new[] { typeof(string), typeof(StringComparison) }); - public Expression BuildConditionExpression(IImplementationExpressionBuilder builder, BuildConditionExpressionArgs args) + public Expression BuildConditionExpression(IExpressionBlockBuilder builder, BuildConditionExpressionArgs args) { if (args.DataTypeConfiguration.DataType != DataTypes.String) { diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/DefaultExpressionBuilderFactory.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/DefaultExpressionBuilderFactory.cs new file mode 100644 index 00000000..4e728397 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/DefaultExpressionBuilderFactory.cs @@ -0,0 +1,48 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders +{ + using System; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + + internal class DefaultExpressionBuilderFactory : IExpressionBuilderFactory + { + public IExpressionBlockBuilder CreateExpressionBlockBuilder( + string scopeName, + IExpressionBlockBuilder parent, + ExpressionConfiguration expressionConfiguration) + { + if (expressionConfiguration is null) + { + throw new ArgumentNullException(nameof(expressionConfiguration)); + } + + return new ExpressionBlockBuilder(scopeName, parent, this, expressionConfiguration); + } + + public IExpressionParametersBuilder CreateExpressionBuilder( + ExpressionConfiguration expressionConfiguration) + { + if (expressionConfiguration is null) + { + throw new ArgumentNullException(nameof(expressionConfiguration)); + } + + return new ExpressionBuilder(expressionConfiguration, this); + } + + public IExpressionParametersConfiguration CreateExpressionParametersConfiguration() + { + return new ExpressionParametersConfiguration(); + } + + public IExpressionSwitchBuilder CreateExpressionSwitchBuilder( + IExpressionBlockBuilder parent) + { + if (parent is null) + { + throw new ArgumentNullException(nameof(parent)); + } + + return new ExpressionSwitchBuilder(parent); + } + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ImplementationExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionBlockBuilder.cs similarity index 80% rename from src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ImplementationExpressionBuilder.cs rename to src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionBlockBuilder.cs index 1e924959..36b71e1f 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ImplementationExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionBlockBuilder.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders { using System; using System.Collections.Generic; @@ -6,32 +6,38 @@ namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine using System.Linq.Expressions; using System.Reflection; - internal sealed class ImplementationExpressionBuilder : IImplementationExpressionBuilder + internal sealed class ExpressionBlockBuilder : IExpressionBlockBuilder { private readonly ExpressionConfiguration expressionConfiguration; private readonly List expressions; + private readonly IExpressionBuilderFactory factory; private readonly Dictionary labelTargets; private readonly Dictionary variables; - public ImplementationExpressionBuilder(string scopeName, IImplementationExpressionBuilder parent, ExpressionConfiguration expressionConfiguration) + public ExpressionBlockBuilder( + string scopeName, + IExpressionBlockBuilder parent, + IExpressionBuilderFactory factory, + ExpressionConfiguration expressionConfiguration) { this.expressionConfiguration = expressionConfiguration; this.expressions = new List(); this.labelTargets = new Dictionary(StringComparer.Ordinal); this.ScopeName = scopeName; this.Parent = parent; + this.factory = factory; this.variables = new Dictionary(StringComparer.Ordinal); } - public IList Expressions => this.expressions; + public IReadOnlyList Expressions => this.expressions; - public IDictionary LabelTargets => this.labelTargets; + public IReadOnlyDictionary LabelTargets => this.labelTargets; - public IImplementationExpressionBuilder Parent { get; } + public IExpressionBlockBuilder Parent { get; } public string ScopeName { get; } - public IDictionary Variables => this.variables; + public IReadOnlyDictionary Variables => this.variables; public void AddExpression(Expression expression) { @@ -69,10 +75,10 @@ public void Assign(Expression left, Expression right) this.AddExpression(expression); } - public Expression Block(Action implementationBuilder) + public Expression Block(Action implementationBuilder) => this.Block(string.Empty, implementationBuilder); - public Expression Block(string scopeName, Action implementationBuilder) + public Expression Block(string scopeName, Action implementationBuilder) { if (implementationBuilder is null) { @@ -80,15 +86,15 @@ public Expression Block(string scopeName, Action Expression.GreaterThanOrEqual(left, right); public void If( - Func testExpressionBuilder, - Func thenExpressionBuilder) + Func testExpressionBuilder, + Func thenExpressionBuilder) => this.If(testExpressionBuilder, thenExpressionBuilder, elseExpressionBuilder: null); public void If( - Func testExpressionBuilder, - Func thenExpressionBuilder, - Func elseExpressionBuilder) + Func testExpressionBuilder, + Func thenExpressionBuilder, + Func elseExpressionBuilder) { if (testExpressionBuilder is null) { @@ -305,22 +311,25 @@ public void Return(Expression returnValueExpression) } public void Switch( - Expression switchExpressionValue, - Action switchExpressionBuilderAction) + Expression switchValueExpression, + Action expressionSwitchBuilderAction) { - if (switchExpressionValue is null) + if (switchValueExpression is null) { - throw new ArgumentNullException(nameof(switchExpressionValue)); + throw new ArgumentNullException(nameof(switchValueExpression)); } - if (switchExpressionBuilderAction is null) + if (expressionSwitchBuilderAction is null) { - throw new ArgumentNullException(nameof(switchExpressionBuilderAction)); + throw new ArgumentNullException(nameof(expressionSwitchBuilderAction)); } - var switchExpressionBuilder = new SwitchExpressionBuilder(this); - switchExpressionBuilderAction.Invoke(switchExpressionBuilder); - var expression = switchExpressionBuilder.CreateSwitchExpression(switchExpressionValue); + var expressionSwitchBuilder = this.factory.CreateExpressionSwitchBuilder(this); + expressionSwitchBuilderAction.Invoke(expressionSwitchBuilder); + var expression = Expression.Switch( + switchValueExpression, + expressionSwitchBuilder.DefaultBody, + expressionSwitchBuilder.SwitchCases.ToArray()); this.AddExpression(expression); } } diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionBuilder.cs index 34496733..ffabac06 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionBuilder.cs @@ -1,17 +1,118 @@ namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders { + using System; + using System.Collections.Generic; + using System.Linq.Expressions; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; - internal static class ExpressionBuilder + internal class ExpressionBuilder : IExpressionParametersBuilder, IExpressionReturnBuilder, IExpressionImplementationBuilder, IConfiguredExpressionBuilder { - public static INamedExpressionBuilder NewExpression(string name) + private readonly IExpressionBuilderFactory factory; + + public ExpressionBuilder(ExpressionConfiguration expressionConfiguration, IExpressionBuilderFactory factory) + { + this.ExpressionConfiguration = expressionConfiguration; + this.factory = factory; + } + + public static IExpressionBuilderFactory ExpressionBuilderFactory { get; set; } = new DefaultExpressionBuilderFactory(); + + public ExpressionConfiguration ExpressionConfiguration { get; } + + public static IExpressionParametersBuilder NewExpression(string name) { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("A non-null, empty, or white-space expression name must be provided.", nameof(name)); + } + var expressionConfiguration = new ExpressionConfiguration { ExpressionName = name, }; - return new NamedExpressionBuilder(expressionConfiguration); + return ExpressionBuilderFactory.CreateExpressionBuilder(expressionConfiguration); + } + + public ExpressionResult Build() + { + var variableExpressionsCopy = new List(this.ExpressionConfiguration.Variables.Values); + var bodyBlockExpressionsCopy = new List(this.ExpressionConfiguration.Expressions) + { + Expression.Label( + this.ExpressionConfiguration.ReturnLabelTarget, + Expression.Constant(this.ExpressionConfiguration.ReturnDefaultValue)), + }; + + var implementationExpression = Expression.Block(variables: variableExpressionsCopy, expressions: bodyBlockExpressionsCopy); + + return new ExpressionResult( + this.ExpressionConfiguration.ExpressionName, + implementationExpression, + this.ExpressionConfiguration.Parameters.Values, + this.ExpressionConfiguration.ReturnType); + } + + public IExpressionImplementationBuilder HavingReturn(Type type, object defaultValue) + { + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } + + this.ExpressionConfiguration.ReturnType = type; + this.ExpressionConfiguration.ReturnDefaultValue = defaultValue; + this.ExpressionConfiguration.ReturnLabelTarget + = Expression.Label(type, $"{this.ExpressionConfiguration.ExpressionName}_ReturnLabel"); + + return this; + } + + public IExpressionImplementationBuilder HavingReturn(object defaultValue) + => this.HavingReturn(typeof(T), defaultValue); + + public IConfiguredExpressionBuilder SetImplementation( + Action builder) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + var implementationExpressionBuilder = this.factory.CreateExpressionBlockBuilder( + scopeName: string.Empty, + parent: null, + expressionConfiguration: this.ExpressionConfiguration); + builder.Invoke(implementationExpressionBuilder); + + this.ExpressionConfiguration.LabelTargets = implementationExpressionBuilder.LabelTargets; + this.ExpressionConfiguration.Variables = implementationExpressionBuilder.Variables; + this.ExpressionConfiguration.Expressions = implementationExpressionBuilder.Expressions; + + return this; + } + + public IExpressionReturnBuilder WithoutParameters() + { + this.ExpressionConfiguration.Parameters = new Dictionary(StringComparer.Ordinal); + + return this; + } + + public IExpressionReturnBuilder WithParameters( + Action parametersConfigurationAction) + { + if (parametersConfigurationAction is null) + { + throw new ArgumentNullException(nameof(parametersConfigurationAction)); + } + + var expressionBuilderParametersConfiguration = this.factory.CreateExpressionParametersConfiguration(); + parametersConfigurationAction.Invoke(expressionBuilderParametersConfiguration); + + this.ExpressionConfiguration.Parameters = expressionBuilderParametersConfiguration.Parameters; + + return this; } } } \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ExpressionConfiguration.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionConfiguration.cs similarity index 65% rename from src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ExpressionConfiguration.cs rename to src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionConfiguration.cs index 7a143ef9..42b1d401 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ExpressionConfiguration.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionConfiguration.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders { using System; using System.Collections.Generic; @@ -10,9 +10,9 @@ internal sealed class ExpressionConfiguration public IEnumerable Expressions { get; set; } - public IDictionary LabelTargets { get; set; } + public IReadOnlyDictionary LabelTargets { get; set; } - public IDictionary Parameters { get; set; } + public IReadOnlyDictionary Parameters { get; set; } public object ReturnDefaultValue { get; set; } @@ -20,6 +20,6 @@ internal sealed class ExpressionConfiguration public Type ReturnType { get; set; } - public IDictionary Variables { get; set; } + public IReadOnlyDictionary Variables { get; set; } } } \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ExpressionParametersConfiguration.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionParametersConfiguration.cs similarity index 70% rename from src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ExpressionParametersConfiguration.cs rename to src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionParametersConfiguration.cs index c64ab04e..88e27edf 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ExpressionParametersConfiguration.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionParametersConfiguration.cs @@ -1,4 +1,4 @@ -namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders { using System; using System.Collections.Generic; @@ -6,22 +6,24 @@ namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine internal sealed class ExpressionParametersConfiguration : IExpressionParametersConfiguration { + private readonly Dictionary parameters; + public ExpressionParametersConfiguration() { - this.Parameters = new Dictionary(StringComparer.Ordinal); + this.parameters = new Dictionary(StringComparer.Ordinal); } - public IDictionary Parameters { get; } + public IReadOnlyDictionary Parameters => parameters; public ParameterExpression CreateParameter(string name, Type type) { - if (this.Parameters.ContainsKey(name)) + if (this.parameters.ContainsKey(name)) { throw new InvalidOperationException($"A parameter for name '{name}' was already added."); } var parameterExpression = Expression.Parameter(type, name); - this.Parameters.Add(name, parameterExpression); + this.parameters.Add(name, parameterExpression); return parameterExpression; } diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/SwitchExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionSwitchBuilder.cs similarity index 58% rename from src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/SwitchExpressionBuilder.cs rename to src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionSwitchBuilder.cs index 5632e130..42b42e6e 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/SwitchExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionSwitchBuilder.cs @@ -1,25 +1,29 @@ -namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders { using System; using System.Collections.Generic; using System.Linq.Expressions; - internal sealed class SwitchExpressionBuilder : ISwitchExpressionBuilder + internal sealed class ExpressionSwitchBuilder : IExpressionSwitchBuilder { - private readonly IImplementationExpressionBuilder implementationExpressionBuilder; + private readonly IExpressionBlockBuilder implementationExpressionBuilder; private readonly List switchCases; - private Expression defaultExpression; + private Expression defaultBody; - public SwitchExpressionBuilder(IImplementationExpressionBuilder implementationExpressionBuilder) + public ExpressionSwitchBuilder(IExpressionBlockBuilder implementationExpressionBuilder) { - this.defaultExpression = null; + this.defaultBody = null; this.implementationExpressionBuilder = implementationExpressionBuilder; this.switchCases = new List(); } - public ISwitchExpressionBuilder Case( + public Expression DefaultBody => this.defaultBody; + + public IEnumerable SwitchCases => this.switchCases.AsReadOnly(); + + public IExpressionSwitchBuilder Case( Expression caseExpression, - Func caseBodyExpressionBuilder) + Func caseBodyExpressionBuilder) { if (caseExpression is null) { @@ -29,9 +33,9 @@ public ISwitchExpressionBuilder Case( return this.Case(new[] { caseExpression }, caseBodyExpressionBuilder); } - public ISwitchExpressionBuilder Case( + public IExpressionSwitchBuilder Case( IEnumerable caseExpressions, - Func caseBodyExpressionBuilder) + Func caseBodyExpressionBuilder) { if (caseExpressions is null) { @@ -49,17 +53,14 @@ public ISwitchExpressionBuilder Case( return this; } - public SwitchExpression CreateSwitchExpression(Expression switchValueExpression) - => Expression.Switch(switchValueExpression, this.defaultExpression, this.switchCases.ToArray()); - - public void Default(Func defaultBodyExpressionBuilder) + public void Default(Func defaultBodyExpressionBuilder) { if (defaultBodyExpressionBuilder is null) { throw new ArgumentNullException(nameof(defaultBodyExpressionBuilder)); } - defaultExpression = defaultBodyExpressionBuilder.Invoke(this.implementationExpressionBuilder); + defaultBody = defaultBodyExpressionBuilder.Invoke(this.implementationExpressionBuilder); } } } \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IImplementationExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/IExpressionBlockBuilder.cs similarity index 67% rename from src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IImplementationExpressionBuilder.cs rename to src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/IExpressionBlockBuilder.cs index bd2385d4..40b19363 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IImplementationExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/IExpressionBlockBuilder.cs @@ -1,14 +1,20 @@ -namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders { using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; - internal interface IImplementationExpressionBuilder + internal interface IExpressionBlockBuilder { + IReadOnlyList Expressions { get; } + + IReadOnlyDictionary LabelTargets { get; } + string ScopeName { get; } + IReadOnlyDictionary Variables { get; } + void AddExpression(Expression expression); Expression AndAlso(Expression left, Expression right); @@ -17,9 +23,9 @@ internal interface IImplementationExpressionBuilder void Assign(Expression left, Expression right); - Expression Block(Action blcokImplementationBuilderAction); + Expression Block(Action blcokImplementationBuilderAction); - Expression Block(string scopeName, Action blockImplementationBuilderAction); + Expression Block(string scopeName, Action blockImplementationBuilderAction); Expression Call(Expression instance, MethodInfo method); @@ -56,13 +62,13 @@ internal interface IImplementationExpressionBuilder Expression GreaterThanOrEqual(Expression left, Expression right); void If( - Func testExpressionBuilderFunc, - Func thenExpressionBuilderFunc); + Func testExpressionBuilderFunc, + Func thenExpressionBuilderFunc); void If( - Func testExpressionBuilderFunc, - Func thenExpressionBuilderFunc, - Func elseExpressionBuilderFunc); + Func testExpressionBuilderFunc, + Func thenExpressionBuilderFunc, + Func elseExpressionBuilderFunc); void Label(LabelTarget labelTarget); @@ -81,7 +87,7 @@ void If( void Return(Expression returnValueExpression); void Switch( - Expression switchExpressionValue, - Action switchExpressionBuilderAction); + Expression expressionSwitchValue, + Action expressionSwitchBuilderAction); } } \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/IExpressionBuilderFactory.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/IExpressionBuilderFactory.cs new file mode 100644 index 00000000..b044c855 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/IExpressionBuilderFactory.cs @@ -0,0 +1,18 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders +{ + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + + internal interface IExpressionBuilderFactory + { + IExpressionBlockBuilder CreateExpressionBlockBuilder( + string scopeName, + IExpressionBlockBuilder parent, + ExpressionConfiguration expressionConfiguration); + + IExpressionParametersBuilder CreateExpressionBuilder( + ExpressionConfiguration expressionConfiguration); + IExpressionParametersConfiguration CreateExpressionParametersConfiguration(); + IExpressionSwitchBuilder CreateExpressionSwitchBuilder( + IExpressionBlockBuilder parent); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IExpressionParametersConfiguration.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/IExpressionParametersConfiguration.cs similarity index 73% rename from src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IExpressionParametersConfiguration.cs rename to src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/IExpressionParametersConfiguration.cs index ef7aca45..25667e18 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IExpressionParametersConfiguration.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/IExpressionParametersConfiguration.cs @@ -1,10 +1,13 @@ -namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders { using System; + using System.Collections.Generic; using System.Linq.Expressions; internal interface IExpressionParametersConfiguration { + IReadOnlyDictionary Parameters { get; } + ParameterExpression CreateParameter(string name); ParameterExpression CreateParameter(string name, Type type); diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/IExpressionSwitchBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/IExpressionSwitchBuilder.cs new file mode 100644 index 00000000..7922d4dc --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/IExpressionSwitchBuilder.cs @@ -0,0 +1,23 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + + internal interface IExpressionSwitchBuilder + { + Expression DefaultBody { get; } + + IEnumerable SwitchCases { get; } + + IExpressionSwitchBuilder Case( + Expression caseExpression, + Func caseBodyExpressionBuilder); + + IExpressionSwitchBuilder Case( + IEnumerable caseExpressions, + Func caseBodyExpressionBuilder); + + void Default(Func defaultBodyExpressionBuilder); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ConfiguredExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ConfiguredExpressionBuilder.cs deleted file mode 100644 index ab1ea0a0..00000000 --- a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ConfiguredExpressionBuilder.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine -{ - using System.Collections.Generic; - using System.Linq.Expressions; - - internal sealed class ConfiguredExpressionBuilder : IConfiguredExpressionBuilder - { - private readonly ExpressionConfiguration expressionConfiguration; - - public ConfiguredExpressionBuilder(ExpressionConfiguration expressionConfiguration) - { - this.expressionConfiguration = expressionConfiguration; - } - - public ExpressionResult Build() - { - var variableExpressionsCopy = new List(this.expressionConfiguration.Variables.Values); - var bodyBlockExpressionsCopy = new List(this.expressionConfiguration.Expressions) - { - Expression.Label( - this.expressionConfiguration.ReturnLabelTarget, - Expression.Constant(this.expressionConfiguration.ReturnDefaultValue)), - }; - - var implementationExpression = Expression.Block(variables: variableExpressionsCopy, expressions: bodyBlockExpressionsCopy); - - return new ExpressionResult( - this.expressionConfiguration.ExpressionName, - implementationExpression, - this.expressionConfiguration.Parameters.Values, - this.expressionConfiguration.ReturnType); - } - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ConfiguredSignatureExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ConfiguredSignatureExpressionBuilder.cs deleted file mode 100644 index f7ded5ac..00000000 --- a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ConfiguredSignatureExpressionBuilder.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine -{ - using System; - - internal sealed class ConfiguredSignatureExpressionBuilder : IConfiguredSignatureExpressionBuilder - { - private readonly ExpressionConfiguration expressionConfiguration; - - public ConfiguredSignatureExpressionBuilder(ExpressionConfiguration expressionConfiguration) - { - this.expressionConfiguration = expressionConfiguration; - } - - public IConfiguredExpressionBuilder SetImplementation( - Action builder) - { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } - - var implementationExpressionBuilder = new ImplementationExpressionBuilder( - scopeName: string.Empty, - parent: null, - expressionConfiguration: this.expressionConfiguration); - builder.Invoke(implementationExpressionBuilder); - - this.expressionConfiguration.LabelTargets = implementationExpressionBuilder.LabelTargets; - this.expressionConfiguration.Variables = implementationExpressionBuilder.Variables; - this.expressionConfiguration.Expressions = implementationExpressionBuilder.Expressions; - - return new ConfiguredExpressionBuilder(expressionConfiguration); - } - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IConfiguredSignatureExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IConfiguredSignatureExpressionBuilder.cs deleted file mode 100644 index 69d13ac1..00000000 --- a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IConfiguredSignatureExpressionBuilder.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine -{ - using System; - - internal interface IConfiguredSignatureExpressionBuilder - { - IConfiguredExpressionBuilder SetImplementation(Action builder); - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IExpressionImplementationBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IExpressionImplementationBuilder.cs new file mode 100644 index 00000000..b1ff3e51 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IExpressionImplementationBuilder.cs @@ -0,0 +1,10 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +{ + using System; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + + internal interface IExpressionImplementationBuilder + { + IConfiguredExpressionBuilder SetImplementation(Action builder); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IExpressionParametersBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IExpressionParametersBuilder.cs new file mode 100644 index 00000000..c4067035 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IExpressionParametersBuilder.cs @@ -0,0 +1,12 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +{ + using System; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + + internal interface IExpressionParametersBuilder + { + IExpressionReturnBuilder WithoutParameters(); + + IExpressionReturnBuilder WithParameters(Action parametersConfigurationAction); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IExpressionReturnBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IExpressionReturnBuilder.cs new file mode 100644 index 00000000..58efb6e6 --- /dev/null +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IExpressionReturnBuilder.cs @@ -0,0 +1,11 @@ +namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine +{ + using System; + + internal interface IExpressionReturnBuilder + { + IExpressionImplementationBuilder HavingReturn(object defaultValue); + + IExpressionImplementationBuilder HavingReturn(Type type, object defaultValue); + } +} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/INamedExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/INamedExpressionBuilder.cs deleted file mode 100644 index 97eae4f4..00000000 --- a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/INamedExpressionBuilder.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine -{ - using System; - - internal interface INamedExpressionBuilder - { - IParameterizedExpressionBuilder WithoutParameters(); - - IParameterizedExpressionBuilder WithParameters(Action parametersConfigurationAction); - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IParameterizedExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IParameterizedExpressionBuilder.cs deleted file mode 100644 index 69e78584..00000000 --- a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/IParameterizedExpressionBuilder.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine -{ - using System; - - internal interface IParameterizedExpressionBuilder - { - IConfiguredSignatureExpressionBuilder HavingReturn(object defaultValue); - - IConfiguredSignatureExpressionBuilder HavingReturn(Type type, object defaultValue); - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ISwitchExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ISwitchExpressionBuilder.cs deleted file mode 100644 index 084264f7..00000000 --- a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ISwitchExpressionBuilder.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine -{ - using System; - using System.Collections.Generic; - using System.Linq.Expressions; - - internal interface ISwitchExpressionBuilder - { - ISwitchExpressionBuilder Case( - Expression caseExpression, - Func caseBodyExpressionBuilder); - - ISwitchExpressionBuilder Case( - IEnumerable caseExpressions, - Func caseBodyExpressionBuilder); - - void Default(Func defaultBodyExpressionBuilder); - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/NamedExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/NamedExpressionBuilder.cs deleted file mode 100644 index 8b8a32a2..00000000 --- a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/NamedExpressionBuilder.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine -{ - using System; - using System.Collections.Generic; - using System.Linq.Expressions; - - internal sealed class NamedExpressionBuilder : INamedExpressionBuilder - { - private readonly ExpressionConfiguration expressionConfiguration; - - public NamedExpressionBuilder(ExpressionConfiguration expressionConfiguration) - { - this.expressionConfiguration = expressionConfiguration; - } - - public IParameterizedExpressionBuilder WithoutParameters() - { - this.expressionConfiguration.Parameters = new Dictionary(StringComparer.Ordinal); - - return new ParameterizedExpressionBuilder(this.expressionConfiguration); - } - - public IParameterizedExpressionBuilder WithParameters( - Action parametersConfigurationAction) - { - if (parametersConfigurationAction is null) - { - throw new ArgumentNullException(nameof(parametersConfigurationAction)); - } - - var expressionBuilderParametersConfiguration = new ExpressionParametersConfiguration(); - parametersConfigurationAction.Invoke(expressionBuilderParametersConfiguration); - - this.expressionConfiguration.Parameters = expressionBuilderParametersConfiguration.Parameters; - - return new ParameterizedExpressionBuilder(this.expressionConfiguration); - } - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ParameterizedExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ParameterizedExpressionBuilder.cs deleted file mode 100644 index 86930183..00000000 --- a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/StateMachine/ParameterizedExpressionBuilder.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine -{ - using System; - using System.Linq.Expressions; - - internal sealed class ParameterizedExpressionBuilder : IParameterizedExpressionBuilder - { - private readonly ExpressionConfiguration expressionConfiguration; - - public ParameterizedExpressionBuilder(ExpressionConfiguration expressionConfiguration) - { - this.expressionConfiguration = expressionConfiguration; - } - - public IConfiguredSignatureExpressionBuilder HavingReturn(Type type, object defaultValue) - { - if (type is null) - { - throw new ArgumentNullException(nameof(type)); - } - - this.expressionConfiguration.ReturnType = type; - this.expressionConfiguration.ReturnDefaultValue = defaultValue; - this.expressionConfiguration.ReturnLabelTarget - = Expression.Label(type, $"{this.expressionConfiguration.ExpressionName}_ReturnLabel"); - - return new ConfiguredSignatureExpressionBuilder(expressionConfiguration); - } - - public IConfiguredSignatureExpressionBuilder HavingReturn(object defaultValue) - => this.HavingReturn(typeof(T), defaultValue); - } -} \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeExpressionBuilder.cs index 90e2076c..fb120418 100644 --- a/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/IValueConditionNodeExpressionBuilder.cs @@ -1,11 +1,11 @@ namespace Rules.Framework.Evaluation.Compiled { - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal interface IValueConditionNodeExpressionBuilder { void Build( - IImplementationExpressionBuilder builder, + IExpressionBlockBuilder builder, BuildValueConditionNodeExpressionArgs args); } } \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilder.cs index 503a9527..3303a967 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilder.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Evaluation.Compiled using System.Collections.Generic; using System.Reflection; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class ManyToManyValueConditionNodeExpressionBuilder : IValueConditionNodeExpressionBuilder { @@ -22,7 +22,7 @@ public ManyToManyValueConditionNodeExpressionBuilder(IConditionExpressionBuilder } public void Build( - IImplementationExpressionBuilder builder, + IExpressionBlockBuilder builder, BuildValueConditionNodeExpressionArgs args) { var enumerableOfDataType = enumerableType.MakeGenericType(args.DataTypeConfiguration.Type); diff --git a/src/Rules.Framework/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilder.cs index ec9e1eb0..5e69ef31 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilder.cs @@ -4,7 +4,7 @@ namespace Rules.Framework.Evaluation.Compiled using System.Collections.Generic; using System.Reflection; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class ManyToOneValueConditionNodeExpressionBuilder : IValueConditionNodeExpressionBuilder { @@ -22,7 +22,7 @@ public ManyToOneValueConditionNodeExpressionBuilder(IConditionExpressionBuilderP } public void Build( - IImplementationExpressionBuilder builder, + IExpressionBlockBuilder builder, BuildValueConditionNodeExpressionArgs args) { var enumerableOfDataType = enumerableType.MakeGenericType(args.DataTypeConfiguration.Type); diff --git a/src/Rules.Framework/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilder.cs index 7d02f1c4..10961e95 100644 --- a/src/Rules.Framework/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilder.cs @@ -7,7 +7,7 @@ namespace Rules.Framework.Evaluation.Compiled using System.Reflection; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class OneToManyValueConditionNodeExpressionBuilder : IValueConditionNodeExpressionBuilder { @@ -25,7 +25,7 @@ public OneToManyValueConditionNodeExpressionBuilder(IConditionExpressionBuilderP } public void Build( - IImplementationExpressionBuilder builder, + IExpressionBlockBuilder builder, BuildValueConditionNodeExpressionArgs args) { var enumerableOfDataType = enumerableType.MakeGenericType(args.DataTypeConfiguration.Type); diff --git a/src/Rules.Framework/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilder.cs index fc86d09a..870929b4 100644 --- a/src/Rules.Framework/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilder.cs @@ -7,7 +7,7 @@ namespace Rules.Framework.Evaluation.Compiled using System.Reflection; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; internal sealed class OneToOneValueConditionNodeExpressionBuilder : IValueConditionNodeExpressionBuilder { @@ -25,7 +25,7 @@ public OneToOneValueConditionNodeExpressionBuilder(IConditionExpressionBuilderPr } public void Build( - IImplementationExpressionBuilder builder, + IExpressionBlockBuilder builder, BuildValueConditionNodeExpressionArgs args) { var coalescedLeftOperandExpression = builder.CreateVariable("coalescedLeftOperand"); diff --git a/src/Rules.Framework/Evaluation/Compiled/RuleConditionsExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/RuleConditionsExpressionBuilder.cs index 5b731a1d..76b1ce36 100644 --- a/src/Rules.Framework/Evaluation/Compiled/RuleConditionsExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/RuleConditionsExpressionBuilder.cs @@ -8,7 +8,6 @@ namespace Rules.Framework.Evaluation.Compiled using Rules.Framework.Core; using Rules.Framework.Core.ConditionNodes; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; internal sealed class RuleConditionsExpressionBuilder : IRuleConditionsExpressionBuilder { @@ -60,7 +59,7 @@ public Expression, bool>> BuildExpression parameters: expressionResult.Parameters); } - private static void BuildExpressionForBehaviorOnNullLeftOperand(IImplementationExpressionBuilder builder) + private static void BuildExpressionForBehaviorOnNullLeftOperand(IExpressionBlockBuilder builder) { var leftOperandVariableExpression = builder.GetVariable("leftOperand"); var resultVariableExpression = builder.GetVariable("result"); @@ -88,7 +87,7 @@ private static void BuildExpressionForBehaviorOnNullLeftOperand(IImplementationE })); } - private void BuildExpression(IConditionNode conditionNode, IImplementationExpressionBuilder builder) + private void BuildExpression(IConditionNode conditionNode, IExpressionBlockBuilder builder) { switch (conditionNode) { @@ -112,7 +111,7 @@ private void BuildExpression(IConditionNode conditionNode, IImpl { LogicalOperators.And => builder.AndAlso(conditionExpressions), LogicalOperators.Or => builder.OrElse(conditionExpressions), - _ => throw new NotSupportedException() + _ => throw new NotSupportedException($"Unsupported logical operator on composed condition node: '{conditionNode.LogicalOperator}'.") }; var composedResultVariableExpression = builder.GetVariable("result"); builder.Assign(composedResultVariableExpression, conditionExpression); @@ -147,7 +146,7 @@ private void BuildExpression(IConditionNode conditionNode, IImpl } private void BuildFetchAndSwitchOverMultiplicity( - IImplementationExpressionBuilder builder, + IExpressionBlockBuilder builder, ValueConditionNode valueConditionNode) { var operatorConstantExpression = builder.Constant(valueConditionNode.Operator); From 00b42a382ebef85123ea628762804a0a2ffceca7 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 19 Feb 2023 00:27:38 +0000 Subject: [PATCH 15/54] docs: update expression builder flow documentation --- docs/expression-builder-fluent-api.drawio | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/expression-builder-fluent-api.drawio b/docs/expression-builder-fluent-api.drawio index 5ceda2cb..a9bdf650 100644 --- a/docs/expression-builder-fluent-api.drawio +++ b/docs/expression-builder-fluent-api.drawio @@ -1 +1 @@ -7V3dd+K2Ev9rcs7tQ3KsD8v2Y0I22Z52tzlNT3d737xgwI3BrDH5uH/9lcFyLMlg4QBDiPqwxbIZB83Mb0ajGc0Z6U2eb7NwNv6SDqLkDDuD5zNyfYYxYq7P/1eMvJQjhJYjoywerMac14H7+H9R+aAYXcSDaF6OrYbyNE3yeCYP9tPpNOrn0liYZemT/NgwTQbSwCwcRdKfUQzc98Mk0h77Fg/y8WrUd2tPf47i0Vi8GTnlnUkoHi4H5uNwkD7VhsinM9LL0jRffZo896KkmD15Xm7W3K3+sCya5iZfoF/i7P7xZ3T9Gf37lMXp5K+n7+d4ReUxTBblD/4aTqIBH/r0PMui+TxOp1eLOBlEWfkr8hcxNVm6mA6igrpzRq6exnEe3c/CfnH3iUsDHxvnk4RfIf5x/hDl/XF50V9kj9FNnOfxdFQO/RuPRkvCfF6uhuk0vwkncVJIzWXWL0j38zl//XW44HPN/5jVQ/fpIlu+cJznXCCwSy75P3wKin+KB+YXozTlhMNZPL/op5Pljf58+ejNcPUK/lF6iYuv1NesxBIxfq1Pe8mJxyjLo+faUMmG2yidRHnG3+OUd1124a6+U2rFOXJKRjy9yhh2SsLjmnx5QSnapViPKuKvnOcfSuZvIQiuJgh3YcZFgU9CMe+9dDqMR4sszLlA7FASTpzTnifxmQhUq7EZMUdns+/sic1MY7PGzGjAAbC8nKZT/r+rNMvH6SidhsnvaToTGhvl+Us5X+EiT2UuyyJxzGyOpoPLwlDw63QWTQsJzcOseoSuHqlfzpd/5F2UxUv9KER9BWQ+v8m/O4ryNTeXwDcop2itfM3FJLSg9upVG54rwaLg6EZprYsjLUUvixKu7I+yGWySx5LcXRrzn1FJPvMlyec8kCmUc7j6Ut1qKXSw00KonG+VEGdp+FJ7bFY8MNf0p5qE7irlWZU6AZVyDVVKiB+ETlEhWqUuUKqQMFUqgmSloogcmVIFmlJ9i/l0YqfulahaliR8VRBZD6TugSBHdjUR84XzWZdQhzY4IWxPTojwgixkHgFkRs9x/r2coOLzP7Xx6+fajesXcTHlQrD8ygV2xfU/9Zuv31teiS92R+fAFJ3hwBnJeqa58KbYjNwWQltjc/N7AiS/hrmKXq8I7hTVkYAvq/hHpPgXnlvTfcdM91Fd8dHeFN/ULQNc6WCirFA82tEr81oI7Ujz+RpKeQ86gOrrkcbKleOybSOOe/UCiWKdkAcdbxQrDmsKjsAU7N8xE+oP4pl5ivCLYPv2y+YWQuDLZkQ1rfoaPUnYyi/+M+W4+4uubnb9rCMnFZL7ulXjwi+g9TC+2KBZWtL7eDQNc35hreo+ZcMnsmwcgVXVg9Gfw8eCO9j5M+ISoe/dWa1v4CxTIq1HETXzrcd0Ah6TsCftLpMH5zK5jMnyT5QNAlOXyXNaCMG7TPpWg1Wrd6hWnqlaMTi1Yko+SOB30yofbaYDrlRYD/TeR8UP+HUyS6IJ525zIpF1RprcTN9R8sUI9YCdEQGSFjXfN2oyQ9QUFCFQM3DlrSqCFKk23lpzghZK8MCph0Wllb1dzR9sNe/iQAPZw67msR7NsyD7/kC2qltoBVkCB7KvhQsiJ6zrNibCtIUSPMo2xE+zKMyLeOnfYRaHP7g+Wd/UADQRUZiNRMUPnGuqx8nUVQdL+B9x9YMbTzYqPu3Tqp66AGAqZy1UBV5gNQ7YRnQ+ltn0Ac2mg2X8qwoftjablZKsowRuNolN3zsJxfJNFQswVIpwEMjqILI4tlYs6lPZQjkKJXjF0lf9lwN5uW/dUSNvhHqK1IgID5g7Suwi/kOBJoFcxFNP3m4lRKFhDJpeIHsjJFCiXPCguWER/3v4Y3kQxV8rblnsNMFOF8nYSSh0yguxtbUfCzshHU6XyOLv4q4VXNQniiLhY8NOPURylaT9B4uUhl6mEuEOCDBSUltS+7GQErDgDbmevI1OWdetIg+1UNpRyRuiim8jIHqvNW9UX9Yv9xcsypqgrJqD7WL4wguqL+Y3hGhsKkZH1quFVvCpGFQ/H83a13doX4mhfRXQDZIlrGZi+B0j356aiKESAl+HUHse3SmoFTVWKwqnVlSJaFK3Y00LIy2E4NVKT4G5Xabfz8SRC2e2CngbhyRQHBLPgU7Ap3oEZ8Xixyp/zXJ4q3PSFHhg4ohnMBa7NqZzCsbR3OcEzGPylWMCWdeQTkBbCIEbR1FNYtXqg6gVZKgUKZWarGshNcJuCyV4xcKaYt2p/uYZ7hU8e5lZv8QwFMaIWvx5SN/zKQkI/mP42H94uGXfs+dhRkbnOoD2wuJblp8GfiZTEoZdcWYTGDt1tT2IPXyPNqdhe26ThsDszrnyVhfDXkeTw4IWSoc0OY3z3JA3O5/HI5sta1i85agpL+SQW2yNLAVKlz1lNALNSBXrvyqs0rV4nzktlMDRSN8jvORSlcxTC0dGcER9ORuk6l0ABkZA21OnDEaQR/QzqggY6wpGniqqKiVwMNJTp3vplIvR1KbDG6bDKxsCuKmw+aBoBHQA5CmjEeAZi3x5pZTYdK5w9EkLJXA00ncrORoVysgHe+Oo/1CcbmRhySh+JBcXUATtJEG1czplXILcs/CU7nCEdS2E8d0WSuC41NCR6NNklr9YMDICI4zlTSlEoYPZDX1mLBi9NZoNGc5GSkkzDnBHNKpOe1xHCR6NGsoLfi5Cu7VmGD9SsvSYE4CHsxFQ4cBJ4xFgFjEibE1tyvaNHp0WSvB4pMc/V0mm9nyFDvCElFZCCEEHlBraxVhweis4QZ5hoCYx087OElaLHI7PWVpX5FBLOrO4ZIBLjtp31AHHJT1caHHprbgEekqmcoS/63QNKaGghRI4LmE9HLrCJXuy9FYlV76y/cYwMCphPVZoUemNqCSUBQaVlHZMxO2KShi1UIJHJT1V8jYtOGSTk8zgKJA5XPWFhYMjmym5eziCTJV0FBFzxWZKh8VbCyV4ONLDor8OLRKZIBFCjpIBII5fgIMimye5eyiCTJSs9nGrnZTOUOS3UIKHIj0IWoa3LRoZRY+UXQx6wHMTmhlq8yR3j0aQiZKYyAaPsa5bbsRroQSPRnro82ta/ASbCLCFh6R0gahONgfDJGJzJHePSZBJkliJHfmdPSSKWiiBY1JD36c/sk/J3EayDbMkiZx4xsBL/kWrWwtHO6yyhcySxIG8h1u1b9kejkgLJXg40kPZf0b5IrNV/4aFbVjmMHPBvSMbyt49HB1T1b+Hupa2qVX/GiV4ONJD2fdPcQEnFo6MvCOlW84hG0g3M9RGs3ePRpDRbCpaBVSFHx3ByKWbCcFjkR7LFlhke57vELJcOeZ9jpyGNqP7anrezHkb9G4ArY1Y1A5akEFvqlYLdI0vue5mQvCg1XA4QGiDS6b7b8pxfxyLoJdzDc3orqNhuEhspZDhCp0Shafg2WZUjwEfxL4srYmwMCXR5dhNXPyAd2uDPEMbRCETZF0s+zm0q+PM2GZC4DZIdGi14n1Y8YbsYxn4suX0FArmNSl4MyF46dYj5la6jaR7Y/PV1tYDGLLmylM6cRAPd5Rv32+htLMurcreE6Weog5v69LarBxAmw8nqhxiedmuHHC6cY5cpbisa6p9dfjZOkLw2A90AMn7F+/m6TTFfshDStRNM1J1Tt66hairnJl0bMfcUpMjLKogQz2gMA5nxf1CwPIwL+4OuWT20iTNll8jzvK/pVBm6UNUuzMclnfWypH5Yh8rncldt+kMftIQSyb7iiU3tI+0kNHdItIGi7hRliEggyhtaagoMtoWMShrIQQPGSanS2yCjJXEQYIGE0tMESD0vAOCxk/66FzFk4F/99/+jzif/D3u/dbQFOpbzPFgIZ3IMt8w0acVANZ42cDxtezVm34JET1E/LeRvUDxMWC4V5C4Y5KEDv/rNeggYI89df+6G9YzH0t0II/3bZxSoLjXO5PaTXjevs7BhxNbpV1V0LF7B6Gb6exRbPlllqZ5/XFu6cZf0kFUPPF/ \ No newline at end of file +7V1bd+I4Ev41OWf3ITnWxbL9mJBOes5Oz+RM5kz37JsDAjwxmDYml/31K4PlIMnBwiEUIeqHNJZNAVLVp9KnKtUJ6U2ervN4Nv6WDXh6gr3B0wm5PMEYMT8U/5Utz1ULoVXLKE8GqzbvpeE2+R+vHpSti2TA51XbqqnIsrRIZmpjP5tOeb9Q2uI8zx7Vx4ZZOlAaZvGIK1+jbLjtxyk3HvueDIrxqjX0157+ypPRWH4y8qo7k1g+XDXMx/Ege1xrIl9OSC/PsmL1avLU42nZe2q/XL1yt/5iOZ8WNm+g35L89uEnv/yK/nnMk2zy5+OPU7yS8hCni+oHf3ma5Xw+T7KpaL+J83jCC57PxcXFIkkHPK9+TfEsuyjPFtMBLz/FOyEXj+Ok4LezuF/efRRaIdrGxSQVV0i8nN/zoj+uLvqL/IFfJUWRTEdV0z/JaLQULPrnYphNi6t4kqSl9pzn/VJ0vyi/y2W8EH0uvszqodtskS8/cFwUQjGwT87FH9EV5Z/ygfnZKMuE4HiWzM/62WR5oz9fPno1XH2EeKl8iI8v9I9ZqSdi5XWSpr0szfJlJ5BBzMNhv/yBRZ7d87U7rB/yu6G4Yw5YNYYPPC/401pTNYDXPBOdn4tv5lV3fXbmr95T2dMp8qohfHzRTuxVgsdrmhlElVFUBjGqhb/ojHhRqc0WKuQbKqRoTS+bDpPRIo+LpUrtTHc+kG4Mh5z1G3VjEER3nrcb3QgCRTOIRNA1xUDMMxUj9N5JMZihGMbw84EA2+pymk3FfxdZXoyzUTaN01+zbCZRgRfFc9XD8aLIVL1QleiQFYNPB+flpCSusxmfLlUizutH6OqR9cv58kve8DxZWlRpHCuwDMVN8d4RL165uQTXQdVFr+rXXHZCywyx+qgNz1XwUo7oRm1dV0daqV7OUwEPD+qU26SPlbibLBE/o9Z8FiqaL8ZAlVD14epN6zOkJgd7LYKq/tYFiSGNn9cem5UPzA37qTuhu0kFzqSOwKR8S5OS6gdhU1SqVmULlGoibI2KINWoKCIHZlSRYVTfE9GdqverW1maihUIPyKfZQceCPJU5xSxULqr6xrq0QYnhL2TEyK9IAeZBwCZ/CkpflQdVL7+e6398mntxuWzvJgKJVi+5Qz78vrv9Zsv71teyTd2R+fIFp3hwBmpdma48LbYjPwWQVtjc/PnREj9GOZrdr0SuFNURxK+nOEfkOGfBf6a7Xt2to/WDR+9m+HbumWAKx1MtBVKQDt6ZUGLoB1ZvlhDaZ+D9mD6LazmH7xY5FPHaB4Co0m0uQwF0HymXJ+4ieMAJo73d+MkWID4cYGm/JLM336R3SIIfJGNqGFVv/FH0aAg87+mYsH9b9Pc3GrbRE4qNfdlK8iHX26bpL8ywL9MZimfiB9dbQe5KRh+Cg6JqkgHMAWbPPfX+KEczxfnzUFE+8gyjcQ9CEIudO7VEbhXcvJp968COP/KZ0zVf6LtPdj6V4HXIgjevzJ3MZxZfUCzCmzNisGZFdNCTaKwm1WFaLMccKPCJod8y4sGN9Y5IxZuZuhpwWuEBsDOiARJh5ofGzWZJWpKiRCoGfnqLhhBmlZb79p5UYskeOA0OVQZByqGWyN9HAVwcBSAjyMDmfdLAWCTL3TI/PGQuc7ZaEVmAofML0kbMkat67YqwrRFEjw0mwxtL+dxwUXbX3GexHfCnpxDawGaiGiDjWS2E5w/a5Jr6lSbZv37d5lyD3msQdIxBBSoIRZ15htYQgZ2HNHnmlNDwDnVwyo41lkaW8+ptZG8Jgl8TiUu1vAoDCu0NSxA8hXhKFLNQQaRbG1YNKTqDOVpkuANy+QRzgc6geB8VQtvhAaa1kjOCMxXJW6F/6lAk0Cu8GmgbuASosmwBs0gUr0REmkUGDxobljh/xrfLU/o+HM1Wg47bbDTRyp2EgodRENcIvDnwk5Ih9Mnqvr7uGu6GQ2JZkj40LDTpEgqzswhpZWXqdHfEQFGSuryfz8XUgJm5yE/UDfmKeu6jxSgFkk7ys9DVPNtJES/a4IeNZf1yx0Jh7I2KKtHdfsYPu+Dmov5DRSNi+wAy6+Ej+yg5oFxbkb+gDMysZyRJdiDRCrrgR1hR6480OM6dEHgKxfqjts7BrOi1mZF4cyKahwo9Tvm1TDSIgjerMyImutlCsBMHg524tKWt3FIIs0hCTzoJABqcj6rIX6ow+HcCG91DJwGD0yelg02xL5jgY5hcrT3OQEjn0LtFETWlQSKaIsg8MlRZrQ4s/okZgVJriItW5R1TeZG2G+RBG9Y2DCsG93fPMG9csyeZ84vsaTCGNETUPfpez6mEcG/Dx/69/fX7Ef+NMzJ6NQE0F5cvsuNp4WfybQQY18eMgU2nKbZ7mU+/IhzTsOG3iYLgdnP89XNMYaDjlMOi1ok7XPKaeznhkjb+TwZufhay1wwTw+SIfvclGscUqAA22NGI9AYVrn+q2mVrgcIMK9FEjgamXuE50Kr0nnm4MgKjmioxo/UpRnAwAhoe+qYwQiyAgGjmoKxrmAU6KqqSwIHIzPYupdNhRpNXQC9ZQC9tiGAm1Kh94pGQIdQHjMaAZ7zKJZXWlJO55zIkLRIAkcjc7dSoFFpjKKxN+b9+/KEJQdLVvyRmo5AEbSTBFWt6phxCXLPItCK3xHWNXUm9FskgeNSQ8GlL5NZ8ezAyAqMMFY3pRCFJrMbyug4MHormw1JZyMtCRpHuCMa1SdOviYJHo0aEhJ+LmK3tWbJH2lResyLwOlsBJQ4cNR4BBhFjAh7JTdl+zqWXoskeDwy+c9VkKk7kaEDPCGt9hFC0IRSQ8kaB05vBSfIUw/0IGba2VnCepLD4TlLryU5rAWdOVyywCVPL6vqgeOSSRc6XHorLoGeq6mVEfC9rpQSilokgeMSNunQFS65g6q3SrkKte03hoFRCZtcoUOlN6KSNBYYVNJKQhG/Kyph1CIJHpXMUMnrrBwhF5xkB0eROsJ1IVs4OHKRkruHI8hQSU9TMV9upnRYvLVIgocjkxb9ZeiQyAaJEPK0CAB5/AIcFLk4yd1DEWSgZL2PW++kdIaisEUSPBSZJGhFbzs0smKPtF0MusdzE5oH1MVJ7h6NIAMlMVEnPMa6brmRoEUSPBqZ1OdvWfkTXCDAFh6SVjeiPgsdDJOIi5HcPSZBBklijTsKO3tIFLVIAsekhkpRv+df0rljsi2jJIkaeMbAU/5l5VwHRzvMsoWMksSRuodbF3zZHo5IiyR4ODKp7D94schd1r9lYhtWR5j54N6Ro7J3D0eHlPUfoK6pbXrWvyEJHo5MKvv2MSnhxMGRlXek1dfZZz3q5gF1bPbu0QiSzaayVECd+NERjHy6WRA8FplctlJKXQITS8VgXdzl4tWofOVKq++htLqvEuWnyGuoZvpetdWb1cUx5Q1ItxHA2pEOkimneopBV1LK9zcLgke6hhMFYsdI2W7aaWcECiyCXgM21Ly75MN4kbr0IstlPSXamIKHqFGTON7L/LKcTeQMUwldtl0l5Q/4sHNQYDkHUcioWh+rfg7t6m0ztlkQ+BwkC8E69d6vekOWy4xCdeYMNAn2iSx4syB47TZpdqfdVtq9scZra70CDJmoFWjlO0iAO+p3GLZI2lkxWG3DitJAM4e3FYNtNg6gHYsjNQ65vGw3DjjbOEW+lpHWNT6/PjHtNUHw2A90asnHV+/m7rTFfsiTTfSdNlIXaN667qivHbR0aGfjUptzL2qSYZ1QGMez8n6pYEVccJNM9pb/msjk4bC686oe2S/2sVYA3febDu4nDVwyeS8uuaHmpIOM7jMibZgRN+oyBGQQrZYNlZlJ2yIGZS2C4CHD5kiKTZCx0jhI0GByiSkJwiDYI2j8pA/eRTIZhDf/7d8lxeSvce8/DZWkvicCDxbKMS7zDR3tCOANlcKkiu6D/20cXiB+7APCPTbRfpPBQIA9DvT9625Yz0KsyIE8E7ixj4F4r8PW2k3a2L6sAVy2Y63GVdSx5Aehm+W8o9qKyzzLivXHxUw3/pYNePnE/wE= \ No newline at end of file From 401a0bee8d67b139f58c6112b12db338a36de408 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 19 Feb 2023 12:14:17 +0000 Subject: [PATCH 16/54] test: add the possibility to run only unit tests via script --- run-tests.ps1 | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/run-tests.ps1 b/run-tests.ps1 index 42a32c89..33a28ca6 100644 --- a/run-tests.ps1 +++ b/run-tests.ps1 @@ -1,3 +1,5 @@ +param ([switch] $OnlyRunUnitTests) + $globalTools = dotnet tool list -g $reportGeneratorTool = $globalTools | Select-String -Pattern "dotnet-reportgenerator-globaltool" @@ -10,7 +12,11 @@ $currentDir = (Get-Location).Path $coverageDir = "$currentDir\\coverage-outputs\\$reportTimestamp\\" # Run with current core "Rules.Framework" package version on other packages -dotnet test .\rules-framework.sln -m:1 +if ($OnlyRunUnitTests) { + dotnet test .\rules-framework.sln -m:1 --filter 'Category=Unit' +} else { + dotnet test .\rules-framework.sln -m:1 +} # Use project reference to "Rules.Framework" now dotnet add src\Rules.Framework.Providers.InMemory\Rules.Framework.Providers.InMemory.csproj reference src\Rules.Framework\Rules.Framework.csproj @@ -18,7 +24,11 @@ dotnet add src\Rules.Framework.Providers.MongoDb\Rules.Framework.Providers.Mongo dotnet add src\Rules.Framework.WebUI\Rules.Framework.WebUI.csproj reference src\Rules.Framework\Rules.Framework.csproj # Run again with project reference -dotnet test .\rules-framework.sln --collect:"XPlat Code Coverage" --results-directory:"$coverageDir" -m:1 +if ($OnlyRunUnitTests) { + dotnet test .\rules-framework.sln --collect:"XPlat Code Coverage" --results-directory:"$coverageDir" -m:1 --filter 'Category=Unit' +} else { + dotnet test .\rules-framework.sln --collect:"XPlat Code Coverage" --results-directory:"$coverageDir" -m:1 +} # Remove project reference to "Rules.Framework" dotnet remove src\Rules.Framework.Providers.InMemory\Rules.Framework.Providers.InMemory.csproj reference src\Rules.Framework\Rules.Framework.csproj From 264b7385c7395976c884baccbc8fa8467af0b487 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 19 Feb 2023 12:15:11 +0000 Subject: [PATCH 17/54] test: add unit tests --- .../AssemblyMetadata.cs | 4 +- .../AssemblyMetadata.cs | 4 +- .../AssemblyMetadata.cs | 4 +- .../AssemblyMetadata.cs | 4 +- .../AssemblyMetadata.cs | 4 +- .../Rules.Framework.Tests/AssemblyMetadata.cs | 4 +- .../CompilationRulesSourceMiddlewareTests.cs | 614 ++++++++ .../CompiledConditionsEvalEngineTests.cs | 145 ++ ...OneToOneConditionExpressionBuilderTests.cs | 3 +- ...OneToOneConditionExpressionBuilderTests.cs | 3 +- ...OneToOneConditionExpressionBuilderTests.cs | 3 +- ...OneToOneConditionExpressionBuilderTests.cs | 3 +- ...OneToOneConditionExpressionBuilderTests.cs | 3 +- ...OneToOneConditionExpressionBuilderTests.cs | 3 +- ...OneToOneConditionExpressionBuilderTests.cs | 3 +- ...OneToOneConditionExpressionBuilderTests.cs | 3 +- ...OneToOneConditionExpressionBuilderTests.cs | 3 +- ...OneToOneConditionExpressionBuilderTests.cs | 3 +- ...OneToOneConditionExpressionBuilderTests.cs | 3 +- ...OneToOneConditionExpressionBuilderTests.cs | 3 +- .../ConditionsValueLookupExtensionTests.cs | 43 + .../DefaultExpressionBuilderFactoryTests.cs | 133 ++ .../ExpressionBlockBuilderTests.cs | 1346 +++++++++++++++++ .../ExpressionBuilderTests.cs | 327 ++++ .../ExpressionParametersConfigurationTests.cs | 72 + .../ExpressionResultTests.cs | 30 + .../ExpressionSwitchBuilderTests.cs | 143 ++ ...alueConditionNodeExpressionBuilderTests.cs | 5 +- ...alueConditionNodeExpressionBuilderTests.cs | 5 +- ...alueConditionNodeExpressionBuilderTests.cs | 5 +- ...alueConditionNodeExpressionBuilderTests.cs | 5 +- ...ionsExpressionBuilderTests.GoldenFile2.csx | 75 + .../RuleConditionsExpressionBuilderTests.cs | 172 ++- .../Rules.Framework.Tests.csproj | 6 + .../AssemblyMetadata.cs | 5 + 35 files changed, 3140 insertions(+), 51 deletions(-) create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/CompilationRulesSourceMiddlewareTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/CompiledConditionsEvalEngineTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionsValueLookupExtensionTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/DefaultExpressionBuilderFactoryTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionBlockBuilderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionBuilderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionParametersConfigurationTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionResultTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionSwitchBuilderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.GoldenFile2.csx create mode 100644 tests/Rules.Framework.WebUI.Tests/AssemblyMetadata.cs diff --git a/tests/Rules.Framework.IntegrationTests/AssemblyMetadata.cs b/tests/Rules.Framework.IntegrationTests/AssemblyMetadata.cs index c16917bc..1d5e1911 100644 --- a/tests/Rules.Framework.IntegrationTests/AssemblyMetadata.cs +++ b/tests/Rules.Framework.IntegrationTests/AssemblyMetadata.cs @@ -1,5 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using Xunit; [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] -[assembly: ExcludeFromCodeCoverage] \ No newline at end of file +[assembly: ExcludeFromCodeCoverage] +[assembly: AssemblyTrait("Category", "Integration")] \ No newline at end of file diff --git a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/AssemblyMetadata.cs b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/AssemblyMetadata.cs index c16917bc..1d5e1911 100644 --- a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/AssemblyMetadata.cs +++ b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/AssemblyMetadata.cs @@ -1,5 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using Xunit; [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] -[assembly: ExcludeFromCodeCoverage] \ No newline at end of file +[assembly: ExcludeFromCodeCoverage] +[assembly: AssemblyTrait("Category", "Integration")] \ No newline at end of file diff --git a/tests/Rules.Framework.Providers.InMemory.Tests/AssemblyMetadata.cs b/tests/Rules.Framework.Providers.InMemory.Tests/AssemblyMetadata.cs index c16917bc..79c09eb0 100644 --- a/tests/Rules.Framework.Providers.InMemory.Tests/AssemblyMetadata.cs +++ b/tests/Rules.Framework.Providers.InMemory.Tests/AssemblyMetadata.cs @@ -1,5 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using Xunit; [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] -[assembly: ExcludeFromCodeCoverage] \ No newline at end of file +[assembly: ExcludeFromCodeCoverage] +[assembly: AssemblyTrait("Category", "Unit")] \ No newline at end of file diff --git a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/AssemblyMetadata.cs b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/AssemblyMetadata.cs index c16917bc..1d5e1911 100644 --- a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/AssemblyMetadata.cs +++ b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/AssemblyMetadata.cs @@ -1,5 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using Xunit; [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] -[assembly: ExcludeFromCodeCoverage] \ No newline at end of file +[assembly: ExcludeFromCodeCoverage] +[assembly: AssemblyTrait("Category", "Integration")] \ No newline at end of file diff --git a/tests/Rules.Framework.Providers.MongoDb.Tests/AssemblyMetadata.cs b/tests/Rules.Framework.Providers.MongoDb.Tests/AssemblyMetadata.cs index c16917bc..79c09eb0 100644 --- a/tests/Rules.Framework.Providers.MongoDb.Tests/AssemblyMetadata.cs +++ b/tests/Rules.Framework.Providers.MongoDb.Tests/AssemblyMetadata.cs @@ -1,5 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using Xunit; [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] -[assembly: ExcludeFromCodeCoverage] \ No newline at end of file +[assembly: ExcludeFromCodeCoverage] +[assembly: AssemblyTrait("Category", "Unit")] \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/AssemblyMetadata.cs b/tests/Rules.Framework.Tests/AssemblyMetadata.cs index c16917bc..79c09eb0 100644 --- a/tests/Rules.Framework.Tests/AssemblyMetadata.cs +++ b/tests/Rules.Framework.Tests/AssemblyMetadata.cs @@ -1,5 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using Xunit; [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] -[assembly: ExcludeFromCodeCoverage] \ No newline at end of file +[assembly: ExcludeFromCodeCoverage] +[assembly: AssemblyTrait("Category", "Unit")] \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/CompilationRulesSourceMiddlewareTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/CompilationRulesSourceMiddlewareTests.cs new file mode 100644 index 00000000..5a9ad168 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/CompilationRulesSourceMiddlewareTests.cs @@ -0,0 +1,614 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using System.Threading.Tasks; + using FluentAssertions; + using Moq; + using Rules.Framework.Builder; + using Rules.Framework.Core; + using Rules.Framework.Evaluation.Compiled; + using Rules.Framework.Source; + using Rules.Framework.Tests.TestStubs; + using Xunit; + + public class CompilationRulesSourceMiddlewareTests + { + [Fact] + public async Task HandleAddRuleAsync_GivenRuleWithCompiledConditionAndNextDelegate_IgnoresAndProceedsToNextDelegate() + { + // Arrange + var ruleResult = CreateTestRule(withCondition: true); + var expectedRule = ruleResult.Rule; + + // Simulate compiled rule. + expectedRule.RootCondition.Properties[ConditionNodeProperties.CompilationProperties.IsCompiledKey] = true; + + var addRuleArgs = new AddRuleArgs + { + Rule = expectedRule, + }; + + bool nextDelegateWasInvoked = false; + var nextDelegate = new AddRuleDelegate((args) => + { + nextDelegateWasInvoked = true; + return Task.CompletedTask; + }); + + Expression, bool>> expectedExpression = (evaluationContext) => true; + + var ruleConditionsExpressionBuilder = Mock.Of>(); + Mock.Get(ruleConditionsExpressionBuilder) + .Setup(x => x.BuildExpression(It.IsAny>())) + .Returns(expectedExpression); + + var rulesDataSource = Mock.Of>(); + + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + ruleConditionsExpressionBuilder, + rulesDataSource); + + // Act + await compilationRulesSourceMiddleware.HandleAddRuleAsync(addRuleArgs, nextDelegate).ConfigureAwait(false); + + // Assert + nextDelegateWasInvoked.Should().BeTrue(); + + Mock.Get(ruleConditionsExpressionBuilder) + .Verify(x => x.BuildExpression(It.IsAny>()), Times.Never()); + } + + [Fact] + public async Task HandleAddRuleAsync_GivenRuleWithoutConditionsAndNextDelegate_IgnoresAndProceedsToNextDelegate() + { + // Arrange + var ruleResult = CreateTestRule(withCondition: false); + var expectedRule = ruleResult.Rule; + + var addRuleArgs = new AddRuleArgs + { + Rule = expectedRule, + }; + + bool nextDelegateWasInvoked = false; + var nextDelegate = new AddRuleDelegate((args) => + { + nextDelegateWasInvoked = true; + return Task.CompletedTask; + }); + + Expression, bool>> expectedExpression = (evaluationContext) => true; + + var ruleConditionsExpressionBuilder = Mock.Of>(); + Mock.Get(ruleConditionsExpressionBuilder) + .Setup(x => x.BuildExpression(It.IsAny>())) + .Returns(expectedExpression); + + var rulesDataSource = Mock.Of>(); + + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + ruleConditionsExpressionBuilder, + rulesDataSource); + + // Act + await compilationRulesSourceMiddleware.HandleAddRuleAsync(addRuleArgs, nextDelegate).ConfigureAwait(false); + + // Assert + nextDelegateWasInvoked.Should().BeTrue(); + + Mock.Get(ruleConditionsExpressionBuilder) + .Verify(x => x.BuildExpression(It.IsAny>()), Times.Never()); + } + + [Fact] + public async Task HandleAddRuleAsync_GivenRuleWithUncompiledConditionAndNextDelegate_CompilesAndProceedsToNextDelegate() + { + // Arrange + var ruleResult = CreateTestRule(withCondition: true); + var expectedRule = ruleResult.Rule; + + var addRuleArgs = new AddRuleArgs + { + Rule = expectedRule, + }; + + bool nextDelegateWasInvoked = false; + var nextDelegate = new AddRuleDelegate((args) => + { + nextDelegateWasInvoked = true; + return Task.CompletedTask; + }); + + Expression, bool>> expectedExpression = (evaluationContext) => true; + + var ruleConditionsExpressionBuilder = Mock.Of>(); + Mock.Get(ruleConditionsExpressionBuilder) + .Setup(x => x.BuildExpression(It.IsAny>())) + .Returns(expectedExpression); + + var rulesDataSource = Mock.Of>(); + + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + ruleConditionsExpressionBuilder, + rulesDataSource); + + // Act + await compilationRulesSourceMiddleware.HandleAddRuleAsync(addRuleArgs, nextDelegate).ConfigureAwait(false); + + // Assert + expectedRule.RootCondition.Properties.Should().HaveCount(2); + expectedRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.IsCompiledKey) + .WhoseValue.Should().Be(true); + expectedRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.CompiledDelegateKey) + .WhoseValue.Should().BeOfType, bool>>(); + nextDelegateWasInvoked.Should().BeTrue(); + + Mock.Get(ruleConditionsExpressionBuilder) + .Verify(x => x.BuildExpression(It.IsAny>()), Times.Once()); + } + + [Fact] + public async Task HandleGetRulesAsync_GivenArgsFilteringToRulesWithCompiledConditionsAndNextDelegate_IgnoresAndProceedsToNextDelegate() + { + // Arrange + var ruleResult = CreateTestRule(withCondition: true); + var expectedRule = ruleResult.Rule; + + // Simulate compiled rule. + expectedRule.RootCondition.Properties[ConditionNodeProperties.CompilationProperties.IsCompiledKey] = true; + var expectedRules = new[] { expectedRule }; + + var getRulesArgs = new GetRulesArgs + { + ContentType = ContentType.Type1, + DateBegin = DateTime.UtcNow.AddDays(-1), + DateEnd = DateTime.UtcNow.AddDays(1), + }; + + bool nextDelegateWasInvoked = false; + var nextDelegate = new GetRulesDelegate((args) => + { + nextDelegateWasInvoked = true; + return Task.FromResult>>(expectedRules); + }); + + Expression, bool>> expectedExpression = (evaluationContext) => true; + + var ruleConditionsExpressionBuilder = Mock.Of>(); + Mock.Get(ruleConditionsExpressionBuilder) + .Setup(x => x.BuildExpression(It.IsAny>())) + .Returns(expectedExpression); + + var rulesDataSource = Mock.Of>(); + Mock.Get(rulesDataSource) + .Setup(x => x.UpdateRuleAsync(It.IsAny>())); + + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + ruleConditionsExpressionBuilder, + rulesDataSource); + + // Act + var actualRules = await compilationRulesSourceMiddleware.HandleGetRulesAsync(getRulesArgs, nextDelegate).ConfigureAwait(false); + + // Assert + actualRules.Should().BeSameAs(expectedRules); + nextDelegateWasInvoked.Should().BeTrue(); + + Mock.Get(ruleConditionsExpressionBuilder) + .Verify(x => x.BuildExpression(It.IsAny>()), Times.Never()); + Mock.Get(rulesDataSource) + .Verify(x => x.UpdateRuleAsync(It.IsAny>()), Times.Never()); + } + + [Fact] + public async Task HandleGetRulesAsync_GivenArgsFilteringToRulesWithoutConditionsAndNextDelegate_IgnoresAndProceedsToNextDelegate() + { + // Arrange + var ruleResult = CreateTestRule(withCondition: false); + var expectedRule = ruleResult.Rule; + var expectedRules = new[] { expectedRule }; + + var getRulesArgs = new GetRulesArgs + { + ContentType = ContentType.Type1, + DateBegin = DateTime.UtcNow.AddDays(-1), + DateEnd = DateTime.UtcNow.AddDays(1), + }; + + bool nextDelegateWasInvoked = false; + var nextDelegate = new GetRulesDelegate((args) => + { + nextDelegateWasInvoked = true; + return Task.FromResult>>(expectedRules); + }); + + Expression, bool>> expectedExpression = (evaluationContext) => true; + + var ruleConditionsExpressionBuilder = Mock.Of>(); + Mock.Get(ruleConditionsExpressionBuilder) + .Setup(x => x.BuildExpression(It.IsAny>())) + .Returns(expectedExpression); + + var rulesDataSource = Mock.Of>(); + Mock.Get(rulesDataSource) + .Setup(x => x.UpdateRuleAsync(It.IsAny>())); + + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + ruleConditionsExpressionBuilder, + rulesDataSource); + + // Act + var actualRules = await compilationRulesSourceMiddleware.HandleGetRulesAsync(getRulesArgs, nextDelegate).ConfigureAwait(false); + + // Assert + actualRules.Should().BeSameAs(expectedRules); + nextDelegateWasInvoked.Should().BeTrue(); + + Mock.Get(ruleConditionsExpressionBuilder) + .Verify(x => x.BuildExpression(It.IsAny>()), Times.Never()); + Mock.Get(rulesDataSource) + .Verify(x => x.UpdateRuleAsync(It.IsAny>()), Times.Never()); + } + + [Fact] + public async Task HandleGetRulesAsync_GivenArgsFilteringToRulesWithUncompiledConditionsAndNextDelegate_CompilesUpdatesAndProceedsToNextDelegate() + { + // Arrange + var ruleResult = CreateTestRule(withCondition: true); + var expectedRule = ruleResult.Rule; + var expectedRules = new[] { expectedRule }; + + var getRulesArgs = new GetRulesArgs + { + ContentType = ContentType.Type1, + DateBegin = DateTime.UtcNow.AddDays(-1), + DateEnd = DateTime.UtcNow.AddDays(1), + }; + + bool nextDelegateWasInvoked = false; + var nextDelegate = new GetRulesDelegate((args) => + { + nextDelegateWasInvoked = true; + return Task.FromResult>>(expectedRules); + }); + + Expression, bool>> expectedExpression = (evaluationContext) => true; + + var ruleConditionsExpressionBuilder = Mock.Of>(); + Mock.Get(ruleConditionsExpressionBuilder) + .Setup(x => x.BuildExpression(It.IsAny>())) + .Returns(expectedExpression); + + var rulesDataSource = Mock.Of>(); + Mock.Get(rulesDataSource) + .Setup(x => x.UpdateRuleAsync(It.IsAny>())); + + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + ruleConditionsExpressionBuilder, + rulesDataSource); + + // Act + var actualRules = await compilationRulesSourceMiddleware.HandleGetRulesAsync(getRulesArgs, nextDelegate).ConfigureAwait(false); + + // Assert + expectedRule.RootCondition.Properties.Should().HaveCount(2); + expectedRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.IsCompiledKey) + .WhoseValue.Should().Be(true); + expectedRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.CompiledDelegateKey) + .WhoseValue.Should().BeOfType, bool>>(); + nextDelegateWasInvoked.Should().BeTrue(); + + Mock.Get(ruleConditionsExpressionBuilder) + .Verify(x => x.BuildExpression(It.IsAny>()), Times.Once()); + Mock.Get(rulesDataSource) + .Verify(x => x.UpdateRuleAsync(It.IsAny>()), Times.Once()); + } + + [Fact] + public async Task HandleGetRulesFilteredAsync_GivenArgsFilteringToRulesWithCompiledConditionsAndNextDelegate_IgnoresAndProceedsToNextDelegate() + { + // Arrange + var ruleResult = CreateTestRule(withCondition: true); + var expectedRule = ruleResult.Rule; + + // Simulate compiled rule. + expectedRule.RootCondition.Properties[ConditionNodeProperties.CompilationProperties.IsCompiledKey] = true; + var expectedRules = new[] { expectedRule }; + + var getRulesFilteredArgs = new GetRulesFilteredArgs + { + ContentType = ContentType.Type1, + }; + + bool nextDelegateWasInvoked = false; + var nextDelegate = new GetRulesFilteredDelegate((args) => + { + nextDelegateWasInvoked = true; + return Task.FromResult>>(expectedRules); + }); + + Expression, bool>> expectedExpression = (evaluationContext) => true; + + var ruleConditionsExpressionBuilder = Mock.Of>(); + Mock.Get(ruleConditionsExpressionBuilder) + .Setup(x => x.BuildExpression(It.IsAny>())) + .Returns(expectedExpression); + + var rulesDataSource = Mock.Of>(); + Mock.Get(rulesDataSource) + .Setup(x => x.UpdateRuleAsync(It.IsAny>())); + + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + ruleConditionsExpressionBuilder, + rulesDataSource); + + // Act + var actualRules = await compilationRulesSourceMiddleware.HandleGetRulesFilteredAsync(getRulesFilteredArgs, nextDelegate).ConfigureAwait(false); + + // Assert + actualRules.Should().BeSameAs(expectedRules); + nextDelegateWasInvoked.Should().BeTrue(); + + Mock.Get(ruleConditionsExpressionBuilder) + .Verify(x => x.BuildExpression(It.IsAny>()), Times.Never()); + Mock.Get(rulesDataSource) + .Verify(x => x.UpdateRuleAsync(It.IsAny>()), Times.Never()); + } + + [Fact] + public async Task HandleGetRulesFilteredAsync_GivenArgsFilteringToRulesWithoutConditionsAndNextDelegate_IgnoresAndProceedsToNextDelegate() + { + // Arrange + var ruleResult = CreateTestRule(withCondition: false); + var expectedRule = ruleResult.Rule; + var expectedRules = new[] { expectedRule }; + + var getRulesFilteredArgs = new GetRulesFilteredArgs + { + ContentType = ContentType.Type1, + }; + + bool nextDelegateWasInvoked = false; + var nextDelegate = new GetRulesFilteredDelegate((args) => + { + nextDelegateWasInvoked = true; + return Task.FromResult>>(expectedRules); + }); + + Expression, bool>> expectedExpression = (evaluationContext) => true; + + var ruleConditionsExpressionBuilder = Mock.Of>(); + Mock.Get(ruleConditionsExpressionBuilder) + .Setup(x => x.BuildExpression(It.IsAny>())) + .Returns(expectedExpression); + + var rulesDataSource = Mock.Of>(); + Mock.Get(rulesDataSource) + .Setup(x => x.UpdateRuleAsync(It.IsAny>())); + + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + ruleConditionsExpressionBuilder, + rulesDataSource); + + // Act + var actualRules = await compilationRulesSourceMiddleware.HandleGetRulesFilteredAsync(getRulesFilteredArgs, nextDelegate).ConfigureAwait(false); + + // Assert + actualRules.Should().BeSameAs(expectedRules); + nextDelegateWasInvoked.Should().BeTrue(); + + Mock.Get(ruleConditionsExpressionBuilder) + .Verify(x => x.BuildExpression(It.IsAny>()), Times.Never()); + Mock.Get(rulesDataSource) + .Verify(x => x.UpdateRuleAsync(It.IsAny>()), Times.Never()); + } + + [Fact] + public async Task HandleGetRulesFilteredAsync_GivenArgsFilteringToRulesWithUncompiledConditionsAndNextDelegate_CompilesUpdatesAndProceedsToNextDelegate() + { + // Arrange + var ruleResult = CreateTestRule(withCondition: true); + var expectedRule = ruleResult.Rule; + var expectedRules = new[] { expectedRule }; + + var getRulesFilteredArgs = new GetRulesFilteredArgs + { + ContentType = ContentType.Type1, + }; + + bool nextDelegateWasInvoked = false; + var nextDelegate = new GetRulesFilteredDelegate((args) => + { + nextDelegateWasInvoked = true; + return Task.FromResult>>(expectedRules); + }); + + Expression, bool>> expectedExpression = (evaluationContext) => true; + + var ruleConditionsExpressionBuilder = Mock.Of>(); + Mock.Get(ruleConditionsExpressionBuilder) + .Setup(x => x.BuildExpression(It.IsAny>())) + .Returns(expectedExpression); + + var rulesDataSource = Mock.Of>(); + Mock.Get(rulesDataSource) + .Setup(x => x.UpdateRuleAsync(It.IsAny>())); + + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + ruleConditionsExpressionBuilder, + rulesDataSource); + + // Act + var actualRules = await compilationRulesSourceMiddleware.HandleGetRulesFilteredAsync(getRulesFilteredArgs, nextDelegate).ConfigureAwait(false); + + // Assert + expectedRule.RootCondition.Properties.Should().HaveCount(2); + expectedRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.IsCompiledKey) + .WhoseValue.Should().Be(true); + expectedRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.CompiledDelegateKey) + .WhoseValue.Should().BeOfType, bool>>(); + nextDelegateWasInvoked.Should().BeTrue(); + + Mock.Get(ruleConditionsExpressionBuilder) + .Verify(x => x.BuildExpression(It.IsAny>()), Times.Once()); + Mock.Get(rulesDataSource) + .Verify(x => x.UpdateRuleAsync(It.IsAny>()), Times.Once()); + } + + [Fact] + public async Task HandleUpdateRuleAsync_GivenRuleWithCompiledConditionAndNextDelegate_IgnoresAndProceedsToNextDelegate() + { + // Arrange + var ruleResult = CreateTestRule(withCondition: true); + var expectedRule = ruleResult.Rule; + + // Simulate compiled rule. + expectedRule.RootCondition.Properties[ConditionNodeProperties.CompilationProperties.IsCompiledKey] = true; + + var updateRuleArgs = new UpdateRuleArgs + { + Rule = expectedRule, + }; + + bool nextDelegateWasInvoked = false; + var nextDelegate = new UpdateRuleDelegate((args) => + { + nextDelegateWasInvoked = true; + return Task.CompletedTask; + }); + + Expression, bool>> expectedExpression = (evaluationContext) => true; + + var ruleConditionsExpressionBuilder = Mock.Of>(); + Mock.Get(ruleConditionsExpressionBuilder) + .Setup(x => x.BuildExpression(It.IsAny>())) + .Returns(expectedExpression); + + var rulesDataSource = Mock.Of>(); + + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + ruleConditionsExpressionBuilder, + rulesDataSource); + + // Act + await compilationRulesSourceMiddleware.HandleUpdateRuleAsync(updateRuleArgs, nextDelegate).ConfigureAwait(false); + + // Assert + nextDelegateWasInvoked.Should().BeTrue(); + + Mock.Get(ruleConditionsExpressionBuilder) + .Verify(x => x.BuildExpression(It.IsAny>()), Times.Never()); + } + + [Fact] + public async Task HandleUpdateRuleAsync_GivenRuleWithoutConditionsAndNextDelegate_IgnoresAndProceedsToNextDelegate() + { + // Arrange + var ruleResult = CreateTestRule(withCondition: false); + var expectedRule = ruleResult.Rule; + + var updateRuleArgs = new UpdateRuleArgs + { + Rule = expectedRule, + }; + + bool nextDelegateWasInvoked = false; + var nextDelegate = new UpdateRuleDelegate((args) => + { + nextDelegateWasInvoked = true; + return Task.CompletedTask; + }); + + Expression, bool>> expectedExpression = (evaluationContext) => true; + + var ruleConditionsExpressionBuilder = Mock.Of>(); + Mock.Get(ruleConditionsExpressionBuilder) + .Setup(x => x.BuildExpression(It.IsAny>())) + .Returns(expectedExpression); + + var rulesDataSource = Mock.Of>(); + + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + ruleConditionsExpressionBuilder, + rulesDataSource); + + // Act + await compilationRulesSourceMiddleware.HandleUpdateRuleAsync(updateRuleArgs, nextDelegate).ConfigureAwait(false); + + // Assert + nextDelegateWasInvoked.Should().BeTrue(); + + Mock.Get(ruleConditionsExpressionBuilder) + .Verify(x => x.BuildExpression(It.IsAny>()), Times.Never()); + } + + [Fact] + public async Task HandleUpdateRuleAsync_GivenRuleWithUncompiledConditionAndNextDelegate_CompilesAndProceedsToNextDelegate() + { + // Arrange + var ruleResult = CreateTestRule(withCondition: true); + var expectedRule = ruleResult.Rule; + + var updateRuleArgs = new UpdateRuleArgs + { + Rule = expectedRule, + }; + + bool nextDelegateWasInvoked = false; + var nextDelegate = new UpdateRuleDelegate((args) => + { + nextDelegateWasInvoked = true; + return Task.CompletedTask; + }); + + Expression, bool>> expectedExpression = (evaluationContext) => true; + + var ruleConditionsExpressionBuilder = Mock.Of>(); + Mock.Get(ruleConditionsExpressionBuilder) + .Setup(x => x.BuildExpression(It.IsAny>())) + .Returns(expectedExpression); + + var rulesDataSource = Mock.Of>(); + + var compilationRulesSourceMiddleware = new CompilationRulesSourceMiddleware( + ruleConditionsExpressionBuilder, + rulesDataSource); + + // Act + await compilationRulesSourceMiddleware.HandleUpdateRuleAsync(updateRuleArgs, nextDelegate).ConfigureAwait(false); + + // Assert + expectedRule.RootCondition.Properties.Should().HaveCount(2); + expectedRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.IsCompiledKey) + .WhoseValue.Should().Be(true); + expectedRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.CompiledDelegateKey) + .WhoseValue.Should().BeOfType, bool>>(); + nextDelegateWasInvoked.Should().BeTrue(); + + Mock.Get(ruleConditionsExpressionBuilder) + .Verify(x => x.BuildExpression(It.IsAny>()), Times.Once()); + } + + private static RuleBuilderResult CreateTestRule(bool withCondition) + { + var ruleBuilder = RuleBuilder.NewRule() + .WithName("Test rule") + .WithDateBegin(DateTime.UtcNow) + .WithContentContainer(new ContentContainer(ContentType.Type1, t => "Test content")); + + if (withCondition) + { + ruleBuilder.WithCondition(x => + x.AsValued(ConditionType.IsoCountryCode) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand("PT") + .Build()); + } + + return ruleBuilder.Build(); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/CompiledConditionsEvalEngineTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/CompiledConditionsEvalEngineTests.cs new file mode 100644 index 00000000..9e8b4515 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/CompiledConditionsEvalEngineTests.cs @@ -0,0 +1,145 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled +{ + using System; + using System.Collections.Generic; + using FluentAssertions; + using Moq; + using Rules.Framework.Builder; + using Rules.Framework.Core; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled; + using Rules.Framework.Tests.TestStubs; + using Xunit; + + public class CompiledConditionsEvalEngineTests + { + [Fact] + public void Eval_GivenCompiledConditionNodeConditionsAndExcludeRulesWithoutSearchConditions_DoesNotHaveAllConditionsAndReturnsFalse() + { + // Arrange + var ruleResult = CreateTestRule(); + var expectedRule = ruleResult.Rule; + Func, bool> expectedExpression = (evaluationContext) => true; + expectedRule.RootCondition.Properties[ConditionNodeProperties.CompilationProperties.CompiledDelegateKey] = expectedExpression; + var conditions = new Dictionary(); + var evaluationOptions = new EvaluationOptions + { + ExcludeRulesWithoutSearchConditions = true, + MatchMode = MatchModes.Exact, + }; + + var conditionsTreeAnalyzer = Mock.Of>(); + Mock.Get(conditionsTreeAnalyzer) + .Setup(x => x.AreAllSearchConditionsPresent(It.IsAny>(), It.IsAny>())) + .Returns(false); + + var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); + + var compiledConditionsEvalEngine = new CompiledConditionsEvalEngine(conditionsTreeAnalyzer, rulesEngineOptions); + + // Act + var result = compiledConditionsEvalEngine.Eval(expectedRule.RootCondition, conditions, evaluationOptions); + + // Assert + result.Should().BeFalse(); + } + + [Fact] + public void Eval_GivenCompiledConditionNodeConditionsAndExcludeRulesWithoutSearchConditions_ExecutesEvaluationAndReturnsResult() + { + // Arrange + var ruleResult = CreateTestRule(); + var expectedRule = ruleResult.Rule; + Func, bool> expectedExpression = (evaluationContext) => true; + expectedRule.RootCondition.Properties[ConditionNodeProperties.CompilationProperties.CompiledDelegateKey] = expectedExpression; + var conditions = new Dictionary(); + var evaluationOptions = new EvaluationOptions + { + ExcludeRulesWithoutSearchConditions = true, + MatchMode = MatchModes.Exact, + }; + + var conditionsTreeAnalyzer = Mock.Of>(); + Mock.Get(conditionsTreeAnalyzer) + .Setup(x => x.AreAllSearchConditionsPresent(It.IsAny>(), It.IsAny>())) + .Returns(true); + + var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); + + var compiledConditionsEvalEngine = new CompiledConditionsEvalEngine(conditionsTreeAnalyzer, rulesEngineOptions); + + // Act + var result = compiledConditionsEvalEngine.Eval(expectedRule.RootCondition, conditions, evaluationOptions); + + // Assert + result.Should().BeTrue(); + } + + [Fact] + public void Eval_GivenCompiledConditionNodeConditionsAndIncludeRulesWithoutSearchConditions_ExecutesEvaluationAndReturnsResult() + { + // Arrange + var ruleResult = CreateTestRule(); + var expectedRule = ruleResult.Rule; + Func, bool> expectedExpression = (evaluationContext) => true; + expectedRule.RootCondition.Properties[ConditionNodeProperties.CompilationProperties.CompiledDelegateKey] = expectedExpression; + var conditions = new Dictionary(); + var evaluationOptions = new EvaluationOptions + { + ExcludeRulesWithoutSearchConditions = false, + MatchMode = MatchModes.Exact, + }; + + var conditionsTreeAnalyzer = Mock.Of>(); + + var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); + + var compiledConditionsEvalEngine = new CompiledConditionsEvalEngine(conditionsTreeAnalyzer, rulesEngineOptions); + + // Act + var result = compiledConditionsEvalEngine.Eval(expectedRule.RootCondition, conditions, evaluationOptions); + + // Assert + result.Should().BeTrue(); + } + + [Fact] + public void Eval_GivenUncompiledConditionNodeConditionsAndIncludeRulesWithoutSearchConditions_ThrowsArgumentException() + { + // Arrange + var ruleResult = CreateTestRule(); + var expectedRule = ruleResult.Rule; + var conditions = new Dictionary(); + var evaluationOptions = new EvaluationOptions + { + ExcludeRulesWithoutSearchConditions = false, + MatchMode = MatchModes.Exact, + }; + + var conditionsTreeAnalyzer = Mock.Of>(); + + var rulesEngineOptions = RulesEngineOptions.NewWithDefaults(); + + var compiledConditionsEvalEngine = new CompiledConditionsEvalEngine(conditionsTreeAnalyzer, rulesEngineOptions); + + // Act + var action = FluentActions.Invoking(() => compiledConditionsEvalEngine.Eval(expectedRule.RootCondition, conditions, evaluationOptions)); + + // Assert + action.Should().ThrowExactly() + .Which.ParamName.Should().Be("conditionNode"); + } + + private static RuleBuilderResult CreateTestRule() => RuleBuilder.NewRule() + .WithName("Test rule") + .WithDateBegin(DateTime.UtcNow) + .WithContentContainer(new ContentContainer(ContentType.Type1, t => "Test content")) + .WithCondition(x => + x.AsValued(ConditionType.IsoCurrency) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand("EUR") + .Build()) + .Build(); + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilderTests.cs index 5b735cf0..839d0a47 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveEndsWithOneToOneConditionExpressionBuilderTests.cs @@ -8,7 +8,6 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class CaseInsensitiveEndsWithOneToOneConditionExpressionBuilderTests @@ -24,7 +23,7 @@ public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTy RightHandOperand = Expression.Constant(2), }; - var builder = Mock.Of(); + var builder = Mock.Of(); var caseInsensitiveEndsWithOneToOneConditionExpressionBuilder = new CaseInsensitiveEndsWithOneToOneConditionExpressionBuilder(); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilderTests.cs index 2c1aeaa9..f34dab4e 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/CaseInsensitiveStartsWithOneToOneConditionExpressionBuilderTests.cs @@ -8,7 +8,6 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class CaseInsensitiveStartsWithOneToOneConditionExpressionBuilderTests @@ -24,7 +23,7 @@ public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTy RightHandOperand = Expression.Constant(2), }; - var builder = Mock.Of(); + var builder = Mock.Of(); var caseInsensitiveStartsWithOneToOneConditionExpressionBuilder = new CaseInsensitiveStartsWithOneToOneConditionExpressionBuilder(); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilderTests.cs index 5f335371..feca11cd 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ContainsOneToOneConditionExpressionBuilderTests.cs @@ -8,7 +8,6 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class ContainsOneToOneConditionExpressionBuilderTests @@ -24,7 +23,7 @@ public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTy RightHandOperand = Expression.Constant(2), }; - var builder = Mock.Of(); + var builder = Mock.Of(); var containsOneToOneConditionExpressionBuilder = new ContainsOneToOneConditionExpressionBuilder(); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilderTests.cs index 9c989bd9..711c2726 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/EndsWithOneToOneConditionExpressionBuilderTests.cs @@ -8,7 +8,6 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class EndsWithOneToOneConditionExpressionBuilderTests @@ -24,7 +23,7 @@ public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTy RightHandOperand = Expression.Constant(2), }; - var builder = Mock.Of(); + var builder = Mock.Of(); var endsWithOneToOneConditionExpressionBuilder = new EndsWithOneToOneConditionExpressionBuilder(); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilderTests.cs index 92ea854e..682973cf 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOneToOneConditionExpressionBuilderTests.cs @@ -8,7 +8,6 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class GreaterThanOneToOneConditionExpressionBuilderTests @@ -65,7 +64,7 @@ public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTy RightHandOperand = Expression.Constant("test", typeof(string)), }; - var builder = Mock.Of(); + var builder = Mock.Of(); var greaterThanOneToOneConditionExpressionBuilder = new GreaterThanOneToOneConditionExpressionBuilder(); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilderTests.cs index 75146744..aa6f5bd2 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/GreaterThanOrEqualOneToOneConditionExpressionBuilderTests.cs @@ -8,7 +8,6 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class GreaterThanOrEqualOneToOneConditionExpressionBuilderTests @@ -65,7 +64,7 @@ public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTy RightHandOperand = Expression.Constant("test", typeof(string)), }; - var builder = Mock.Of(); + var builder = Mock.Of(); var greaterThanOrEqualOneToOneConditionExpressionBuilder = new GreaterThanOrEqualOneToOneConditionExpressionBuilder(); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilderTests.cs index 8f94f50a..f1b0e1c1 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOneToOneConditionExpressionBuilderTests.cs @@ -8,7 +8,6 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class LesserThanOneToOneConditionExpressionBuilderTests @@ -65,7 +64,7 @@ public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTy RightHandOperand = Expression.Constant("test", typeof(string)), }; - var builder = Mock.Of(); + var builder = Mock.Of(); var lesserThanOneToOneConditionExpressionBuilder = new LesserThanOneToOneConditionExpressionBuilder(); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilderTests.cs index 1113b7e1..5bf384c9 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/LesserThanOrEqualOneToOneConditionExpressionBuilderTests.cs @@ -8,7 +8,6 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class LesserThanOrEqualOneToOneConditionExpressionBuilderTests @@ -65,7 +64,7 @@ public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTy RightHandOperand = Expression.Constant("test", typeof(string)), }; - var builder = Mock.Of(); + var builder = Mock.Of(); var lesserThanOrEqualOneToOneConditionExpressionBuilder = new LesserThanOrEqualOneToOneConditionExpressionBuilder(); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilderTests.cs index a9b123d2..dd78a75f 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotContainsOneToOneConditionExpressionBuilderTests.cs @@ -8,7 +8,6 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class NotContainsOneToOneConditionExpressionBuilderTests @@ -24,7 +23,7 @@ public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTy RightHandOperand = Expression.Constant("test", typeof(string)), }; - var builder = Mock.Of(); + var builder = Mock.Of(); var containsOneToOneConditionExpressionBuilder = new NotContainsOneToOneConditionExpressionBuilder(); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilderTests.cs index c9e624f7..a0c79782 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotEndsWithOneToOneConditionExpressionBuilderTests.cs @@ -8,7 +8,6 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class NotEndsWithOneToOneConditionExpressionBuilderTests @@ -24,7 +23,7 @@ public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTy RightHandOperand = Expression.Constant(2, typeof(int)), }; - var builder = Mock.Of(); + var builder = Mock.Of(); var notEndsWithOneToOneConditionExpressionBuilder = new NotEndsWithOneToOneConditionExpressionBuilder(); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilderTests.cs index 5cd62d69..44237c8e 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotStartsWithOneToOneConditionExpressionBuilderTests.cs @@ -8,7 +8,6 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class NotStartsWithOneToOneConditionExpressionBuilderTests @@ -24,7 +23,7 @@ public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTy RightHandOperand = Expression.Constant(2, typeof(int)), }; - var builder = Mock.Of(); + var builder = Mock.Of(); var notStartsWithOneToOneConditionExpressionBuilder = new NotStartsWithOneToOneConditionExpressionBuilder(); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilderTests.cs index 7cd0929f..07c1a70a 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilderTests.cs @@ -8,7 +8,6 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class StartsWithOneToOneConditionExpressionBuilderTests @@ -24,7 +23,7 @@ public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTy RightHandOperand = Expression.Constant(2, typeof(int)), }; - var builder = Mock.Of(); + var builder = Mock.Of(); var endsWithOneToOneConditionExpressionBuilder = new StartsWithOneToOneConditionExpressionBuilder(); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionsValueLookupExtensionTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionsValueLookupExtensionTests.cs new file mode 100644 index 00000000..acfb5fc2 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionsValueLookupExtensionTests.cs @@ -0,0 +1,43 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled +{ + using System.Collections.Generic; + using FluentAssertions; + using Rules.Framework.Evaluation.Compiled; + using Rules.Framework.Tests.TestStubs; + using Xunit; + + public class ConditionsValueLookupExtensionTests + { + [Fact] + public void GetValueOrDefault_GivenConditionsDictionaryAndConditionType_ReturnsNull() + { + // Arrange + const string expected = "EUR"; + var conditions = new Dictionary + { + { ConditionType.IsoCurrency, expected } + }; + var conditionType = ConditionType.IsoCurrency; + + // Act + var result = ConditionsValueLookupExtension.GetValueOrDefault(conditions, conditionType); + + // Assert + result.Should().Be(expected); + } + + [Fact] + public void GetValueOrDefault_GivenEmptyConditionsDictionaryAndConditionType_ReturnsNull() + { + // Arrange + var conditions = new Dictionary(); + var conditionType = ConditionType.IsoCurrency; + + // Act + var result = ConditionsValueLookupExtension.GetValueOrDefault(conditions, conditionType); + + // Assert + result.Should().BeNull(); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/DefaultExpressionBuilderFactoryTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/DefaultExpressionBuilderFactoryTests.cs new file mode 100644 index 00000000..437d7057 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/DefaultExpressionBuilderFactoryTests.cs @@ -0,0 +1,133 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ExpressionBuilders +{ + using System; + using FluentAssertions; + using Moq; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Xunit; + + public class DefaultExpressionBuilderFactoryTests + { + [Fact] + public void CreateExpressionBlockBuilder_GivenNotNullExpressionConfigurationWithNullScopeAndParent_ReturnsNewExpressionBlockBuilder() + { + // Arrange + var scope = "TestScope"; + var parent = Mock.Of(); + var expressionConfiguration = new ExpressionConfiguration(); + + var defaultExpressionBuilderFactory = new DefaultExpressionBuilderFactory(); + + // Act + var actual = defaultExpressionBuilderFactory.CreateExpressionBlockBuilder(scope, parent, expressionConfiguration); + + // Assert + actual.Should().NotBeNull() + .And.BeOfType(); + } + + [Fact] + public void CreateExpressionBlockBuilder_GivenNotNullExpressionConfigurationWithScopeAndParent_ReturnsNewExpressionBlockBuilder() + { + // Arrange + var expressionConfiguration = new ExpressionConfiguration(); + + var defaultExpressionBuilderFactory = new DefaultExpressionBuilderFactory(); + + // Act + var actual = defaultExpressionBuilderFactory.CreateExpressionBlockBuilder(null, null, expressionConfiguration); + + // Assert + actual.Should().NotBeNull() + .And.BeOfType(); + } + + [Fact] + public void CreateExpressionBlockBuilder_GivenNullExpressionConfiguration_ThrowsArgumentNullException() + { + // Arrange + var defaultExpressionBuilderFactory = new DefaultExpressionBuilderFactory(); + + // Act + var action = FluentActions.Invoking(() => defaultExpressionBuilderFactory.CreateExpressionBlockBuilder(null, null, null)); + + // Assert + action.Should().ThrowExactly() + .Which.ParamName.Should().Be("expressionConfiguration"); + } + + [Fact] + public void CreateExpressionBuilder_GivenNotNullExpressionConfiguration_ReturnsNewExpressionBuilder() + { + // Arrange + var expressionConfiguration = new ExpressionConfiguration(); + + var defaultExpressionBuilderFactory = new DefaultExpressionBuilderFactory(); + + // Act + var actual = defaultExpressionBuilderFactory.CreateExpressionBuilder(expressionConfiguration); + + // Assert + actual.Should().NotBeNull() + .And.BeOfType(); + } + + [Fact] + public void CreateExpressionBuilder_GivenNullExpressionConfiguration_ThrowsArgumentNullException() + { + // Arrange + var defaultExpressionBuilderFactory = new DefaultExpressionBuilderFactory(); + + // Act + var action = FluentActions.Invoking(() => defaultExpressionBuilderFactory.CreateExpressionBuilder(null)); + + // Assert + action.Should().ThrowExactly() + .Which.ParamName.Should().Be("expressionConfiguration"); + } + + [Fact] + public void CreateExpressionParametersConfiguration_NoConditions_ReturnsExpressionParametersConfiguration() + { + // Arrange + var defaultExpressionBuilderFactory = new DefaultExpressionBuilderFactory(); + + // Act + var actual = defaultExpressionBuilderFactory.CreateExpressionParametersConfiguration(); + + // Assert + actual.Should().NotBeNull() + .And.BeOfType(); + } + + [Fact] + public void CreateSwitchExpressionBuilder_GivenNotNullParent_ReturnsNewExpressionSwitchBuilder() + { + // Arrange + var parent = Mock.Of(); + + var defaultExpressionBuilderFactory = new DefaultExpressionBuilderFactory(); + + // Act + var actual = defaultExpressionBuilderFactory.CreateExpressionSwitchBuilder(parent); + + // Assert + actual.Should().NotBeNull() + .And.BeOfType(); + } + + [Fact] + public void CreateSwitchExpressionBuilder_GivenNullParent_ThrowsArgumentNullException() + { + // Arrange + var defaultExpressionBuilderFactory = new DefaultExpressionBuilderFactory(); + + // Act + var action = FluentActions.Invoking(() => defaultExpressionBuilderFactory.CreateExpressionSwitchBuilder(null)); + + // Assert + action.Should().ThrowExactly() + .Which.ParamName.Should().Be("parent"); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionBlockBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionBlockBuilderTests.cs new file mode 100644 index 00000000..fb5cd2b3 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionBlockBuilderTests.cs @@ -0,0 +1,1346 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ExpressionBuilders +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using FluentAssertions; + using Moq; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Xunit; + + public class ExpressionBlockBuilderTests + { + [Fact] + public void AddExpression_GivenNotNullExpression_AddsExpressionToExpressionsList() + { + // Arrange + var expression = Expression.Constant(1); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + expressionBlockBuilder.AddExpression(expression); + + // Assert + expressionBlockBuilder.Expressions.Should().HaveCount(1) + .And.Contain(expression); + } + + [Fact] + public void AddExpression_GivenNullExpression_ThrowsArgumentNullException() + { + // Arrange + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var action = FluentActions.Invoking(() => expressionBlockBuilder.AddExpression(null)); + + // Arrange + action.Should().ThrowExactly() + .Which.ParamName.Should().Be("expression"); + } + + [Fact] + public void AndAlso_GivenCollectionOfExpressionsWithMultipleExpressions_ReturnsExpression() + { + // Arrange + var expressions = new Expression[] + { + Expression.Constant(true), + Expression.Constant(true), + Expression.Constant(true), + Expression.Constant(true), + Expression.Constant(true), + }; + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.AndAlso(expressions); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + actual.NodeType.Should().Be(ExpressionType.AndAlso); + + var actualAsBinaryExpression = actual as BinaryExpression; + actualAsBinaryExpression.Right.Should().BeAssignableTo(); + actualAsBinaryExpression.Left.Should().BeAssignableTo(); + var chainedExpressionsCount = 1; + while (actualAsBinaryExpression.Left is BinaryExpression) + { + actualAsBinaryExpression.Right.Should().BeAssignableTo(); + actualAsBinaryExpression = actualAsBinaryExpression.Left as BinaryExpression; + chainedExpressionsCount++; + } + + actualAsBinaryExpression.Left.Should().BeAssignableTo(); + actualAsBinaryExpression.Right.Should().BeAssignableTo(); + chainedExpressionsCount.Should().Be(4); + } + + [Fact] + public void AndAlso_GivenCollectionOfExpressionsWithNoneExpressions_ThrowsArgumentException() + { + // Arrange + var expressions = Array.Empty(); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var action = FluentActions.Invoking(() => expressionBlockBuilder.AndAlso(expressions)); + + // Assert + action.Should().ThrowExactly() + .Which.ParamName.Should().Be("expressions"); + } + + [Fact] + public void AndAlso_GivenLeftAndRightExpressions_ReturnsExpression() + { + // Arrange + var leftExpression = Expression.Constant(true); + var rightExpression = Expression.Constant(false); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.AndAlso(leftExpression, rightExpression); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + actual.NodeType.Should().Be(ExpressionType.AndAlso); + + var actualAsBinaryExpression = actual as BinaryExpression; + actualAsBinaryExpression.Left.Should().Be(leftExpression); + actualAsBinaryExpression.Right.Should().Be(rightExpression); + } + + [Fact] + public void AndAlso_GivenNullCollectionOfExpressions_ThrowsArgumentNullException() + { + // Arrange + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var action = FluentActions.Invoking(() => expressionBlockBuilder.AndAlso(null)); + + // Assert + action.Should().ThrowExactly() + .Which.ParamName.Should().Be("expressions"); + } + + [Fact] + public void Assign_GivenLeftAndRightExpressions_AddsExpressionToExpressionsList() + { + // Arrange + var leftExpression = Expression.Variable(type: typeof(int), name: "testVariable"); + var rightExpression = Expression.Constant(1); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + expressionBlockBuilder.Assign(leftExpression, rightExpression); + + // Assert + expressionBlockBuilder.Expressions.Should().HaveCount(1); + var expression = expressionBlockBuilder.Expressions[0]; + expression.Should().NotBeNull() + .And.BeAssignableTo(); + expression.NodeType.Should().Be(ExpressionType.Assign); + var binaryExpression = expression as BinaryExpression; + binaryExpression.Left.Should().NotBeNull() + .And.BeSameAs(leftExpression); + binaryExpression.Right.Should().NotBeNull() + .And.BeSameAs(rightExpression); + } + + [Fact] + public void Block_GivenNotNullOrEmptyScopeAndNotNullImplementationBuilderWithLogicToAddExpressions_ReturnsExpression() + { + // Arrange + var scopeName = "testScope"; + + var expressionConfiguration = new ExpressionConfiguration(); + var childExpressionBlockBuilder = Mock.Of(); + Mock.Get(childExpressionBlockBuilder) + .SetupGet(x => x.Expressions) + .Returns(() => + { + var valueToReturnExpression = Expression.Constant(1); + var labelTarget = Expression.Label("label"); + var returnExpression = Expression.Return(labelTarget, valueToReturnExpression); + + return new Expression[] { returnExpression }; + }); + var expressionBuilderFactory = Mock.Of(); + Mock.Get(expressionBuilderFactory) + .Setup(x => x.CreateExpressionBlockBuilder(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(childExpressionBlockBuilder); + IExpressionBlockBuilder actualChildExpressionBlockBuilder = null; + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.Block(scopeName, b => + { + actualChildExpressionBlockBuilder = b; + }); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + actualChildExpressionBlockBuilder.Should().NotBeNull() + .And.BeSameAs(childExpressionBlockBuilder); + } + + [Fact] + public void Block_GivenNotNullOrEmptyScopeAndNotNullImplementationBuilderWithoutLogic_ThrowsInvalidOperationException() + { + // Arrange + var scopeName = "testScope"; + + var expressionConfiguration = new ExpressionConfiguration(); + var childExpressionBlockBuilder = Mock.Of(); + Mock.Get(childExpressionBlockBuilder) + .SetupGet(x => x.Expressions) + .Returns(Array.Empty()); + var expressionBuilderFactory = Mock.Of(); + Mock.Get(expressionBuilderFactory) + .Setup(x => x.CreateExpressionBlockBuilder(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(childExpressionBlockBuilder); + IExpressionBlockBuilder actualChildExpressionBlockBuilder = null; + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var action = FluentActions.Invoking(() => expressionBlockBuilder.Block(scopeName, b => + { + actualChildExpressionBlockBuilder = b; + })); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void Block_GivenNotNullOrEmptyScopeAndNullImplementationBuilder_ThrowsArgumentNullException() + { + // Arrange + var scopeName = "testScope"; + + var expressionConfiguration = new ExpressionConfiguration(); + var childExpressionBlockBuilder = Mock.Of(); + var expressionBuilderFactory = Mock.Of(); + Mock.Get(expressionBuilderFactory) + .Setup(x => x.CreateExpressionBlockBuilder(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(childExpressionBlockBuilder); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var action = FluentActions.Invoking(() => expressionBlockBuilder.Block(scopeName, null)); + + // Assert + action.Should().Throw() + .Which.ParamName.Should().Be("implementationBuilder"); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void Block_GivenNullOrEmptyScopeAndNotNullImplementationBuilderWithLogicToAddExpressions_ReturnsExpression(string scopeName) + { + // Arrange + var expressionConfiguration = new ExpressionConfiguration(); + var childExpressionBlockBuilder = Mock.Of(); + Mock.Get(childExpressionBlockBuilder) + .SetupGet(x => x.Expressions) + .Returns(() => + { + var valueToReturnExpression = Expression.Constant(1); + var labelTarget = Expression.Label("label"); + var returnExpression = Expression.Return(labelTarget, valueToReturnExpression); + + return new Expression[] { returnExpression }; + }); + var expressionBuilderFactory = Mock.Of(); + Mock.Get(expressionBuilderFactory) + .Setup(x => x.CreateExpressionBlockBuilder(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(childExpressionBlockBuilder); + IExpressionBlockBuilder actualChildExpressionBlockBuilder = null; + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.Block(scopeName, b => + { + actualChildExpressionBlockBuilder = b; + }); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + actualChildExpressionBlockBuilder.Should().NotBeNull() + .And.BeSameAs(childExpressionBlockBuilder); + } + + [Fact] + public void Call_GivenInstanceExpressionMethodInfo_ReturnsExpression() + { + // Arrange + var instanceExpression = Expression.Variable(typeof(int)); + var methodInfo = typeof(int).GetMethod(nameof(ToString), Array.Empty()); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.Call(instanceExpression, methodInfo); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + } + + [Fact] + public void Call_GivenInstanceExpressionMethodInfoAndParameters_ReturnsExpression() + { + // Arrange + var instanceExpression = Expression.Variable(typeof(int)); + var methodInfo = typeof(int).GetMethod(nameof(ToString), new[] { typeof(string) }); + var parameterExpressions = new Expression[] { Expression.Constant("format") }; + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.Call(instanceExpression, methodInfo, parameterExpressions); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + } + + [Fact] + public void Constant_GivenValueAndType_ReturnsExpression() + { + // Arrange + var constantValue = true; + var constantType = typeof(bool); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.Constant(constantValue, constantType); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + } + + [Fact] + public void Constant_GivenValueWithGenericType_ReturnsExpression() + { + // Arrange + var constantValue = true; + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.Constant(constantValue); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + } + + [Fact] + public void ConvertChecked_GivenExpressionAndTypeAsGeneric_ReturnsExpression() + { + // Arrange + var expressionToConvert = Expression.Constant("test", typeof(object)); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.ConvertChecked(expressionToConvert); + + // Assert + actual.Should().NotBeNull(); + actual.NodeType.Should().Be(ExpressionType.Convert); + } + + [Fact] + public void ConvertChecked_GivenExpressionAndTypeAsParameter_ReturnsExpression() + { + // Arrange + var expressionToConvert = Expression.Constant("test", typeof(object)); + var typeToConvert = typeof(string); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.ConvertChecked(expressionToConvert, typeToConvert); + + // Assert + actual.Should().NotBeNull(); + actual.NodeType.Should().Be(ExpressionType.Convert); + } + + [Fact] + public void CreateLabelTarget_GivenNameWithoutParentAndLabelNameAlreadyExists_ThrowsInvalidOperationException() + { + // Arrange + var labelTargetName = "testLabel"; + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + expressionBlockBuilder.CreateLabelTarget(labelTargetName); + + // Act + var action = FluentActions.Invoking(() => expressionBlockBuilder.CreateLabelTarget(labelTargetName)); + + // Assert + action.Should().ThrowExactly(); + } + + [Fact] + public void CreateLabelTarget_GivenNameWithoutParentAndLabelNameDoesNotExist_AddsNewLabelTargetAndReturns() + { + // Arrange + var labelTargetName = "testLabel"; + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.CreateLabelTarget(labelTargetName); + + // Assert + actual.Should().NotBeNull(); + actual.Name.Should().Be(labelTargetName); + expressionBlockBuilder.LabelTargets.Should().HaveCount(1) + .And.ContainKey(labelTargetName) + .And.ContainValue(actual); + } + + [Fact] + public void CreateLabelTarget_GivenNameWithParentAndLabelNameAlreadyExists_ThrowsInvalidOperationException() + { + // Arrange + var labelTargetName = "testLabel"; + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + var parent = Mock.Of(); + Mock.Get(parent) + .Setup(x => x.CreateLabelTarget(labelTargetName)) + .Returns(Expression.Label(labelTargetName)); + var scopeName = "childScopeTest"; + + var expressionBlockBuilder = new ExpressionBlockBuilder(scopeName, parent, expressionBuilderFactory, expressionConfiguration); + expressionBlockBuilder.CreateLabelTarget(labelTargetName); + + // Act + var action = FluentActions.Invoking(() => expressionBlockBuilder.CreateLabelTarget(labelTargetName)); + + // Assert + action.Should().ThrowExactly() + .Which.Message.Should().Contain(scopeName); + } + + [Fact] + public void CreateLabelTarget_GivenNameWithParentAndLabelNameDoesNotExist_AddsNewLabelTargetAndReturns() + { + // Arrange + var labelTargetName = "testLabel"; + var scopeName = "childScopeTest"; + var parentLabelTargetName = $"{scopeName}_{labelTargetName}"; + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + var parent = Mock.Of(); + string actualParentLabelTargetName = null; + Mock.Get(parent) + .Setup(x => x.CreateLabelTarget(It.IsAny())) + .Returns(name => + { + actualParentLabelTargetName = name; + return Expression.Label(name); + }); + + var expressionBlockBuilder = new ExpressionBlockBuilder(scopeName, parent, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.CreateLabelTarget(labelTargetName); + + // Assert + actual.Should().NotBeNull(); + actual.Name.Should().Be(parentLabelTargetName); + expressionBlockBuilder.LabelTargets.Should().HaveCount(1) + .And.ContainKey(labelTargetName) + .And.ContainValue(actual); + actualParentLabelTargetName.Should().Be(parentLabelTargetName); + } + + [Fact] + public void CreateVariable_GivenNameAndTypeAsGenericWithoutParentAndNameDoesNotExist_AddsNewVariableAndReturns() + { + // Arrange + var variableName = "testVariable"; + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.CreateVariable(variableName); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + actual.Name.Should().Be(variableName); + actual.Type.Should().Be(typeof(int)); + expressionBlockBuilder.Variables.Should().HaveCount(1) + .And.ContainKey(variableName) + .And.ContainValue(actual); + } + + [Fact] + public void CreateVariable_GivenNameAndTypeWithoutParentAndNameAlreadyExists_ThrowsInvalidOperationException() + { + // Arrange + var variableName = "testVariable"; + var variableType = typeof(int); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + expressionBlockBuilder.CreateVariable(variableName, variableType); + + // Act + var action = FluentActions.Invoking(() => expressionBlockBuilder.CreateVariable(variableName, variableType)); + + // Assert + action.Should().ThrowExactly(); + } + + [Fact] + public void CreateVariable_GivenNameAndTypeWithoutParentAndNameDoesNotExist_AddsNewVariableAndReturns() + { + // Arrange + var variableName = "testVariable"; + var variableType = typeof(int); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.CreateVariable(variableName, variableType); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + actual.Name.Should().Be(variableName); + actual.Type.Should().Be(variableType); + expressionBlockBuilder.Variables.Should().HaveCount(1) + .And.ContainKey(variableName) + .And.ContainValue(actual); + } + + [Fact] + public void CreateVariable_GivenNameAndTypeWithParentAndNameAlreadyExists_ThrowsInvalidOperationException() + { + // Arrange + var variableName = "testVariable"; + var variableType = typeof(int); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + var parent = Mock.Of(); + Mock.Get(parent) + .Setup(x => x.CreateVariable(variableName, variableType)) + .Returns(Expression.Variable(variableType, variableName)); + var scopeName = "childScopeTest"; + + var expressionBlockBuilder = new ExpressionBlockBuilder(scopeName, parent, expressionBuilderFactory, expressionConfiguration); + expressionBlockBuilder.CreateVariable(variableName, variableType); + + // Act + var action = FluentActions.Invoking(() => expressionBlockBuilder.CreateVariable(variableName, variableType)); + + // Assert + action.Should().ThrowExactly() + .Which.Message.Should().Contain(scopeName); + } + + [Fact] + public void CreateVariable_GivenNameAndTypeWithParentAndNameDoesNotExist_AddsNewVariableAndReturns() + { + // Arrange + var variableName = "testVariable"; + var variableType = typeof(int); + var scopeName = "childScopeTest"; + var parentLabelTargetName = $"{scopeName}_{variableName}"; + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + var parent = Mock.Of(); + string actualParentLabelTargetName = null; + Mock.Get(parent) + .Setup(x => x.CreateVariable(It.IsAny(), It.IsAny())) + .Returns((name, type) => + { + actualParentLabelTargetName = name; + return Expression.Variable(type, name); + }); + + var expressionBlockBuilder = new ExpressionBlockBuilder(scopeName, parent, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.CreateVariable(variableName, variableType); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + actual.Name.Should().Be(parentLabelTargetName); + actual.Type.Should().Be(variableType); + expressionBlockBuilder.Variables.Should().HaveCount(1) + .And.ContainKey(variableName) + .And.ContainValue(actual); + actualParentLabelTargetName.Should().Be(parentLabelTargetName); + } + + [Fact] + public void Empty_NoConditions_ReturnsExpression() + { + // Arrange + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.Empty(); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + } + + [Fact] + public void Equal_GivenLeftAndRightExpressions_ReturnsExpression() + { + // Arrange + var leftExpression = Expression.Parameter(typeof(int), "x"); + var rightExpression = Expression.Constant(0, typeof(int)); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.Equal(leftExpression, rightExpression); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + actual.NodeType.Should().Be(ExpressionType.Equal); + var actualBinary = actual as BinaryExpression; + actualBinary.Left.Should().Be(leftExpression); + actualBinary.Right.Should().Be(rightExpression); + } + + [Fact] + public void GetLabelTarget_GivenNameForExistentLabelTarget_ReturnsLabelTarget() + { + // Arrange + var labelTargetName = "testLabel"; + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + var expected = expressionBlockBuilder.CreateLabelTarget(labelTargetName); + + // Act + var actual = expressionBlockBuilder.GetLabelTarget(labelTargetName); + + // Assert + actual.Should().NotBeNull() + .And.BeSameAs(expected); + actual.Name.Should().Be(labelTargetName); + } + + [Fact] + public void GetLabelTarget_GivenNameForNonExistentLabelTarget_ThrowsKeyNotFoundException() + { + // Arrange + var labelTargetName = "testLabel"; + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var action = FluentActions.Invoking(() => expressionBlockBuilder.GetLabelTarget(labelTargetName)); + + // Assert + action.Should().ThrowExactly() + .Which.Message.Should().Contain(labelTargetName); + } + + [Fact] + public void GetParameter_GivenNameForExistentParameter_ReturnsParameterExpression() + { + // Arrange + var parameterName = "testParameter"; + var expected = Expression.Parameter(typeof(int), parameterName); + + var expressionConfiguration = new ExpressionConfiguration + { + Parameters = new Dictionary + { + { parameterName, expected } + } + }; + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.GetParameter(parameterName); + + // Assert + actual.Should().NotBeNull() + .And.BeSameAs(expected); + actual.Name.Should().Be(parameterName); + } + + [Fact] + public void GetParameter_GivenNameForNonExistentParameter_ThrowsKeyNotFoundException() + { + // Arrange + var parameterName = "testParameter"; + + var expressionConfiguration = new ExpressionConfiguration + { + Parameters = new Dictionary() + }; + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var action = FluentActions.Invoking(() => expressionBlockBuilder.GetParameter(parameterName)); + + // Assert + action.Should().ThrowExactly() + .Which.Message.Should().Contain(parameterName); + } + + [Fact] + public void GetVariable_GivenNameForExistentVariable_ReturnsParameterExpression() + { + // Arrange + var variableName = "testVariable"; + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + var expected = expressionBlockBuilder.CreateVariable(variableName); + + // Act + var actual = expressionBlockBuilder.GetVariable(variableName); + + // Assert + actual.Should().NotBeNull() + .And.BeSameAs(expected); + actual.Name.Should().Be(variableName); + } + + [Fact] + public void GetVariable_GivenNameForNonExistentVariable_ThrowsKeyNotFoundException() + { + // Arrange + var variableName = "testVariable"; + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var action = FluentActions.Invoking(() => expressionBlockBuilder.GetVariable(variableName)); + + // Assert + action.Should().ThrowExactly() + .Which.Message.Should().Contain(variableName); + } + + [Fact] + public void Goto_GivenLabelTarget_CreatesGotoExpressionAndAddsToExpressionsList() + { + // Arrange + var labelTarget = Expression.Label("testLabelTarget"); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + expressionBlockBuilder.Goto(labelTarget); + + // Assert + expressionBlockBuilder.Expressions.Should().HaveCount(1); + var gotoExpression = expressionBlockBuilder.Expressions[0] as GotoExpression; + gotoExpression.Should().NotBeNull(); + gotoExpression.Target.Should().BeSameAs(labelTarget); + } + + [Fact] + public void GreaterThan_GivenLeftAndRightExpressions_ReturnsExpression() + { + // Arrange + var leftExpression = Expression.Parameter(typeof(int), "x"); + var rightExpression = Expression.Constant(0, typeof(int)); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.GreaterThan(leftExpression, rightExpression); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + actual.NodeType.Should().Be(ExpressionType.GreaterThan); + var actualBinary = actual as BinaryExpression; + actualBinary.Left.Should().Be(leftExpression); + actualBinary.Right.Should().Be(rightExpression); + } + + [Fact] + public void GreaterThanOrEqual_GivenLeftAndRightExpressions_ReturnsExpression() + { + // Arrange + var leftExpression = Expression.Parameter(typeof(int), "x"); + var rightExpression = Expression.Constant(0, typeof(int)); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.GreaterThanOrEqual(leftExpression, rightExpression); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + actual.NodeType.Should().Be(ExpressionType.GreaterThanOrEqual); + var actualBinary = actual as BinaryExpression; + actualBinary.Left.Should().Be(leftExpression); + actualBinary.Right.Should().Be(rightExpression); + } + + [Fact] + public void If_GivenNullTestExpressionAndThenExpression_ThrowsArgumentNullException() + { + // Arrange + var resultExpression = Expression.Variable(typeof(bool), "result"); + var testExpression = Expression.Equal(Expression.Variable(typeof(int), "x"), Expression.Constant(0, typeof(int))); + var thenExpression = Expression.Assign(resultExpression, Expression.Constant(true)); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var action = FluentActions.Invoking(() => expressionBlockBuilder.If(null, b => thenExpression)); + + // Assert + action.Should().Throw() + .Which.ParamName.Should().Be("testExpressionBuilder"); + } + + [Fact] + public void If_GivenTestExpressionAndNullThenExpression_ThrowsArgumentNullException() + { + // Arrange + var resultExpression = Expression.Variable(typeof(bool), "result"); + var testExpression = Expression.Equal(Expression.Variable(typeof(int), "x"), Expression.Constant(0, typeof(int))); + var thenExpression = Expression.Assign(resultExpression, Expression.Constant(true)); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var action = FluentActions.Invoking(() => expressionBlockBuilder.If(b => testExpression, null)); + + // Assert + action.Should().Throw() + .Which.ParamName.Should().Be("thenExpressionBuilder"); + } + + [Fact] + public void If_GivenTestExpressionAndThenExpression_CreatesConditionalExpressionAndAddsToExpressionsList() + { + // Arrange + var resultExpression = Expression.Variable(typeof(bool), "result"); + var testExpression = Expression.Equal(Expression.Variable(typeof(int), "x"), Expression.Constant(0, typeof(int))); + var thenExpression = Expression.Assign(resultExpression, Expression.Constant(true)); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + expressionBlockBuilder.If(b => testExpression, b => thenExpression); + + // Assert + expressionBlockBuilder.Expressions.Count.Should().Be(1); + var expression = expressionBlockBuilder.Expressions[0]; + expression.Should().NotBeNull() + .And.BeAssignableTo(); + expression.NodeType.Should().Be(ExpressionType.Conditional); + var conditionalExpression = expression as ConditionalExpression; + conditionalExpression.Test.Should().Be(testExpression); + conditionalExpression.IfTrue.Should().Be(thenExpression); + conditionalExpression.IfFalse.Should().NotBeNull(); + conditionalExpression.IfFalse.NodeType.Should().Be(ExpressionType.Default); + } + + [Fact] + public void If_GivenTestExpressionThenExpressionAndElseExpression_CreatesConditionalExpressionAndAddsToExpressionsList() + { + // Arrange + var resultExpression = Expression.Variable(typeof(bool), "result"); + var testExpression = Expression.Equal(Expression.Variable(typeof(int), "x"), Expression.Constant(0, typeof(int))); + var thenExpression = Expression.Assign(resultExpression, Expression.Constant(true)); + var elseExpression = Expression.Assign(resultExpression, Expression.Constant(false)); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + expressionBlockBuilder.If(b => testExpression, b => thenExpression, b => elseExpression); + + // Assert + expressionBlockBuilder.Expressions.Count.Should().Be(1); + var expression = expressionBlockBuilder.Expressions[0]; + expression.Should().NotBeNull() + .And.BeAssignableTo(); + expression.NodeType.Should().Be(ExpressionType.Conditional); + var conditionalExpression = expression as ConditionalExpression; + conditionalExpression.Test.Should().Be(testExpression); + conditionalExpression.IfTrue.Should().Be(thenExpression); + conditionalExpression.IfFalse.Should().Be(elseExpression); + } + + [Fact] + public void Label_GivenLabelTarget_CreatesLabelExpressionAndAddsToExpressionsList() + { + // Arrange + var labelTarget = Expression.Label("testLabel"); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + expressionBlockBuilder.Label(labelTarget); + + // Assert + expressionBlockBuilder.Expressions.Should().HaveCount(1); + var expression = expressionBlockBuilder.Expressions[0]; + expression.Should().NotBeNull() + .And.BeAssignableTo(); + var labelExpression = expression as LabelExpression; + labelExpression.Target.Should().Be(labelTarget); + } + + [Fact] + public void LessThan_GivenLeftAndRightExpressions_ReturnsExpression() + { + // Arrange + var leftExpression = Expression.Parameter(typeof(int), "x"); + var rightExpression = Expression.Constant(0, typeof(int)); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.LessThan(leftExpression, rightExpression); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + actual.NodeType.Should().Be(ExpressionType.LessThan); + var actualBinary = actual as BinaryExpression; + actualBinary.Left.Should().Be(leftExpression); + actualBinary.Right.Should().Be(rightExpression); + } + + [Fact] + public void LessThanOrEqual_GivenLeftAndRightExpressions_ReturnsExpression() + { + // Arrange + var leftExpression = Expression.Parameter(typeof(int), "x"); + var rightExpression = Expression.Constant(0, typeof(int)); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.LessThanOrEqual(leftExpression, rightExpression); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + actual.NodeType.Should().Be(ExpressionType.LessThanOrEqual); + var actualBinary = actual as BinaryExpression; + actualBinary.Left.Should().Be(leftExpression); + actualBinary.Right.Should().Be(rightExpression); + } + + [Fact] + public void Not_GivenExpression_ReturnsExpression() + { + // Arrange + var originalExpression = Expression.Constant(false, typeof(bool)); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.Not(originalExpression); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + var unaryExpression = actual as UnaryExpression; + unaryExpression.Operand.Should().Be(originalExpression); + } + + [Fact] + public void NotEqual_GivenLeftAndRightExpressions_ReturnsExpression() + { + // Arrange + var leftExpression = Expression.Parameter(typeof(int), "x"); + var rightExpression = Expression.Constant(0, typeof(int)); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.NotEqual(leftExpression, rightExpression); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + actual.NodeType.Should().Be(ExpressionType.NotEqual); + var actualBinary = actual as BinaryExpression; + actualBinary.Left.Should().Be(leftExpression); + actualBinary.Right.Should().Be(rightExpression); + } + + [Fact] + public void OrElse_GivenCollectionOfExpressionsWithMultipleExpressions_ReturnsExpression() + { + // Arrange + var expressions = new Expression[] + { + Expression.Constant(true), + Expression.Constant(true), + Expression.Constant(true), + Expression.Constant(true), + Expression.Constant(true), + }; + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.OrElse(expressions); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + actual.NodeType.Should().Be(ExpressionType.OrElse); + + var actualAsBinaryExpression = actual as BinaryExpression; + actualAsBinaryExpression.Right.Should().BeAssignableTo(); + actualAsBinaryExpression.Left.Should().BeAssignableTo(); + var chainedExpressionsCount = 1; + while (actualAsBinaryExpression.Left is BinaryExpression) + { + actualAsBinaryExpression.Right.Should().BeAssignableTo(); + actualAsBinaryExpression = actualAsBinaryExpression.Left as BinaryExpression; + chainedExpressionsCount++; + } + + actualAsBinaryExpression.Left.Should().BeAssignableTo(); + actualAsBinaryExpression.Right.Should().BeAssignableTo(); + chainedExpressionsCount.Should().Be(4); + } + + [Fact] + public void OrElse_GivenCollectionOfExpressionsWithNoneExpressions_ThrowsArgumentException() + { + // Arrange + var expressions = Array.Empty(); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var action = FluentActions.Invoking(() => expressionBlockBuilder.OrElse(expressions)); + + // Assert + action.Should().ThrowExactly() + .Which.ParamName.Should().Be("expressions"); + } + + [Fact] + public void OrElse_GivenLeftAndRightExpressions_ReturnsExpression() + { + // Arrange + var leftExpression = Expression.Constant(true); + var rightExpression = Expression.Constant(false); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var actual = expressionBlockBuilder.OrElse(leftExpression, rightExpression); + + // Assert + actual.Should().NotBeNull() + .And.BeAssignableTo(); + actual.NodeType.Should().Be(ExpressionType.OrElse); + + var actualAsBinaryExpression = actual as BinaryExpression; + actualAsBinaryExpression.Left.Should().Be(leftExpression); + actualAsBinaryExpression.Right.Should().Be(rightExpression); + } + + [Fact] + public void OrElse_GivenNullCollectionOfExpressions_ThrowsArgumentNullException() + { + // Arrange + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var action = FluentActions.Invoking(() => expressionBlockBuilder.OrElse(null)); + + // Assert + action.Should().ThrowExactly() + .Which.ParamName.Should().Be("expressions"); + } + + [Fact] + public void Return_GivenNullReturnValueExpression_ThrowsArgumentNullException() + { + // Arrange + var expressionConfiguration = new ExpressionConfiguration() + { + ReturnLabelTarget = Expression.Label(typeof(string)), + }; + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var action = FluentActions.Invoking(() => expressionBlockBuilder.Return(null)); + + // Assert + action.Should().Throw() + .Which.ParamName.Should().Be("returnValueExpression"); + } + + [Fact] + public void Return_GivenReturnValueExpression_CreatesReturnExpressionAndAddsToExpressionsList() + { + // Arrange + var returnValueExpression = Expression.Constant("testValue"); + + var expressionConfiguration = new ExpressionConfiguration() + { + ReturnLabelTarget = Expression.Label(typeof(string)), + }; + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + expressionBlockBuilder.Return(returnValueExpression); + + // Assert + expressionBlockBuilder.Expressions.Should().HaveCount(1); + var returnExpression = expressionBlockBuilder.Expressions[0]; + returnExpression.Should().NotBeNull() + .And.BeAssignableTo(); + var gotoExpression = returnExpression as GotoExpression; + gotoExpression.Target.Should().NotBeNull() + .And.BeSameAs(expressionConfiguration.ReturnLabelTarget); + gotoExpression.Value.Should().Be(returnValueExpression); + } + + [Fact] + public void Switch_GivenNullSwitchValueExpressionAndSwitchExpressionBuilder_ThrowsArgumentNullException() + { + // Arrange + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var action = FluentActions.Invoking(() => expressionBlockBuilder.Switch(null, b => { })); + + // Assert + action.Should().Throw() + .Which.ParamName.Should().Be("switchValueExpression"); + } + + [Fact] + public void Switch_GivenSwitchValueExpressionAndNullSwitchExpressionBuilder_ThrowsArgumentNullException() + { + // Arrange + var switchValueExpression = Expression.Variable(typeof(string), "input"); + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + var action = FluentActions.Invoking(() => expressionBlockBuilder.Switch(switchValueExpression, null)); + + // Assert + action.Should().Throw() + .Which.ParamName.Should().Be("expressionSwitchBuilderAction"); + } + + [Fact] + public void Switch_GivenSwitchValueExpressionAndSwitchExpressionBuilder_CreatesSwitchExpressionAndAddsToExpressionsList() + { + // Arrange + var switchValueExpression = Expression.Variable(typeof(string), "input"); + var resultVariable = Expression.Variable(typeof(bool), "result"); + var expectedDefaultBody = Expression.Assign(resultVariable, Expression.Constant(false)); + var expectedSwitchCases = new[] + { + Expression.SwitchCase(Expression.Assign(resultVariable, Expression.Constant(true)), Expression.Constant("A")), + Expression.SwitchCase(Expression.Assign(resultVariable, Expression.Constant(false)), Expression.Constant("B")), + Expression.SwitchCase(Expression.Assign(resultVariable, Expression.Constant(true)), Expression.Constant("C")), + }; + + var expressionConfiguration = new ExpressionConfiguration(); + var expressionSwitchBuilder = Mock.Of(); + Mock.Get(expressionSwitchBuilder) + .SetupGet(x => x.DefaultBody) + .Returns(expectedDefaultBody); + Mock.Get(expressionSwitchBuilder) + .SetupGet(x => x.SwitchCases) + .Returns(expectedSwitchCases); + var expressionBuilderFactory = Mock.Of(); + Mock.Get(expressionBuilderFactory) + .Setup(x => x.CreateExpressionSwitchBuilder(It.IsAny())) + .Returns(expressionSwitchBuilder); + + var expressionBlockBuilder = new ExpressionBlockBuilder(string.Empty, null, expressionBuilderFactory, expressionConfiguration); + + // Act + expressionBlockBuilder.Switch(switchValueExpression, b => { }); + + // Assert + expressionBlockBuilder.Expressions.Should().HaveCount(1); + var expression = expressionBlockBuilder.Expressions[0]; + expression.Should().NotBeNull() + .And.BeAssignableTo(); + var switchExpression = expression as SwitchExpression; + switchExpression.Should().NotBeNull(); + switchExpression.SwitchValue.Should().BeSameAs(switchValueExpression); + switchExpression.Cases.Should().BeEquivalentTo(expectedSwitchCases); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionBuilderTests.cs new file mode 100644 index 00000000..44f436a1 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionBuilderTests.cs @@ -0,0 +1,327 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ExpressionBuilders +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using FluentAssertions; + using Moq; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Xunit; + + public class ExpressionBuilderTests + { + [Fact] + public void Build_GivenExpressionConfiguration_ReturnsExpressionResultWithBuiltExpression() + { + // Arrange + var testParameter = Expression.Parameter(typeof(int), "input"); + var testVariable = Expression.Variable(typeof(int), "result"); + var testReturnLabelTarget = Expression.Label(typeof(int)); + + var expressionConfiguration = new ExpressionConfiguration + { + ExpressionName = "a", + Expressions = new List + { + Expression.Assign(testVariable, testParameter), + Expression.Return(testReturnLabelTarget, testVariable), + }, + LabelTargets = new Dictionary(), + Parameters = new Dictionary { { "input", testParameter } }, + ReturnDefaultValue = 0, + ReturnLabelTarget = testReturnLabelTarget, + ReturnType = typeof(int), + Variables = new Dictionary { { "result", testVariable } }, + }; + + var expressionBuilderFactory = Mock.Of(); + + var configuredExpressionBuilder = new ExpressionBuilder(expressionConfiguration, expressionBuilderFactory); + + // Act + var result = configuredExpressionBuilder.Build(); + + // Assert + result.Should().NotBeNull(); + result.ExpressionName.Should().Be("a"); + result.Implementation.Should().NotBeNull(); + result.Parameters.Should().NotBeNullOrEmpty() + .And.BeEquivalentTo(expressionConfiguration.Parameters.Values); + result.ReturnType.Should().Be(typeof(int)); + + var compiledExpression = Expression.Lambda>(result.Implementation, result.Parameters).Compile(); + var compiledExpressionResult = compiledExpression.Invoke(4); + compiledExpressionResult.Should().Be(4); + + configuredExpressionBuilder.ExpressionConfiguration.Should().BeSameAs(expressionConfiguration); + } + + [Fact] + public void HavingReturn_GivenNotNullTypeAndDefaultValue_ReturnsExpressionImplementationBuilder() + { + // Arrange + var type = typeof(object); + object defaultValue = null; + + var expressionName = "TestExpression"; + var expressionConfiguration = new ExpressionConfiguration + { + ExpressionName = expressionName, + }; + var expressionBuilderFactory = Mock.Of(); + + var expressionBuilder = new ExpressionBuilder(expressionConfiguration, expressionBuilderFactory); + + // Act + var actual = expressionBuilder.HavingReturn(type, defaultValue); + + // Assert + actual.Should().NotBeNull() + .And.BeSameAs(expressionBuilder); + expressionConfiguration.ReturnDefaultValue.Should().Be(defaultValue); + expressionConfiguration.ReturnType.Should().Be(type); + expressionConfiguration.ReturnLabelTarget.Should().NotBeNull(); + expressionConfiguration.ReturnLabelTarget.Name.Should().Contain(expressionName); + } + + [Fact] + public void HavingReturn_GivenNullTypeAndDefaultValue_ReturnsExpressionImplementationBuilder() + { + // Arrange + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBuilder = new ExpressionBuilder(expressionConfiguration, expressionBuilderFactory); + + // Act + var action = FluentActions.Invoking(() => expressionBuilder.HavingReturn(null, null)); + + // Assert + action.Should().Throw() + .Which.ParamName.Should().Be("type"); + } + + [Fact] + public void HavingReturn_GivenTypeAsGenericParameterAndDefaultValue_ReturnsExpressionImplementationBuilder() + { + // Arrange + object defaultValue = null; + + var expressionName = "TestExpression"; + var expressionConfiguration = new ExpressionConfiguration + { + ExpressionName = expressionName, + }; + var expressionBuilderFactory = Mock.Of(); + + var expressionBuilder = new ExpressionBuilder(expressionConfiguration, expressionBuilderFactory); + + // Act + var actual = expressionBuilder.HavingReturn(defaultValue); + + // Assert + actual.Should().NotBeNull() + .And.BeSameAs(expressionBuilder); + expressionConfiguration.ReturnDefaultValue.Should().Be(defaultValue); + expressionConfiguration.ReturnType.Should().Be(typeof(object)); + expressionConfiguration.ReturnLabelTarget.Should().NotBeNull(); + expressionConfiguration.ReturnLabelTarget.Name.Should().Contain(expressionName); + } + + [Fact] + public void NewExpression_GivenNonNullEmptyOrWhitespaceName_ReturnsNewNamedExpressionBuilder() + { + // Arrange + var expressionName = "TestExpression"; + + // Act + var actual = ExpressionBuilder.NewExpression(expressionName); + + // Assert + actual.Should().NotBeNull() + .And.BeOfType(); + + var expressionBuilder = actual as ExpressionBuilder; + expressionBuilder.ExpressionConfiguration.ExpressionName.Should().Be(expressionName); + } + + [Fact] + public void NewExpression_GivenNullEmptyOrWhitespaceName_ThrowsArgumentException() + { + // Arrange + var expressionName = ""; + + // Act + var action = FluentActions.Invoking(() => ExpressionBuilder.NewExpression(expressionName)); + + // Assert + action.Should().ThrowExactly() + .Which.ParamName.Should().Be("name"); + } + + [Fact] + public void SetImplementation_GivenNonNullBuilderAction_ReturnsConfiguredExpressionBuilder() + { + // Arrange + var testParameter = Expression.Parameter(typeof(int), "input"); + var testReturnLabelTarget = Expression.Label(typeof(int)); + + var expressionConfiguration = new ExpressionConfiguration + { + ExpressionName = "a", + Parameters = new Dictionary { { "input", testParameter } }, + ReturnDefaultValue = 0, + ReturnLabelTarget = testReturnLabelTarget, + ReturnType = typeof(int), + }; + + var testExpressions = new List() + { + Expression.Constant(1), + }; + var testVariables = new Dictionary() + { + { "result", Expression.Variable(typeof(int), "result") }, + }; + var testLabelTargets = new Dictionary() + { + { "Return", Expression.Label(typeof(int)) }, + }; + var executedBuilderAction = false; + + Action builderAction = b => + { + executedBuilderAction = true; + }; + + var implementationExpressionBuilder = Mock.Of(); + Mock.Get(implementationExpressionBuilder) + .SetupGet(x => x.LabelTargets) + .Returns(testLabelTargets); + Mock.Get(implementationExpressionBuilder) + .SetupGet(x => x.Variables) + .Returns(testVariables); + Mock.Get(implementationExpressionBuilder) + .SetupGet(x => x.Expressions) + .Returns(testExpressions); + + var expressionBuilderFactory = Mock.Of(); + Mock.Get(expressionBuilderFactory) + .Setup(x => x.CreateExpressionBlockBuilder(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(implementationExpressionBuilder); + + var expressionBuilder = new ExpressionBuilder( + expressionConfiguration, + expressionBuilderFactory); + + // Act + var actual = expressionBuilder.SetImplementation(builderAction); + + // Assert + actual.Should().NotBeNull() + .And.BeSameAs(expressionBuilder); + executedBuilderAction.Should().BeTrue(); + expressionConfiguration.LabelTargets.Should().NotBeNull() + .And.BeSameAs(testLabelTargets); + expressionConfiguration.Variables.Should().NotBeNull() + .And.BeSameAs(testVariables); + expressionConfiguration.Expressions.Should().NotBeNull() + .And.BeSameAs(testExpressions); + } + + [Fact] + public void SetImplementation_GivenNullBuilderAction_ThrowsArgumentNullException() + { + // Arrange + var expressionConfiguration = new ExpressionConfiguration(); + + Action builderAction = null; + + var expressionBuilderFactory = Mock.Of(); + + var expressionBuilder = new ExpressionBuilder( + expressionConfiguration, + expressionBuilderFactory); + + // Act + var action = FluentActions.Invoking(() => expressionBuilder.SetImplementation(builderAction)); + + // Assert + action.Should().Throw() + .Which.ParamName.Should().Be("builder"); + } + + [Fact] + public void WithoutParameters_NoConditions_ReturnsExpressionReturnBuilder() + { + // Arrange + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBuilder = new ExpressionBuilder(expressionConfiguration, expressionBuilderFactory); + + // Act + var actual = expressionBuilder.WithoutParameters(); + + // Assert + actual.Should().NotBeNull() + .And.BeSameAs(expressionBuilder); + expressionConfiguration.Parameters.Should().NotBeNull() + .And.BeEmpty(); + } + + [Fact] + public void WithParameters_GivenNotNullBuilderAction_ReturnsExpressionReturnBuilder() + { + // Arrange + var expressionConfiguration = new ExpressionConfiguration(); + var parameterExpressions = new Dictionary(); + var expressionParametersConfiguration = Mock.Of(); + Mock.Get(expressionParametersConfiguration) + .Setup(x => x.CreateParameter(It.IsAny())) + .Returns((IInvocation i) => + { + var parameterExpression = Expression.Parameter(i.Method.GetGenericArguments()[0], (string)i.Arguments[0]); + parameterExpressions.Add(parameterExpression.Name, parameterExpression); + return parameterExpression; + }); + Mock.Get(expressionParametersConfiguration) + .SetupGet(x => x.Parameters) + .Returns(parameterExpressions); + var expressionBuilderFactory = Mock.Of(); + Mock.Get(expressionBuilderFactory) + .Setup(x => x.CreateExpressionParametersConfiguration()) + .Returns(expressionParametersConfiguration); + + var expressionBuilder = new ExpressionBuilder(expressionConfiguration, expressionBuilderFactory); + + // Act + var actual = expressionBuilder.WithParameters(c => + { + c.CreateParameter("testParameter"); + }); + + // Assert + actual.Should().NotBeNull() + .And.BeSameAs(expressionBuilder); + expressionConfiguration.Parameters.Should().ContainKey("testParameter"); + } + + [Fact] + public void WithParameters_GivenNullBuilderAction_ThrowsArgumentNullException() + { + // Arrange + var expressionConfiguration = new ExpressionConfiguration(); + var expressionBuilderFactory = Mock.Of(); + + var expressionBuilder = new ExpressionBuilder(expressionConfiguration, expressionBuilderFactory); + + // Act + var action = FluentActions.Invoking(() => expressionBuilder.WithParameters(null)); + + // Assert + action.Should().Throw() + .Which.ParamName.Should().Be("parametersConfigurationAction"); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionParametersConfigurationTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionParametersConfigurationTests.cs new file mode 100644 index 00000000..0f8caf21 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionParametersConfigurationTests.cs @@ -0,0 +1,72 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ExpressionBuilders +{ + using System; + using FluentAssertions; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Xunit; + + public class ExpressionParametersConfigurationTests + { + [Fact] + public void CreateParameter_GivenNameAndTypeAndParameterAlreadyExists_ThrowsInvalidOperationException() + { + // Arrange + var expectedName = "testParameter"; + var expectedType = typeof(string); + + var expressionParametersConfiguration = new ExpressionParametersConfiguration(); + expressionParametersConfiguration.CreateParameter(expectedName, expectedType); + + // Act + var action = FluentActions.Invoking(() => expressionParametersConfiguration.CreateParameter(expectedName, expectedType)); + + // Assert + action.Should().Throw() + .Which.Message.Should().Contain(expectedName); + } + + [Fact] + public void CreateParameter_GivenNameAndTypeAndParameterDoesNotExist_AddsNewParameterAndReturns() + { + // Arrange + var expectedName = "testParameter"; + var expectedType = typeof(string); + + var expressionParametersConfiguration = new ExpressionParametersConfiguration(); + + // Act + var actual = expressionParametersConfiguration.CreateParameter(expectedName, expectedType); + + // Assert + actual.Should().NotBeNull(); + actual.Name.Should().Be(expectedName); + actual.Type.Should().Be(expectedType); + + expressionParametersConfiguration.Parameters.Should().HaveCount(1) + .And.ContainKey(expectedName) + .And.ContainValue(actual); + } + + [Fact] + public void CreateParameter_GivenNameAndTypeAsGenericAndParameterDoesNotExist_AddsNewParameterAndReturns() + { + // Arrange + var expectedName = "testParameter"; + var expectedType = typeof(string); + + var expressionParametersConfiguration = new ExpressionParametersConfiguration(); + + // Act + var actual = expressionParametersConfiguration.CreateParameter(expectedName); + + // Assert + actual.Should().NotBeNull(); + actual.Name.Should().Be(expectedName); + actual.Type.Should().Be(expectedType); + + expressionParametersConfiguration.Parameters.Should().HaveCount(1) + .And.ContainKey(expectedName) + .And.ContainValue(actual); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionResultTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionResultTests.cs new file mode 100644 index 00000000..bee40eda --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionResultTests.cs @@ -0,0 +1,30 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ExpressionBuilders +{ + using System.Linq.Expressions; + using FluentAssertions; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Xunit; + + public class ExpressionResultTests + { + [Fact] + public void Ctor_GivenParameters_ReturnsNewInstanceWithExactValuesProvided() + { + // Arrange + var expectedExpressionName = "TestExpression"; + var expectedImplementation = Expression.Constant(true); + var expectedParameters = new[] { Expression.Parameter(typeof(string)), Expression.Parameter(typeof(int)) }; + var expectedReturnType = typeof(bool); + + // Act + var actual = new ExpressionResult(expectedExpressionName, expectedImplementation, expectedParameters, expectedReturnType); + + // Assert + actual.Should().NotBeNull(); + actual.ExpressionName.Should().Be(expectedExpressionName); + actual.Implementation.Should().Be(expectedImplementation); + actual.Parameters.Should().BeSameAs(expectedParameters); + actual.ReturnType.Should().Be(expectedReturnType); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionSwitchBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionSwitchBuilderTests.cs new file mode 100644 index 00000000..62ca37c3 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionSwitchBuilderTests.cs @@ -0,0 +1,143 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ExpressionBuilders +{ + using System; + using System.Linq; + using System.Linq.Expressions; + using FluentAssertions; + using Moq; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Xunit; + + public class ExpressionSwitchBuilderTests + { + [Fact] + public void Case_GivenCaseExpressionAndCaseBodyExpressionBuilder_CreatesSwitchCaseAndAddsToSwitchCasesList() + { + // Arrange + var expectedCaseValueExpression = Expression.Constant("A"); + var resultVariable = Expression.Variable(typeof(int), "result"); + var expectedCaseBodyExpression = Expression.Assign(resultVariable, Expression.Constant(1)); + var expressionBlockBuilder = Mock.Of(); + var expressionSwitchBuilder = new ExpressionSwitchBuilder(expressionBlockBuilder); + + // Act + var actual = expressionSwitchBuilder.Case(expectedCaseValueExpression, b => expectedCaseBodyExpression); + + // Assert + actual.Should().BeSameAs(expressionSwitchBuilder); + expressionSwitchBuilder.SwitchCases.Should().HaveCount(1); + var actualSwitchCase = expressionSwitchBuilder.SwitchCases.First(); + actualSwitchCase.Body.Should().Be(expectedCaseBodyExpression); + actualSwitchCase.TestValues.Should().HaveCount(1) + .And.Contain(expectedCaseValueExpression); + } + + [Fact] + public void Case_GivenCaseExpressionAndNullCaseBodyExpressionBuilder_ThrowsArgumentNullException() + { + // Arrange + var expectedCaseValueExpression = Expression.Constant("A"); + var expressionBlockBuilder = Mock.Of(); + var expressionSwitchBuilder = new ExpressionSwitchBuilder(expressionBlockBuilder); + + // Act + var action = FluentActions.Invoking(() => expressionSwitchBuilder.Case(expectedCaseValueExpression, null)); + + // Assert + action.Should().Throw() + .Which.ParamName.Should().Be("caseBodyExpressionBuilder"); + } + + [Fact] + public void Case_GivenCaseExpressionsAndCaseBodyExpressionBuilder_CreatesSwitchCaseAndAddsToSwitchCasesList() + { + // Arrange + var expectedCaseValueExpressions = new Expression[] + { + Expression.Constant("A"), + Expression.Constant("B"), + }; + var resultVariable = Expression.Variable(typeof(int), "result"); + var expectedCaseBodyExpression = Expression.Assign(resultVariable, Expression.Constant(1)); + var expressionBlockBuilder = Mock.Of(); + var expressionSwitchBuilder = new ExpressionSwitchBuilder(expressionBlockBuilder); + + // Act + var actual = expressionSwitchBuilder.Case(expectedCaseValueExpressions, b => expectedCaseBodyExpression); + + // Assert + actual.Should().BeSameAs(expressionSwitchBuilder); + expressionSwitchBuilder.SwitchCases.Should().HaveCount(1); + var actualSwitchCase = expressionSwitchBuilder.SwitchCases.First(); + actualSwitchCase.Body.Should().Be(expectedCaseBodyExpression); + actualSwitchCase.TestValues.Should().HaveCount(2) + .And.Contain(expectedCaseValueExpressions); + } + + [Fact] + public void Case_GivenNullCaseExpressionAndCaseBodyExpressionBuilder_ThrowsArgumentNullException() + { + // Arrange + var resultVariable = Expression.Variable(typeof(int), "result"); + var expectedCaseBodyExpression = Expression.Assign(resultVariable, Expression.Constant(1)); + var expressionBlockBuilder = Mock.Of(); + var expressionSwitchBuilder = new ExpressionSwitchBuilder(expressionBlockBuilder); + + // Act + var action = FluentActions.Invoking(() => expressionSwitchBuilder.Case(caseExpression: null, b => expectedCaseBodyExpression)); + + // Assert + action.Should().Throw() + .Which.ParamName.Should().Be("caseExpression"); + } + + [Fact] + public void Case_GivenNullCaseExpressionsAndCaseBodyExpressionBuilder_ThrowsArgumentNullException() + { + // Arrange + var resultVariable = Expression.Variable(typeof(int), "result"); + var expectedCaseBodyExpression = Expression.Assign(resultVariable, Expression.Constant(1)); + var expressionBlockBuilder = Mock.Of(); + var expressionSwitchBuilder = new ExpressionSwitchBuilder(expressionBlockBuilder); + + // Act + var action = FluentActions.Invoking(() => expressionSwitchBuilder.Case(caseExpressions: null, b => expectedCaseBodyExpression)); + + // Assert + action.Should().Throw() + .Which.ParamName.Should().Be("caseExpressions"); + } + + [Fact] + public void Default_GivenCaseBodyExpressionBuilder_SetsDefaultBody() + { + // Arrange + var resultVariable = Expression.Variable(typeof(int), "result"); + var expectedDefaultBodyExpression = Expression.Assign(resultVariable, Expression.Constant(1)); + var expressionBlockBuilder = Mock.Of(); + var expressionSwitchBuilder = new ExpressionSwitchBuilder(expressionBlockBuilder); + + // Act + expressionSwitchBuilder.Default(b => expectedDefaultBodyExpression); + + // Assert + expressionSwitchBuilder.DefaultBody.Should().NotBeNull() + .And.BeSameAs(expectedDefaultBodyExpression); + } + + [Fact] + public void Default_GivenNullCaseBodyExpressionBuilder_ThrowsArgumentNullException() + { + // Arrange + var expressionBlockBuilder = Mock.Of(); + var expressionSwitchBuilder = new ExpressionSwitchBuilder(expressionBlockBuilder); + + // Act + var action = FluentActions.Invoking(() => expressionSwitchBuilder.Default(null)); + + // Assert + action.Should().Throw() + .Which.ParamName.Should().Be("defaultBodyExpressionBuilder"); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilderTests.cs index 15137b25..918bf22b 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilderTests.cs @@ -14,7 +14,6 @@ namespace Rules.Framework.Tests.Evaluation.Compiled using Rules.Framework.Evaluation.Compiled; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class ManyToManyValueConditionNodeExpressionBuilderTests @@ -35,8 +34,8 @@ public void Build_GivenLeftHandOperatorRightHandAndDataTypeConfiguration_Returns var conditionExpression = Expression.AndAlso(Expression.Constant(true, typeof(bool)), Expression.Constant(true, typeof(bool))); // For testing purposes, does not need to stay true to the scenario var conditionExpressionBuilder = Mock.Of(); Mock.Get(conditionExpressionBuilder) - .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny())) - .Callback((b, args) => + .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny())) + .Callback((b, args) => { actualLeftExpression = args.LeftHandOperand; actualRightExpression = args.RightHandOperand; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilderTests.cs index 659f6201..53e0a351 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilderTests.cs @@ -14,7 +14,6 @@ namespace Rules.Framework.Tests.Evaluation.Compiled using Rules.Framework.Evaluation.Compiled; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class ManyToOneValueConditionNodeExpressionBuilderTests @@ -35,8 +34,8 @@ public void Build_GivenLeftHandOperatorRightHandDataTypeConfiguration_ReturnsExp var conditionExpression = Expression.AndAlso(Expression.Constant(true, typeof(bool)), Expression.Constant(true, typeof(bool))); // For testing purposes, does not need to stay true to the scenario var conditionExpressionBuilder = Mock.Of(); Mock.Get(conditionExpressionBuilder) - .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny())) - .Callback((b, args) => + .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny())) + .Callback((b, args) => { actualLeftExpression = args.LeftHandOperand; actualRightExpression = args.RightHandOperand; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilderTests.cs index a317d2a7..e288a937 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilderTests.cs @@ -14,7 +14,6 @@ namespace Rules.Framework.Tests.Evaluation.Compiled using Rules.Framework.Evaluation.Compiled; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class OneToManyValueConditionNodeExpressionBuilderTests @@ -35,8 +34,8 @@ public void Build_GivenLefthandOperatorRightHandAndDataTypeConfiguration_Returns var conditionExpression = Expression.AndAlso(Expression.Constant(true, typeof(bool)), Expression.Constant(true, typeof(bool))); // For testing purposes, does not need to stay true to the scenario var conditionExpressionBuilder = Mock.Of(); Mock.Get(conditionExpressionBuilder) - .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny())) - .Callback((b, args) => + .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny())) + .Callback((b, args) => { actualLeftExpression = args.LeftHandOperand; actualRightExpression = args.RightHandOperand; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilderTests.cs index 4020bff4..be58f0ef 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilderTests.cs @@ -13,7 +13,6 @@ namespace Rules.Framework.Tests.Evaluation.Compiled using Rules.Framework.Evaluation.Compiled; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; using Xunit; public class OneToOneValueConditionNodeExpressionBuilderTests @@ -34,8 +33,8 @@ public void Build_GivenExpressionBuilderAndArgs_BuildsExpression() var conditionExpression = Expression.AndAlso(Expression.Constant(true, typeof(bool)), Expression.Constant(true, typeof(bool))); // For testing purposes, does not need to stay true to the scenario var conditionExpressionBuilder = Mock.Of(); Mock.Get(conditionExpressionBuilder) - .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny())) - .Callback((b, args) => + .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny())) + .Callback((b, args) => { actualLeftExpression = args.LeftHandOperand; actualRightExpression = args.RightHandOperand; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.GoldenFile2.csx b/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.GoldenFile2.csx new file mode 100644 index 00000000..705a18ca --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.GoldenFile2.csx @@ -0,0 +1,75 @@ +private Func Evaluate1; +private Func, ConditionType, object> GetValueOrDefault1; + +internal bool Main(EvaluationContext evaluationContext) +{ + bool _C0_result; + object _C0_leftOperand; + object _C0_rightOperand; + string _C0_multiplicity; + bool _C1_result; + object _C1_leftOperand; + object _C1_rightOperand; + string _C1_multiplicity; + + _C0_leftOperand = GetValueOrDefault1.Invoke(evaluationContext.get_Conditions(), ConditionType.NumberOfSales); + _C0_rightOperand = 100; + + if (_C0_leftOperand == null) + { + if (evaluationContext.get_MissingConditionBehavior() == MissingConditionBehaviors.Discard) + { + _C0_result = false; + goto _C0_Label_EndValueConditionNode; + } + + if (evaluationContext.get_MatchMode() == MatchModes.Search) + { + _C0_result = true; + goto _C0_Label_EndValueConditionNode; + } + } + _C0_multiplicity = Evaluate1.Invoke(_C0_leftOperand, Operators.Equal, _C0_rightOperand); + + switch (_C0_multiplicity) + { + case "one-to-one": + _C0_result = true; + break; + default: + break; + } +_C0_Label_EndValueConditionNode: + + _C1_leftOperand = GetValueOrDefault1.Invoke(evaluationContext.get_Conditions(), ConditionType.IsoCountryCode); + _C1_rightOperand = "GB"; + + if (_C1_leftOperand == null) + { + if (evaluationContext.get_MissingConditionBehavior() == MissingConditionBehaviors.Discard) + { + _C1_result = false; + goto _C1_Label_EndValueConditionNode; + } + + if (evaluationContext.get_MatchMode() == MatchModes.Search) + { + _C1_result = true; + goto _C1_Label_EndValueConditionNode; + } + } + _C1_multiplicity = Evaluate1.Invoke(_C1_leftOperand, Operators.Equal, _C1_rightOperand); + + switch (_C1_multiplicity) + { + case "one-to-one": + _C1_result = true; + break; + default: + break; + } +_C1_Label_EndValueConditionNode: + bool result = _C0_result || _C1_result; + return result; + +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.cs index 85f3a45c..e2f72fef 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/RuleConditionsExpressionBuilderTests.cs @@ -3,6 +3,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled using System; using System.Collections.Generic; using System.IO; + using System.Linq; using System.Reflection; using DiffPlex.DiffBuilder; using ExpressionDebugger; @@ -12,13 +13,13 @@ namespace Rules.Framework.Tests.Evaluation.Compiled using Rules.Framework.Core.ConditionNodes; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled; - using Rules.Framework.Evaluation.Compiled.ExpressionBuilders.StateMachine; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; using Rules.Framework.Tests.TestStubs; using Xunit; public class RuleConditionsExpressionBuilderTests { - internal IEnumerable<(string, EvaluationContext, bool)> Scenarios => new[] + internal static IEnumerable<(string, EvaluationContext, bool)> AndComposedConditionNodeScenarios => new[] { ( "Scenario 1 - MissingConditionsBehavior = 'Discard', MatchMode = 'Exact', and only contains condition for 'NumberOfSales'", @@ -68,7 +69,69 @@ public class RuleConditionsExpressionBuilderTests true ), ( - "Scenario 4 - MissingConditionsBehavior = 'UseDataTypeDefault', MatchMode = 'Search', and only contains condition for 'NumberOfSales'", + "Scenario 5 - MissingConditionsBehavior = 'UseDataTypeDefault', MatchMode = 'Search', and only contains condition for 'NumberOfSales'", + new EvaluationContext( + new Dictionary + { + { ConditionType.NumberOfSales, 500 }, + }, + MatchModes.Search, + MissingConditionBehaviors.UseDataTypeDefault), + true + ) + }; + + internal static IEnumerable<(string, EvaluationContext, bool)> OrComposedConditionNodeScenarios => new[] + { + ( + "Scenario 1 - MissingConditionsBehavior = 'Discard', MatchMode = 'Exact', and only contains condition for 'NumberOfSales'", + new EvaluationContext( + new Dictionary + { + { ConditionType.NumberOfSales, 500 }, + }, + MatchModes.Exact, + MissingConditionBehaviors.Discard), + true + ), + ( + "Scenario 2 - MissingConditionsBehavior = 'Discard', MatchMode = 'Exact', and both needed conditions", + new EvaluationContext( + new Dictionary + { + { ConditionType.NumberOfSales, 500 }, + { ConditionType.IsoCountryCode, "PT" }, + }, + MatchModes.Exact, + MissingConditionBehaviors.Discard), + true + ), + ( + "Scenario 3 - MissingConditionsBehavior = 'UseDataTypeDefault', MatchMode = 'Exact', and both needed conditions", + new EvaluationContext( + new Dictionary + { + { ConditionType.NumberOfSales, 500 }, + { ConditionType.IsoCountryCode, "PT" }, + }, + MatchModes.Exact, + MissingConditionBehaviors.UseDataTypeDefault), + true + ), + ( + "Scenario 4 - MissingConditionsBehavior = 'UseDataTypeDefault', MatchMode = 'Search', and both needed conditions", + new EvaluationContext( + new Dictionary + { + { ConditionType.NumberOfSales, 500 }, + { ConditionType.IsoCountryCode, "PT" }, + }, + MatchModes.Search, + MissingConditionBehaviors.UseDataTypeDefault), + true + ), + ( + "Scenario 5 - MissingConditionsBehavior = 'UseDataTypeDefault', MatchMode = 'Search', and only contains condition for 'NumberOfSales'", new EvaluationContext( new Dictionary { @@ -81,7 +144,7 @@ public class RuleConditionsExpressionBuilderTests }; [Fact] - public void BuildExpression_GivenComposedConditionNodeWith2ChildValueConditionNodes_BuildsLambdaExpression() + public void BuildExpression_GivenAndComposedConditionNodeWith2ChildValueConditionNodes_BuildsLambdaExpression() { // Arrange string expectedScript; @@ -100,8 +163,8 @@ var composedConditionNode var valueConditionNodeExpressionBuilder = Mock.Of(); Mock.Get(valueConditionNodeExpressionBuilder) - .Setup(x => x.Build(It.IsAny(), It.IsAny())) - .Callback( + .Setup(x => x.Build(It.IsAny(), It.IsAny())) + .Callback( (builder, args) => { builder.Assign(args.ResultVariableExpression, builder.Constant(true)); @@ -139,7 +202,77 @@ var composedConditionNode .Should() .NotThrow("expression should be compilable"); - foreach (var scenario in this.Scenarios) + foreach (var scenario in AndComposedConditionNodeScenarios) + { + bool? result = null; + FluentActions.Invoking(() => result = compiledLambdaExpression.Invoke(scenario.Item2)) + .Should() + .NotThrow($"compiled expression should be executable under scenario: {scenario.Item1}"); + + result.Should().Be(scenario.Item3); + } + } + + [Fact] + public void BuildExpression_GivenOrComposedConditionNodeWith2ChildValueConditionNodes_BuildsLambdaExpression() + { + // Arrange + string expectedScript; + using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Rules.Framework.Tests.Evaluation.Compiled.RuleConditionsExpressionBuilderTests.GoldenFile2.csx")) + using (var streamReader = new StreamReader(stream)) + { + expectedScript = streamReader.ReadToEnd(); + } + var valueConditionNode1 + = new ValueConditionNode(DataTypes.Integer, ConditionType.NumberOfSales, Operators.Equal, 100); + var valueConditionNode2 + = new ValueConditionNode(DataTypes.String, ConditionType.IsoCountryCode, Operators.Equal, "GB"); + + var composedConditionNode + = new ComposedConditionNode(LogicalOperators.Or, new[] { valueConditionNode1, valueConditionNode2 }); + + var valueConditionNodeExpressionBuilder = Mock.Of(); + Mock.Get(valueConditionNodeExpressionBuilder) + .Setup(x => x.Build(It.IsAny(), It.IsAny())) + .Callback( + (builder, args) => + { + builder.Assign(args.ResultVariableExpression, builder.Constant(true)); + builder.AddExpression(builder.Empty()); + }); + + var valueConditionNodeExpressionBuilderProvider = Mock.Of(); + Mock.Get(valueConditionNodeExpressionBuilderProvider) + .Setup(x => x.GetExpressionBuilder(It.Is(Multiplicities.OneToOne, StringComparer.Ordinal))) + .Returns(valueConditionNodeExpressionBuilder); + + var dataTypeConfigurationProvider = Mock.Of(); + Mock.Get(dataTypeConfigurationProvider) + .Setup(x => x.GetDataTypeConfiguration(DataTypes.String)) + .Returns(DataTypeConfiguration.Create(DataTypes.String, typeof(string), null)); + Mock.Get(dataTypeConfigurationProvider) + .Setup(x => x.GetDataTypeConfiguration(DataTypes.Integer)) + .Returns(DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0)); + + var conditionsTreeCompiler = new RuleConditionsExpressionBuilder( + valueConditionNodeExpressionBuilderProvider, + dataTypeConfigurationProvider); + + // Act + var expression = conditionsTreeCompiler.BuildExpression(composedConditionNode); + + // Assert + expression.Should().NotBeNull(); + var actualScript = expression.ToScript(); + var diffResult = SideBySideDiffBuilder.Diff(expectedScript, actualScript, ignoreWhiteSpace: true); + diffResult.NewText.HasDifferences.Should().BeFalse(); + + Func, bool> compiledLambdaExpression = null; + FluentActions.Invoking(() => compiledLambdaExpression = expression.Compile()) + .Should() + .NotThrow("expression should be compilable"); + + foreach (var scenario in OrComposedConditionNodeScenarios) { bool? result = null; FluentActions.Invoking(() => result = compiledLambdaExpression.Invoke(scenario.Item2)) @@ -159,16 +292,37 @@ public void BuildExpression_GivenUnknownConditionNode_ThrowsNotSupportedExceptio var valueConditionNodeExpressionBuilderProvider = Mock.Of(); var dataTypeConfigurationProvider = Mock.Of(); - var conditionsTreeCompiler = new RuleConditionsExpressionBuilder( + var ruleConditionsExpressionBuilder = new RuleConditionsExpressionBuilder( valueConditionNodeExpressionBuilderProvider, dataTypeConfigurationProvider); // Act - var notSupportedException = Assert.Throws(() => conditionsTreeCompiler.BuildExpression(stubConditionNode)); + var notSupportedException = Assert.Throws(() => ruleConditionsExpressionBuilder.BuildExpression(stubConditionNode)); // Assert notSupportedException.Should().NotBeNull(); notSupportedException.Message.Should().Contain(nameof(StubConditionNode)); } + + [Fact] + public void BuildExpression_GivenUnsupportedLogicalOperatorForComposedConditionNode_ThrowsNotSupportedException() + { + // Arrange + var composedConditionNode = new ComposedConditionNode(LogicalOperators.Eval, Enumerable.Empty>()); + + var valueConditionNodeExpressionBuilderProvider = Mock.Of(); + var dataTypeConfigurationProvider = Mock.Of(); + + var ruleConditionsExpressionBuilder = new RuleConditionsExpressionBuilder( + valueConditionNodeExpressionBuilderProvider, + dataTypeConfigurationProvider); + + // Act + var notSupportedException = Assert.Throws(() => ruleConditionsExpressionBuilder.BuildExpression(composedConditionNode)); + + // Assert + notSupportedException.Should().NotBeNull(); + notSupportedException.Message.Should().Contain(nameof(LogicalOperators.Eval)); + } } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Rules.Framework.Tests.csproj b/tests/Rules.Framework.Tests/Rules.Framework.Tests.csproj index c175f928..154ac4a6 100644 --- a/tests/Rules.Framework.Tests/Rules.Framework.Tests.csproj +++ b/tests/Rules.Framework.Tests/Rules.Framework.Tests.csproj @@ -13,6 +13,7 @@ + @@ -20,6 +21,7 @@ + @@ -43,4 +45,8 @@ + + + + \ No newline at end of file diff --git a/tests/Rules.Framework.WebUI.Tests/AssemblyMetadata.cs b/tests/Rules.Framework.WebUI.Tests/AssemblyMetadata.cs new file mode 100644 index 00000000..c4c0980e --- /dev/null +++ b/tests/Rules.Framework.WebUI.Tests/AssemblyMetadata.cs @@ -0,0 +1,5 @@ +using System.Diagnostics.CodeAnalysis; +using Xunit; + +[assembly: ExcludeFromCodeCoverage] +[assembly: AssemblyTrait("Category", "Unit")] \ No newline at end of file From 7928cb46890b44f6731ee01a7752b104f21aca5f Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 19 Feb 2023 16:08:35 +0000 Subject: [PATCH 18/54] test: add unit tests --- .../ConfiguredRulesEngineBuilderTests.cs | 62 +++++++++++++++++++ .../NotContainsOperatorEvalStrategyTests.cs | 59 ++++++++++++++++++ ...neToManyConditionExpressionBuilderTests.cs | 56 +++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100644 tests/Rules.Framework.Tests/Builder/ConfiguredRulesEngineBuilderTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotContainsOperatorEvalStrategyTests.cs create mode 100644 tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotInOneToManyConditionExpressionBuilderTests.cs diff --git a/tests/Rules.Framework.Tests/Builder/ConfiguredRulesEngineBuilderTests.cs b/tests/Rules.Framework.Tests/Builder/ConfiguredRulesEngineBuilderTests.cs new file mode 100644 index 00000000..f38e4caa --- /dev/null +++ b/tests/Rules.Framework.Tests/Builder/ConfiguredRulesEngineBuilderTests.cs @@ -0,0 +1,62 @@ +namespace Rules.Framework.Tests.Builder +{ + using FluentAssertions; + using Moq; + using Rules.Framework.Builder; + using Rules.Framework.Tests.TestStubs; + using Xunit; + + public class ConfiguredRulesEngineBuilderTests + { + [Fact] + public void Build_WhenCompilationIsEnabled_ReturnsRulesEngineWithCompiledEvaluation() + { + // Arrange + var rulesDataSource = Mock.Of>(); + var configuredRulesEngineBuilder = new ConfiguredRulesEngineBuilder(rulesDataSource); + + configuredRulesEngineBuilder.Configure(opt => + { + opt.EnableCompilation = true; + }); + + // Act + var actual = configuredRulesEngineBuilder.Build(); + + // Assert + actual.Should().NotBeNull(); + } + + [Fact] + public void Build_WhenCompilationIsNotEnabled_ReturnsRulesEngineWithClassicEvaluation() + { + // Arrange + var rulesDataSource = Mock.Of>(); + var configuredRulesEngineBuilder = new ConfiguredRulesEngineBuilder(rulesDataSource); + + // Act + var actual = configuredRulesEngineBuilder.Build(); + + // Assert + actual.Should().NotBeNull(); + } + + [Fact] + public void Configure_GivenOptionsConfigurationAction_SetsOptionsAndValidates() + { + // Arrange + var rulesDataSource = Mock.Of>(); + var configuredRulesEngineBuilder = new ConfiguredRulesEngineBuilder(rulesDataSource); + + // Act + var actual = configuredRulesEngineBuilder.Configure(opt => + { + opt.MissingConditionBehavior = MissingConditionBehaviors.Discard; + }); + + // Assert + actual.Should().NotBeNull() + .And.BeSameAs(configuredRulesEngineBuilder); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotContainsOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotContainsOperatorEvalStrategyTests.cs new file mode 100644 index 00000000..81349921 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotContainsOperatorEvalStrategyTests.cs @@ -0,0 +1,59 @@ +namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation +{ + using System; + using FluentAssertions; + using Rules.Framework.Evaluation.Classic.ValueEvaluation; + using Xunit; + + public class NotContainsOperatorEvalStrategyTests + { + [Fact] + public void Eval_GivenIntegers1And2_ThrowsNotSupportedException() + { + // Arrange + var expectedLeftOperand = 1; + var expectedRightOperand = 2; + + var sut = new NotContainsOperatorEvalStrategy(); + + // Act + var notSupportedException = Assert.Throws(() => sut.Eval(expectedLeftOperand, expectedRightOperand)); + + // Arrange + notSupportedException.Should().NotBeNull(); + notSupportedException.Message.Should().Contain("System.Int32"); + } + + [Fact] + public void Eval_GivenStringsTheQuickBrownFoxJumpsOverTheLazyDogAndFox_ReturnsTrue() + { + // Arrange + var expectedLeftOperand = "The quick brown fox jumps over the lazy dog"; + var expectedRightOperand = "fox"; + + var sut = new NotContainsOperatorEvalStrategy(); + + // Act + var actual = sut.Eval(expectedLeftOperand, expectedRightOperand); + + // Arrange + actual.Should().BeFalse(); + } + + [Fact] + public void Eval_GivenStringsTheQuickBrownFoxJumpsOverTheLazyDogAndYellow_ReturnsFalse() + { + // Arrange + var expectedLeftOperand = "The quick brown fox jumps over the lazy dog"; + var expectedRightOperand = "yellow"; + + var sut = new NotContainsOperatorEvalStrategy(); + + // Act + var actual = sut.Eval(expectedLeftOperand, expectedRightOperand); + + // Arrange + actual.Should().BeTrue(); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotInOneToManyConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotInOneToManyConditionExpressionBuilderTests.cs new file mode 100644 index 00000000..32227774 --- /dev/null +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotInOneToManyConditionExpressionBuilderTests.cs @@ -0,0 +1,56 @@ +namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using FluentAssertions; + using Rules.Framework.Core; + using Rules.Framework.Evaluation; + using Rules.Framework.Evaluation.Compiled.ConditionBuilders; + using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; + using Xunit; + + public class NotInOneToManyConditionExpressionBuilderTests + { + [Fact] + public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForInt_ReturnsConditionExpression() + { + // Arrange + var notInOneToManyConditionExpressionBuilder + = new NotInOneToManyConditionExpressionBuilder(); + + // Act + var expressionResult = ExpressionBuilder.NewExpression("TestCondition") + .WithParameters(p => + { + p.CreateParameter("leftHand", typeof(int)); + }) + .HavingReturn(typeof(bool), false) + .SetImplementation(builder => + { + var args = new BuildConditionExpressionArgs + { + DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0), + LeftHandOperand = builder.GetParameter("leftHand"), + RightHandOperand = builder.Constant>(new int[] { 1, 2, 3 }), + }; + var conditionExpression = notInOneToManyConditionExpressionBuilder + .BuildConditionExpression(builder, args); + + builder.Return(conditionExpression); + }) + .Build(); + + // Assert + var actualExpression = expressionResult.Implementation; + actualExpression.Should().NotBeNull(); + + var compiledExpression = Expression.Lambda>(actualExpression, expressionResult.Parameters).Compile(true); + var notNullLeftHandValueResult1 = compiledExpression.Invoke(2); + var notNullLeftHandValueResult2 = compiledExpression.Invoke(0); + + notNullLeftHandValueResult1.Should().BeFalse(); + notNullLeftHandValueResult2.Should().BeTrue(); + } + } +} \ No newline at end of file From 6196eb41c5f50fafb9e41fc27c2db5f58ff84a8a Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 19 Feb 2023 18:39:00 +0000 Subject: [PATCH 19/54] refactor: self-review changes --- .../Rules.Framework.Providers.InMemory.csproj | 4 +- .../Rules.Framework.Providers.MongoDb.csproj | 2 +- .../ConditionEvalDispatchProvider.cs | 9 +- .../ConditionEvalDispatcherBase.cs | 1 - .../ManyToManyConditionEvalDispatcher.cs | 1 - .../ManyToOneConditionEvalDispatcher.cs | 1 - .../OneToManyConditionEvalDispatcher.cs | 1 - .../OneToOneConditionEvalDispatcher.cs | 1 - .../ConditionExpressionBuilderProvider.cs | 2 +- .../ExpressionBlockBuilder.cs | 4 +- .../IExpressionBuilderFactory.cs | 2 + ...eConditionNodeExpressionBuilderProvider.cs | 1 - .../Extensions/LanguageOperator.cs | 4 +- .../Extensions/TypeExtensions.cs | 15 +- src/Rules.Framework/Rules.Framework.csproj | 2 +- src/Rules.Framework/RulesEngineOptions.cs | 45 +-- src/Rules.Framework/Source/RulesSource.cs | 24 +- src/Rules.Framework/Source/UpdateRuleArgs.cs | 3 - .../Rules.Framework.BenchmarkTests.csproj | 6 +- .../Tests/Benchmark1/Benchmark1.cs | 9 +- .../Tests/Benchmark1/Benchmark1Data.cs | 6 +- .../Tests/Benchmark1/ConditionTypes.cs | 10 +- .../Tests/Benchmark1/ContentTypes.cs | 10 +- .../Tests/Benchmark2/Benchmark2.cs | 6 +- .../Tests/Benchmark2/Benchmark2Data.cs | 6 +- .../Tests/Benchmark2/ConditionTypes.cs | 10 +- .../Tests/Benchmark2/ContentTypes.cs | 4 +- .../Tests/Benchmark3/Benchmark3.cs | 6 +- .../Tests/Benchmark3/Benchmark3Data.Flush.cs | 9 +- .../Benchmark3/Benchmark3Data.FourOfAKind.cs | 9 +- .../Benchmark3/Benchmark3Data.HighCard.cs | 305 +++++++++--------- .../Tests/Benchmark3/Benchmark3Data.Pair.cs | 9 +- .../Benchmark3/Benchmark3Data.RoyalFlush.cs | 9 +- .../Benchmark3/Benchmark3Data.Straight.cs | 9 +- .../Benchmark3Data.StraightFlush.cs | 9 +- .../Benchmark3/Benchmark3Data.ThreeOfAKind.cs | 9 +- .../Tests/Benchmark3/Benchmark3Data.cs | 14 +- .../Tests/Benchmark3/CardPokerScore.cs | 8 +- .../Tests/Benchmark3/ConditionTypes.cs | 4 +- .../Tests/Benchmark3/ContentTypes.cs | 4 +- .../Tests/IBenchmark.cs | 2 + .../Tests/IBenchmarkData.cs | 7 +- .../Rules.Framework.IntegrationTests.csproj | 2 +- ...Providers.InMemory.IntegrationTests.csproj | 4 +- ....Framework.Providers.InMemory.Tests.csproj | 6 +- ....Providers.MongoDb.IntegrationTests.csproj | 4 +- ...s.Framework.Providers.MongoDb.Tests.csproj | 2 +- .../Evaluation/ConditionsTreeAnalyzerTests.cs | 3 - .../Evaluation/MultiplicityEvaluatorTests.cs | 5 - .../Evaluation/OperatorMetadataTests.cs | 5 - .../Rules.Framework.Tests.csproj | 2 +- .../Source/RulesSourceTests.cs | 5 - .../Rules.Framework.WebUI.Tests.csproj | 2 +- 53 files changed, 281 insertions(+), 361 deletions(-) diff --git a/src/Rules.Framework.Providers.InMemory/Rules.Framework.Providers.InMemory.csproj b/src/Rules.Framework.Providers.InMemory/Rules.Framework.Providers.InMemory.csproj index e6678954..cf21a898 100644 --- a/src/Rules.Framework.Providers.InMemory/Rules.Framework.Providers.InMemory.csproj +++ b/src/Rules.Framework.Providers.InMemory/Rules.Framework.Providers.InMemory.csproj @@ -27,7 +27,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -35,7 +35,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + \ No newline at end of file diff --git a/src/Rules.Framework.Providers.MongoDb/Rules.Framework.Providers.MongoDb.csproj b/src/Rules.Framework.Providers.MongoDb/Rules.Framework.Providers.MongoDb.csproj index 8030721c..bf4b92a9 100644 --- a/src/Rules.Framework.Providers.MongoDb/Rules.Framework.Providers.MongoDb.csproj +++ b/src/Rules.Framework.Providers.MongoDb/Rules.Framework.Providers.MongoDb.csproj @@ -27,7 +27,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs index 657b44d6..55c23399 100644 --- a/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs @@ -1,11 +1,8 @@ namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers { using System; - using System.Collections; using System.Collections.Generic; - using System.Linq; using Rules.Framework.Core; - using Rules.Framework.Evaluation; internal sealed class ConditionEvalDispatchProvider : IConditionEvalDispatchProvider { @@ -22,7 +19,7 @@ public ConditionEvalDispatchProvider( { Multiplicities.OneToOne, new OneToOneConditionEvalDispatcher(operatorEvalStrategyFactory, dataTypesConfigurationProvider) }, { Multiplicities.OneToMany, new OneToManyConditionEvalDispatcher(operatorEvalStrategyFactory, dataTypesConfigurationProvider) }, { Multiplicities.ManyToOne, new ManyToOneConditionEvalDispatcher(operatorEvalStrategyFactory, dataTypesConfigurationProvider) }, - { Multiplicities.ManyToMany, new ManyToManyConditionEvalDispatcher(operatorEvalStrategyFactory, dataTypesConfigurationProvider) } + { Multiplicities.ManyToMany, new ManyToManyConditionEvalDispatcher(operatorEvalStrategyFactory, dataTypesConfigurationProvider) }, }; this.multiplicityEvaluator = multiplicityEvaluator; } @@ -31,12 +28,12 @@ public IConditionEvalDispatcher GetEvalDispatcher(object leftOperand, Operators { string multiplicity = this.multiplicityEvaluator.EvaluateMultiplicity(leftOperand, @operator, rightOperand); - this.ThrowIfUnsupportedOperandsAndOperatorCombination($"{multiplicity}-{@operator}"); + ThrowIfUnsupportedOperandsAndOperatorCombination($"{multiplicity}-{@operator}"); return this.dispatchers[multiplicity]; } - private void ThrowIfUnsupportedOperandsAndOperatorCombination(string combination) + private static void ThrowIfUnsupportedOperandsAndOperatorCombination(string combination) { if (!OperatorsMetadata.AllBySupportedCombination.ContainsKey(combination)) { diff --git a/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs index 9170acb1..0a3a3839 100644 --- a/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs @@ -6,7 +6,6 @@ namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers using System.Globalization; using System.Linq; using Rules.Framework.Core; - using Rules.Framework.Evaluation; internal abstract class ConditionEvalDispatcherBase { diff --git a/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs index d20ac459..ae1672c3 100644 --- a/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs @@ -3,7 +3,6 @@ namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers using System.Collections.Generic; using System.Linq; using Rules.Framework.Core; - using Rules.Framework.Evaluation; internal sealed class ManyToManyConditionEvalDispatcher : ConditionEvalDispatcherBase, IConditionEvalDispatcher { diff --git a/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs index 8370c6df..00bb04ba 100644 --- a/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs @@ -3,7 +3,6 @@ namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers using System.Collections.Generic; using System.Linq; using Rules.Framework.Core; - using Rules.Framework.Evaluation; internal sealed class ManyToOneConditionEvalDispatcher : ConditionEvalDispatcherBase, IConditionEvalDispatcher { diff --git a/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs index 0c856426..e8e6b166 100644 --- a/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs @@ -3,7 +3,6 @@ namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers using System.Collections.Generic; using System.Linq; using Rules.Framework.Core; - using Rules.Framework.Evaluation; internal sealed class OneToManyConditionEvalDispatcher : ConditionEvalDispatcherBase, IConditionEvalDispatcher { diff --git a/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs index df48252a..22b6a210 100644 --- a/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs @@ -1,7 +1,6 @@ namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers { using Rules.Framework.Core; - using Rules.Framework.Evaluation; internal sealed class OneToOneConditionEvalDispatcher : ConditionEvalDispatcherBase, IConditionEvalDispatcher { diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProvider.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProvider.cs index 71bae3fb..d7578b7b 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProvider.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProvider.cs @@ -33,7 +33,7 @@ public ConditionExpressionBuilderProvider() public IConditionExpressionBuilder GetConditionExpressionBuilderFor(Operators @operator, string multiplicity) { - if (this.conditionExpressionBuilders.TryGetValue(Combine(@operator, multiplicity), out IConditionExpressionBuilder operatorEvalStrategy)) + if (this.conditionExpressionBuilders.TryGetValue(Combine(@operator, multiplicity), out var operatorEvalStrategy)) { return operatorEvalStrategy; } diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionBlockBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionBlockBuilder.cs index 36b71e1f..d5391c63 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionBlockBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/ExpressionBlockBuilder.cs @@ -140,7 +140,7 @@ LabelTarget NewLabelTarget(string name) throw new InvalidOperationException($"A label target for name '{name}' under scope '{this.ScopeName}' was already added."); } - string prefixedName = $"{this.ScopeName}_{name}"; + var prefixedName = $"{this.ScopeName}_{name}"; return this.Parent.CreateLabelTarget(prefixedName); } } @@ -168,7 +168,7 @@ ParameterExpression NewVariable(string name, Type type) throw new InvalidOperationException($"A variable for name '{name}' under scope '{this.ScopeName}' was already added."); } - string prefixedName = $"{this.ScopeName}_{name}"; + var prefixedName = $"{this.ScopeName}_{name}"; return this.Parent.CreateVariable(prefixedName, type); } } diff --git a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/IExpressionBuilderFactory.cs b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/IExpressionBuilderFactory.cs index b044c855..e7a955b3 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/IExpressionBuilderFactory.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ExpressionBuilders/IExpressionBuilderFactory.cs @@ -11,7 +11,9 @@ IExpressionBlockBuilder CreateExpressionBlockBuilder( IExpressionParametersBuilder CreateExpressionBuilder( ExpressionConfiguration expressionConfiguration); + IExpressionParametersConfiguration CreateExpressionParametersConfiguration(); + IExpressionSwitchBuilder CreateExpressionSwitchBuilder( IExpressionBlockBuilder parent); } diff --git a/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeExpressionBuilderProvider.cs b/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeExpressionBuilderProvider.cs index 92d931fd..d86399d3 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeExpressionBuilderProvider.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ValueConditionNodeExpressionBuilderProvider.cs @@ -2,7 +2,6 @@ namespace Rules.Framework.Evaluation.Compiled { using System; using System.Collections.Generic; - using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; internal sealed class ValueConditionNodeExpressionBuilderProvider : IValueConditionNodeExpressionBuilderProvider diff --git a/src/Rules.Framework/Extensions/LanguageOperator.cs b/src/Rules.Framework/Extensions/LanguageOperator.cs index 8afc6d7e..a62d78bc 100644 --- a/src/Rules.Framework/Extensions/LanguageOperator.cs +++ b/src/Rules.Framework/Extensions/LanguageOperator.cs @@ -8,6 +8,6 @@ internal enum LanguageOperator GreaterThan = 3, GreaterThanOrEqual = 4, LessThan = 5, - LessThanOrEqual = 6 + LessThanOrEqual = 6, } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Extensions/TypeExtensions.cs b/src/Rules.Framework/Extensions/TypeExtensions.cs index f5b30555..1b5a06a0 100644 --- a/src/Rules.Framework/Extensions/TypeExtensions.cs +++ b/src/Rules.Framework/Extensions/TypeExtensions.cs @@ -2,22 +2,11 @@ namespace System { using System.Collections.Concurrent; using System.Collections.Generic; - using System.Collections.Specialized; using System.Linq; using System.Reflection; internal static class TypeExtensions { - private static readonly LanguageOperator[] languageOperators = new[] - { - LanguageOperator.Equal, - LanguageOperator.NotEqual, - LanguageOperator.GreaterThan, - LanguageOperator.GreaterThanOrEqual, - LanguageOperator.LessThan, - LanguageOperator.LessThanOrEqual, - }; - private static readonly ConcurrentDictionary> languageOperatorsSupportByType = new(); public static bool HasLanguageOperator(this Type type, LanguageOperator languageOperator) @@ -47,7 +36,7 @@ public static bool HasLanguageOperator(this Type type, LanguageOperator language "op_GreaterThanOrEqual" => LanguageOperator.GreaterThanOrEqual, "op_LessThan" => LanguageOperator.LessThan, "op_LessThanOrEqual" => LanguageOperator.LessThanOrEqual, - _ => LanguageOperator.None + _ => LanguageOperator.None, }; } -} +} \ No newline at end of file diff --git a/src/Rules.Framework/Rules.Framework.csproj b/src/Rules.Framework/Rules.Framework.csproj index 010f970f..7e94eb79 100644 --- a/src/Rules.Framework/Rules.Framework.csproj +++ b/src/Rules.Framework/Rules.Framework.csproj @@ -33,7 +33,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Rules.Framework/RulesEngineOptions.cs b/src/Rules.Framework/RulesEngineOptions.cs index e6f362bf..f22571ce 100644 --- a/src/Rules.Framework/RulesEngineOptions.cs +++ b/src/Rules.Framework/RulesEngineOptions.cs @@ -14,6 +14,33 @@ private RulesEngineOptions() this.DataTypeDefaults = new Dictionary(); } + /// + /// Gets the default values for each of the supported data types. + /// + public IDictionary DataTypeDefaults { get; } + + /// + /// Gets or sets wether rules' conditions is enabled or not. + /// + public bool EnableCompilation { get; set; } + + /// + /// + /// Gets or sets the rules engine behavior when no condition with a specific type is + /// provided to rules engine to match with a rule's condition with the same type. + /// + /// + /// e.g. a rule with a condition of type "Age" is under evaluation but no condition of type + /// "Age" was supplied. + /// + /// + public MissingConditionBehaviors MissingConditionBehavior { get; set; } + + /// + /// Gets or sets the priority criteria to untie when multiples rules are matched. + /// + public PriorityCriterias PriotityCriteria { get; set; } + /// /// Creates a new set of rules engine options with framework-configured defaults. /// @@ -46,23 +73,5 @@ public static RulesEngineOptions NewWithDefaults() return rulesEngineOptions; } - - /// - /// Gets the default values for each of the supported data types. - /// - public IDictionary DataTypeDefaults { get; } - - public bool EnableCompilation { get; set; } - - /// - /// Gets or sets the rules engine behavior when no condition with a specific type is provided to rules engine to match with a rule's condition with the same type. - /// e.g. a rule with a condition of type "Age" is under evaluation but no condition of type "Age" was supplied. - /// - public MissingConditionBehaviors MissingConditionBehavior { get; set; } - - /// - /// Gets or sets the priority criteria to untie when multiples rules are matched. - /// - public PriorityCriterias PriotityCriteria { get; set; } } } \ No newline at end of file diff --git a/src/Rules.Framework/Source/RulesSource.cs b/src/Rules.Framework/Source/RulesSource.cs index 013971bf..c21618be 100644 --- a/src/Rules.Framework/Source/RulesSource.cs +++ b/src/Rules.Framework/Source/RulesSource.cs @@ -35,12 +35,12 @@ public async Task>> GetRulesAsync public async Task>> GetRulesFilteredAsync(GetRulesFilteredArgs args) { return await this.getRulesFilteredDelegate.Invoke(args).ConfigureAwait(false); - } + } public async Task UpdateRuleAsync(UpdateRuleArgs args) { await this.updateRuleDelegate.Invoke(args).ConfigureAwait(false); - } + } private static AddRuleDelegate CreateAddRulePipelineDelegate( IRulesDataSource rulesDataSource, @@ -53,15 +53,15 @@ private static AddRuleDelegate CreateAddRulePipeli var middlewareNode = middlewares.Last; while (middlewareNode is { }) - { + { var middleware = middlewareNode.Value; var immutableAction = action; action = async (args) => await middleware.HandleAddRuleAsync(args, immutableAction).ConfigureAwait(false); // Get previous middleware node. middlewareNode = middlewareNode.Previous; + } } - } return action; } @@ -72,9 +72,9 @@ private static GetRulesFilteredDelegate CreateGetR { GetRulesFilteredDelegate action = async (args) => - { - RulesFilterArgs rulesFilterArgs = new() { + RulesFilterArgs rulesFilterArgs = new() + { ContentType = args.ContentType, Name = args.Name, Priority = args.Priority, @@ -88,18 +88,18 @@ private static GetRulesFilteredDelegate CreateGetR var middlewareNode = middlewares.Last; while (middlewareNode is { }) - { + { var middleware = middlewareNode.Value; var immutableAction = action; action = async (args) => await middleware.HandleGetRulesFilteredAsync(args, immutableAction).ConfigureAwait(false); // Get previous middleware node. middlewareNode = middlewareNode.Previous; - } } + } return action; - } + } private static GetRulesDelegate CreateGetRulesPipelineDelegate( IRulesDataSource rulesDataSource, @@ -110,11 +110,11 @@ private static GetRulesDelegate CreateGetRulesPipe => await rulesDataSource.GetRulesAsync(args.ContentType, args.DateBegin, args.DateEnd).ConfigureAwait(false); if (middlewares.Count > 0) - { + { var middlewareNode = middlewares.Last; while (middlewareNode is { }) - { + { var middleware = middlewareNode.Value; var immutableAction = action; action = async (args) => await middleware.HandleGetRulesAsync(args, immutableAction).ConfigureAwait(false); @@ -122,7 +122,7 @@ private static GetRulesDelegate CreateGetRulesPipe // Get previous middleware node. middlewareNode = middlewareNode.Previous; } - } + } return action; } diff --git a/src/Rules.Framework/Source/UpdateRuleArgs.cs b/src/Rules.Framework/Source/UpdateRuleArgs.cs index 63a225b3..69ae0981 100644 --- a/src/Rules.Framework/Source/UpdateRuleArgs.cs +++ b/src/Rules.Framework/Source/UpdateRuleArgs.cs @@ -1,9 +1,6 @@ namespace Rules.Framework.Source { using Rules.Framework.Core; - using System; - using System.Collections.Generic; - using System.Text; internal sealed class UpdateRuleArgs { diff --git a/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj b/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj index d6a52a60..5d767723 100644 --- a/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj +++ b/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj @@ -9,9 +9,9 @@ - - - + + + diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1.cs index 3d30e455..02ca4a44 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1.cs @@ -1,12 +1,9 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark1 { - using BenchmarkDotNet.Attributes; - using Rules.Framework.Builder; - using Rules.Framework.Core; - using System; - using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; + using BenchmarkDotNet.Attributes; + using Rules.Framework.Core; [SkewnessColumn, KurtosisColumn] public class Benchmark1 : IBenchmark @@ -49,4 +46,4 @@ public async Task TearDownAsync() await Task.CompletedTask.ConfigureAwait(false); } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1Data.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1Data.cs index 62787d02..d833a40d 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1Data.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1Data.cs @@ -1,10 +1,10 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark1 { + using System; + using System.Collections.Generic; using Rules.Framework.BenchmarkTests.Tests; using Rules.Framework.Builder; using Rules.Framework.Core; - using System; - using System.Collections.Generic; internal class Benchmark1Data : IBenchmarkData { @@ -33,4 +33,4 @@ private IEnumerable> GetRules() return new[] { ruleResult.Rule }; } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/ConditionTypes.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/ConditionTypes.cs index 0ef2ba63..f2531446 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/ConditionTypes.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/ConditionTypes.cs @@ -1,16 +1,10 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark1 { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - internal enum ConditionTypes { IntegerCondition = 1, DecimalCondition = 2, BooleanCondition = 3, - StringCondition = 4 + StringCondition = 4, } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/ContentTypes.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/ContentTypes.cs index 01610090..869050d2 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/ContentTypes.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/ContentTypes.cs @@ -1,13 +1,7 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark1 { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - internal enum ContentTypes { - ContentType1 = 1 + ContentType1 = 1, } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2.cs index 4903c989..470da6cf 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2.cs @@ -1,9 +1,9 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark2 { - using BenchmarkDotNet.Attributes; - using Rules.Framework.Core; using System.Linq; using System.Threading.Tasks; + using BenchmarkDotNet.Attributes; + using Rules.Framework.Core; [SkewnessColumn, KurtosisColumn] public class Benchmark2 : IBenchmark @@ -46,4 +46,4 @@ public async Task TearDownAsync() await Task.CompletedTask.ConfigureAwait(false); } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2Data.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2Data.cs index 28fae1c7..31cfc075 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2Data.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2Data.cs @@ -1,10 +1,10 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark2 { + using System; + using System.Collections.Generic; using Rules.Framework; using Rules.Framework.Builder; using Rules.Framework.Core; - using System; - using System.Collections.Generic; internal class Benchmark2Data : IBenchmarkData { @@ -106,4 +106,4 @@ private IEnumerable> GetRules() return new[] { rule1Result.Rule, rule2Result.Rule }; } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/ConditionTypes.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/ConditionTypes.cs index 0c96d4c3..91070d3a 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/ConditionTypes.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/ConditionTypes.cs @@ -1,15 +1,9 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark2 { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - internal enum ConditionTypes { Lyrics = 1, Artist = 2, - ReleaseYear = 3 + ReleaseYear = 3, } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/ContentTypes.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/ContentTypes.cs index 628ae5b8..47ea960f 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/ContentTypes.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/ContentTypes.cs @@ -2,6 +2,6 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark2 { internal enum ContentTypes { - Songs = 1 + Songs = 1, } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3.cs index d8590a19..bc714300 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3.cs @@ -1,9 +1,9 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - using BenchmarkDotNet.Attributes; - using Rules.Framework.Core; using System.Linq; using System.Threading.Tasks; + using BenchmarkDotNet.Attributes; + using Rules.Framework.Core; [SkewnessColumn, KurtosisColumn] public class Benchmark3 : IBenchmark @@ -46,4 +46,4 @@ public async Task TearDownAsync() await Task.CompletedTask.ConfigureAwait(false); } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Flush.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Flush.cs index 221ff221..1f7c8f23 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Flush.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Flush.cs @@ -1,12 +1,9 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - using Rules.Framework.Builder; - using Rules.Framework.Core; using System; using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; + using Rules.Framework.Builder; + using Rules.Framework.Core; internal partial class Benchmark3Data { @@ -65,4 +62,4 @@ private IEnumerable> GetFlushRules() }; } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.FourOfAKind.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.FourOfAKind.cs index ddccb45f..6295cc6a 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.FourOfAKind.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.FourOfAKind.cs @@ -1,12 +1,9 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - using Rules.Framework.Builder; - using Rules.Framework.Core; using System; using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; + using Rules.Framework.Builder; + using Rules.Framework.Core; internal partial class Benchmark3Data { @@ -160,4 +157,4 @@ private IEnumerable> GetFourOfAKindRules() }; } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.HighCard.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.HighCard.cs index 49f141bf..879584ae 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.HighCard.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.HighCard.cs @@ -1,163 +1,160 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { + using System; + using System.Collections.Generic; using Rules.Framework.Builder; using Rules.Framework.Core; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - internal partial class Benchmark3Data - { + internal partial class Benchmark3Data + { private IEnumerable> GetHighCardsRules() { return new[] - { - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Deuces") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(x => - x.AsValued(ConditionTypes.NumberOfDeuces) - .OfDataType() - .WithComparisonOperator(Operators.Equal) - .SetOperand(1) - .Build()) - .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Treys") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(x => - x.AsValued(ConditionTypes.NumberOfTreys) - .OfDataType() - .WithComparisonOperator(Operators.Equal) - .SetOperand(1) - .Build()) - .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Fours") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(x => - x.AsValued(ConditionTypes.NumberOfFours) - .OfDataType() - .WithComparisonOperator(Operators.Equal) - .SetOperand(1) - .Build()) - .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Fives") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(x => - x.AsValued(ConditionTypes.NumberOfFives) - .OfDataType() - .WithComparisonOperator(Operators.Equal) - .SetOperand(1) - .Build()) - .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Sixes") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(x => - x.AsValued(ConditionTypes.NumberOfSixes) - .OfDataType() - .WithComparisonOperator(Operators.Equal) - .SetOperand(1) - .Build()) - .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Sevens") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(x => - x.AsValued(ConditionTypes.NumberOfSevens) - .OfDataType() - .WithComparisonOperator(Operators.Equal) - .SetOperand(1) - .Build()) - .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Eights") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(x => - x.AsValued(ConditionTypes.NumberOfEigths) - .OfDataType() - .WithComparisonOperator(Operators.Equal) - .SetOperand(1) - .Build()) - .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Nines") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(x => - x.AsValued(ConditionTypes.NumberOfNines) - .OfDataType() - .WithComparisonOperator(Operators.Equal) - .SetOperand(1) - .Build()) - .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Tens") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(x => - x.AsValued(ConditionTypes.NumberOfTens) - .OfDataType() - .WithComparisonOperator(Operators.Equal) - .SetOperand(1) - .Build()) - .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Jacks") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(x => - x.AsValued(ConditionTypes.NumberOfJacks) - .OfDataType() - .WithComparisonOperator(Operators.Equal) - .SetOperand(1) - .Build()) - .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Queens") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(x => - x.AsValued(ConditionTypes.NumberOfQueens) - .OfDataType() - .WithComparisonOperator(Operators.Equal) - .SetOperand(1) - .Build()) - .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Kings") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(x => - x.AsValued(ConditionTypes.NumberOfKings) - .OfDataType() - .WithComparisonOperator(Operators.Equal) - .SetOperand(1) - .Build()) - .Build().Rule, - RuleBuilder.NewRule() - .WithName("Benchmark 3 - High Card Aces") - .WithDateBegin(DateTime.Parse("2000-01-01")) - .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) - .WithCondition(x => - x.AsValued(ConditionTypes.NumberOfAces) - .OfDataType() - .WithComparisonOperator(Operators.Equal) - .SetOperand(1) - .Build()) - .Build().Rule, - }; - } + { + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Deuces") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfDeuces) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Treys") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfTreys) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Fours") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfFours) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Fives") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfFives) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Sixes") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfSixes) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Sevens") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfSevens) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Eights") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfEigths) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Nines") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfNines) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Tens") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfTens) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Jacks") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfJacks) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Queens") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfQueens) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Kings") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfKings) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + RuleBuilder.NewRule() + .WithName("Benchmark 3 - High Card Aces") + .WithDateBegin(DateTime.Parse("2000-01-01")) + .WithContent(ContentTypes.TexasHoldemPokerSingleCombinations, new SingleCombinationPokerScore { Combination = "High Card" }) + .WithCondition(x => + x.AsValued(ConditionTypes.NumberOfAces) + .OfDataType() + .WithComparisonOperator(Operators.Equal) + .SetOperand(1) + .Build()) + .Build().Rule, + }; + } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Pair.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Pair.cs index a68021b0..3ecde0f1 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Pair.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Pair.cs @@ -1,12 +1,9 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - using Rules.Framework.Builder; - using Rules.Framework.Core; using System; using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; + using Rules.Framework.Builder; + using Rules.Framework.Core; internal partial class Benchmark3Data { @@ -160,4 +157,4 @@ private IEnumerable> GetPairsRules() }; } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.RoyalFlush.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.RoyalFlush.cs index ffb26b17..aebd0d28 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.RoyalFlush.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.RoyalFlush.cs @@ -1,12 +1,9 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - using Rules.Framework.Builder; - using Rules.Framework.Core; using System; using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; + using Rules.Framework.Builder; + using Rules.Framework.Core; internal partial class Benchmark3Data { @@ -197,4 +194,4 @@ private IEnumerable> GetRoyalFlushRules() }; } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Straight.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Straight.cs index ff7ce278..dae975ac 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Straight.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Straight.cs @@ -1,12 +1,9 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - using Rules.Framework.Builder; - using Rules.Framework.Core; using System; using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; + using Rules.Framework.Builder; + using Rules.Framework.Core; internal partial class Benchmark3Data { @@ -377,4 +374,4 @@ private IEnumerable> GetStraightRules() }; } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.StraightFlush.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.StraightFlush.cs index 915ee745..e3e39a9e 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.StraightFlush.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.StraightFlush.cs @@ -1,12 +1,9 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - using Rules.Framework.Builder; - using Rules.Framework.Core; using System; using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; + using Rules.Framework.Builder; + using Rules.Framework.Core; internal partial class Benchmark3Data { @@ -1464,4 +1461,4 @@ private IEnumerable> GetStraightFlushRules() }; } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.ThreeOfAKind.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.ThreeOfAKind.cs index 3e01090f..7ac71dcc 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.ThreeOfAKind.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.ThreeOfAKind.cs @@ -1,12 +1,9 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - using Rules.Framework.Builder; - using Rules.Framework.Core; using System; using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; + using Rules.Framework.Builder; + using Rules.Framework.Core; internal partial class Benchmark3Data { @@ -160,4 +157,4 @@ private IEnumerable> GetThreeOfAKindRules() }; } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.cs index bc09c2eb..c22af7d2 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.cs @@ -1,13 +1,10 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - using Rules.Framework; - using Rules.Framework.Builder; - using Rules.Framework.Core; using System; using System.Collections.Generic; using System.Linq; - using System.Text; - using System.Threading.Tasks; + using Rules.Framework; + using Rules.Framework.Core; internal partial class Benchmark3Data : IBenchmarkData { @@ -31,8 +28,9 @@ internal partial class Benchmark3Data : IBenchmarkData> GetRules() { - // Does not consider the double pairs and full house combinations, as they would imply a combinatorial explosion. - // For the purpose of the benchmark, scenario already simulates a high number of rules. + // Does not consider the double pairs and full house combinations, as they would imply a + // combinatorial explosion. For the purpose of the benchmark, scenario already simulates + // a high number of rules. var highCards = this.GetHighCardsRules(); var pairs = this.GetPairsRules(); @@ -52,4 +50,4 @@ private IEnumerable> GetRules() return highCards.Concat(pairs).Concat(threeOfAKind).Concat(straights).Concat(flushs).Concat(fourOfAKind).Concat(straightFlushs).Concat(royalFlushs); } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/CardPokerScore.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/CardPokerScore.cs index 1a747429..721172df 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/CardPokerScore.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/CardPokerScore.cs @@ -1,15 +1,9 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - internal class SingleCombinationPokerScore { public int BasePoints { get; set; } public string? Combination { get; set; } } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/ConditionTypes.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/ConditionTypes.cs index c3ca263f..9ff1d1fd 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/ConditionTypes.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/ConditionTypes.cs @@ -70,6 +70,6 @@ internal enum ConditionTypes NumberOfClubs = 66, NumberOfDiamonds = 67, NumberOfHearts = 68, - NumberOfSpades = 69 + NumberOfSpades = 69, } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/ContentTypes.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/ContentTypes.cs index b65baee3..9f92f443 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/ContentTypes.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/ContentTypes.cs @@ -2,6 +2,6 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { internal enum ContentTypes { - TexasHoldemPokerSingleCombinations = 1 + TexasHoldemPokerSingleCombinations = 1, } -} +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/IBenchmark.cs b/tests/Rules.Framework.BenchmarkTests/Tests/IBenchmark.cs index 12c6ea7d..96b984d0 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/IBenchmark.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/IBenchmark.cs @@ -5,7 +5,9 @@ namespace Rules.Framework.BenchmarkTests.Tests internal interface IBenchmark { Task RunAsync(); + Task SetUpAsync(); + Task TearDownAsync(); } } \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/IBenchmarkData.cs b/tests/Rules.Framework.BenchmarkTests/Tests/IBenchmarkData.cs index 9d42cbc7..9af2a30a 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/IBenchmarkData.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/IBenchmarkData.cs @@ -1,15 +1,16 @@ namespace Rules.Framework.BenchmarkTests.Tests { - using Rules.Framework; - using Rules.Framework.BenchmarkTests.Tests.Benchmark1; - using Rules.Framework.Core; using System; using System.Collections.Generic; + using Rules.Framework; + using Rules.Framework.Core; internal interface IBenchmarkData { IEnumerable> Conditions { get; } + DateTime MatchDate { get; } + IEnumerable> Rules { get; } } } \ No newline at end of file diff --git a/tests/Rules.Framework.IntegrationTests/Rules.Framework.IntegrationTests.csproj b/tests/Rules.Framework.IntegrationTests/Rules.Framework.IntegrationTests.csproj index 4257d010..8b63f344 100644 --- a/tests/Rules.Framework.IntegrationTests/Rules.Framework.IntegrationTests.csproj +++ b/tests/Rules.Framework.IntegrationTests/Rules.Framework.IntegrationTests.csproj @@ -12,7 +12,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Rules.Framework.Providers.InMemory.IntegrationTests.csproj b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Rules.Framework.Providers.InMemory.IntegrationTests.csproj index 85986198..a22c5067 100644 --- a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Rules.Framework.Providers.InMemory.IntegrationTests.csproj +++ b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Rules.Framework.Providers.InMemory.IntegrationTests.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/tests/Rules.Framework.Providers.InMemory.Tests/Rules.Framework.Providers.InMemory.Tests.csproj b/tests/Rules.Framework.Providers.InMemory.Tests/Rules.Framework.Providers.InMemory.Tests.csproj index e77f80dc..4359e9c3 100644 --- a/tests/Rules.Framework.Providers.InMemory.Tests/Rules.Framework.Providers.InMemory.Tests.csproj +++ b/tests/Rules.Framework.Providers.InMemory.Tests/Rules.Framework.Providers.InMemory.Tests.csproj @@ -9,9 +9,9 @@ - - - + + + diff --git a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Rules.Framework.Providers.MongoDb.IntegrationTests.csproj b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Rules.Framework.Providers.MongoDb.IntegrationTests.csproj index c0f1631c..5175909d 100644 --- a/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Rules.Framework.Providers.MongoDb.IntegrationTests.csproj +++ b/tests/Rules.Framework.Providers.MongoDb.IntegrationTests/Rules.Framework.Providers.MongoDb.IntegrationTests.csproj @@ -17,8 +17,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/tests/Rules.Framework.Providers.MongoDb.Tests/Rules.Framework.Providers.MongoDb.Tests.csproj b/tests/Rules.Framework.Providers.MongoDb.Tests/Rules.Framework.Providers.MongoDb.Tests.csproj index 5ea90126..8e575366 100644 --- a/tests/Rules.Framework.Providers.MongoDb.Tests/Rules.Framework.Providers.MongoDb.Tests.csproj +++ b/tests/Rules.Framework.Providers.MongoDb.Tests/Rules.Framework.Providers.MongoDb.Tests.csproj @@ -13,7 +13,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/tests/Rules.Framework.Tests/Evaluation/ConditionsTreeAnalyzerTests.cs b/tests/Rules.Framework.Tests/Evaluation/ConditionsTreeAnalyzerTests.cs index 65bba8f2..ee2e7f31 100644 --- a/tests/Rules.Framework.Tests/Evaluation/ConditionsTreeAnalyzerTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/ConditionsTreeAnalyzerTests.cs @@ -3,13 +3,10 @@ namespace Rules.Framework.Tests.Evaluation using System; using System.Collections.Generic; using FluentAssertions; - using Rules.Framework.Core.ConditionNodes; using Rules.Framework.Core; using Rules.Framework.Core.ConditionNodes; using Rules.Framework.Evaluation; using Rules.Framework.Tests.TestStubs; - using System; - using System.Collections.Generic; using Xunit; public class ConditionsTreeAnalyzerTests diff --git a/tests/Rules.Framework.Tests/Evaluation/MultiplicityEvaluatorTests.cs b/tests/Rules.Framework.Tests/Evaluation/MultiplicityEvaluatorTests.cs index 5a1dcacc..f257bc22 100644 --- a/tests/Rules.Framework.Tests/Evaluation/MultiplicityEvaluatorTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/MultiplicityEvaluatorTests.cs @@ -5,11 +5,6 @@ namespace Rules.Framework.Tests.Evaluation using FluentAssertions; using Rules.Framework.Core; using Rules.Framework.Evaluation; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; using Xunit; public class MultiplicityEvaluatorTests diff --git a/tests/Rules.Framework.Tests/Evaluation/OperatorMetadataTests.cs b/tests/Rules.Framework.Tests/Evaluation/OperatorMetadataTests.cs index 30c0bbb8..75fc98d0 100644 --- a/tests/Rules.Framework.Tests/Evaluation/OperatorMetadataTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/OperatorMetadataTests.cs @@ -4,11 +4,6 @@ namespace Rules.Framework.Tests.Evaluation using FluentAssertions; using Rules.Framework.Core; using Rules.Framework.Evaluation; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; using Xunit; public class OperatorMetadataTests diff --git a/tests/Rules.Framework.Tests/Rules.Framework.Tests.csproj b/tests/Rules.Framework.Tests/Rules.Framework.Tests.csproj index 154ac4a6..a90e07c2 100644 --- a/tests/Rules.Framework.Tests/Rules.Framework.Tests.csproj +++ b/tests/Rules.Framework.Tests/Rules.Framework.Tests.csproj @@ -32,7 +32,7 @@ - + diff --git a/tests/Rules.Framework.Tests/Source/RulesSourceTests.cs b/tests/Rules.Framework.Tests/Source/RulesSourceTests.cs index 4c9c0e86..aba74221 100644 --- a/tests/Rules.Framework.Tests/Source/RulesSourceTests.cs +++ b/tests/Rules.Framework.Tests/Source/RulesSourceTests.cs @@ -9,11 +9,6 @@ namespace Rules.Framework.Tests.Source using Rules.Framework.Core; using Rules.Framework.Source; using Rules.Framework.Tests.TestStubs; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; using Xunit; public class RulesSourceTests diff --git a/tests/Rules.Framework.WebUI.Tests/Rules.Framework.WebUI.Tests.csproj b/tests/Rules.Framework.WebUI.Tests/Rules.Framework.WebUI.Tests.csproj index a3db444e..dd238a84 100644 --- a/tests/Rules.Framework.WebUI.Tests/Rules.Framework.WebUI.Tests.csproj +++ b/tests/Rules.Framework.WebUI.Tests/Rules.Framework.WebUI.Tests.csproj @@ -8,7 +8,7 @@ - + From faad59231d41de6a69103c1241bf9548a665e13e Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 19 Feb 2023 18:43:03 +0000 Subject: [PATCH 20/54] chore: remove env file --- .env | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index b7cab7de..00000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -MONGO_DB_HOST=docker.local \ No newline at end of file From 37696756194242dde04b5e9d56b029a78e5d5e40 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Tue, 21 Feb 2023 00:13:19 +0000 Subject: [PATCH 21/54] chore: fix static analysis issues found by Codacy --- .../OperatorEvalStrategyFactory.cs | 2 +- .../BuildValueConditionNodeExpressionArgs.cs | 25 +++++++- .../BuildConditionExpressionArgs.cs | 21 ++++++- .../RuleConditionsExpressionBuilder.cs | 12 ++-- .../Extensions/TypeExtensions.cs | 2 +- .../Rules.Framework.BenchmarkTests/Program.cs | 2 +- .../Tests/Benchmark3/Benchmark3Data.Flush.cs | 2 +- .../Benchmark3/Benchmark3Data.FourOfAKind.cs | 2 +- .../Benchmark3/Benchmark3Data.HighCard.cs | 2 +- .../Tests/Benchmark3/Benchmark3Data.Pair.cs | 2 +- .../Benchmark3/Benchmark3Data.RoyalFlush.cs | 2 +- .../Benchmark3/Benchmark3Data.Straight.cs | 2 +- .../Benchmark3Data.StraightFlush.cs | 2 +- .../Benchmark3/Benchmark3Data.ThreeOfAKind.cs | 2 +- .../ComposedConditionNodeTests.cs | 59 +++++++++-------- .../ClassicConditionsEvalEngineTests.cs | 63 ++++++++++--------- .../EqualOperatorEvalStrategyTests.cs | 2 +- .../GreaterThanOperatorEvalStrategyTests.cs | 2 +- ...terThanOrEqualOperatorEvalStrategyTests.cs | 2 +- .../LesserThanOperatorEvalStrategyTests.cs | 2 +- ...serThanOrEqualOperatorEvalStrategyTests.cs | 2 +- .../NotEqualOperatorEvalStrategyTests.cs | 2 +- .../CompilationRulesSourceMiddlewareTests.cs | 48 +++++++------- .../CompiledConditionsEvalEngineTests.cs | 6 +- ...neToManyConditionExpressionBuilderTests.cs | 2 +- ...neToManyConditionExpressionBuilderTests.cs | 2 +- ...OneToOneConditionExpressionBuilderTests.cs | 5 -- .../ExpressionBlockBuilderTests.cs | 6 +- .../ExpressionBuilderTests.cs | 6 +- ...alueConditionNodeExpressionBuilderTests.cs | 6 +- ...alueConditionNodeExpressionBuilderTests.cs | 4 +- ...alueConditionNodeExpressionBuilderTests.cs | 6 +- ...alueConditionNodeExpressionBuilderTests.cs | 2 +- 33 files changed, 173 insertions(+), 134 deletions(-) diff --git a/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/OperatorEvalStrategyFactory.cs b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/OperatorEvalStrategyFactory.cs index e1fed302..20dfcd4e 100644 --- a/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/OperatorEvalStrategyFactory.cs +++ b/src/Rules.Framework/Evaluation/Classic/ValueEvaluation/OperatorEvalStrategyFactory.cs @@ -10,7 +10,7 @@ internal sealed class OperatorEvalStrategyFactory : IOperatorEvalStrategyFactory public OperatorEvalStrategyFactory() { - this.strategies = new Dictionary() + this.strategies = new Dictionary { { Operators.Equal, new EqualOperatorEvalStrategy() }, { Operators.NotEqual, new NotEqualOperatorEvalStrategy() }, diff --git a/src/Rules.Framework/Evaluation/Compiled/BuildValueConditionNodeExpressionArgs.cs b/src/Rules.Framework/Evaluation/Compiled/BuildValueConditionNodeExpressionArgs.cs index a65b5139..8e052fb1 100644 --- a/src/Rules.Framework/Evaluation/Compiled/BuildValueConditionNodeExpressionArgs.cs +++ b/src/Rules.Framework/Evaluation/Compiled/BuildValueConditionNodeExpressionArgs.cs @@ -1,9 +1,11 @@ namespace Rules.Framework.Evaluation.Compiled { + using System; + using System.Collections.Generic; using System.Linq.Expressions; using Rules.Framework.Core; - internal struct BuildValueConditionNodeExpressionArgs + internal struct BuildValueConditionNodeExpressionArgs : IEquatable { public DataTypeConfiguration DataTypeConfiguration { get; set; } @@ -14,5 +16,26 @@ internal struct BuildValueConditionNodeExpressionArgs public ParameterExpression ResultVariableExpression { get; set; } public ParameterExpression RightOperandVariableExpression { get; set; } + + public readonly bool Equals(BuildValueConditionNodeExpressionArgs other) + => EqualityComparer.Default.Equals(this.DataTypeConfiguration, other.DataTypeConfiguration) + && EqualityComparer.Default.Equals(this.LeftOperandVariableExpression, other.LeftOperandVariableExpression) + && EqualityComparer.Default.Equals(this.Operator, other.Operator) + && EqualityComparer.Default.Equals(this.ResultVariableExpression, other.ResultVariableExpression) + && EqualityComparer.Default.Equals(this.RightOperandVariableExpression, other.RightOperandVariableExpression); + + public override readonly bool Equals(object obj) + => obj is BuildValueConditionNodeExpressionArgs args && this.Equals(args); + + public override readonly int GetHashCode() + { + int hashCode = 274727839; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.DataTypeConfiguration); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.LeftOperandVariableExpression); + hashCode = hashCode * -1521134295 + this.Operator.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.ResultVariableExpression); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.RightOperandVariableExpression); + return hashCode; + } } } \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/BuildConditionExpressionArgs.cs b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/BuildConditionExpressionArgs.cs index 54b75056..c9515a54 100644 --- a/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/BuildConditionExpressionArgs.cs +++ b/src/Rules.Framework/Evaluation/Compiled/ConditionBuilders/BuildConditionExpressionArgs.cs @@ -1,13 +1,32 @@ namespace Rules.Framework.Evaluation.Compiled.ConditionBuilders { + using System; + using System.Collections.Generic; using System.Linq.Expressions; - internal struct BuildConditionExpressionArgs + internal struct BuildConditionExpressionArgs : IEquatable { public DataTypeConfiguration DataTypeConfiguration { get; set; } public Expression LeftHandOperand { get; set; } public Expression RightHandOperand { get; set; } + + public override readonly bool Equals(object obj) + => obj is BuildConditionExpressionArgs args && this.Equals(args); + + public readonly bool Equals(BuildConditionExpressionArgs other) + => EqualityComparer.Default.Equals(this.DataTypeConfiguration, other.DataTypeConfiguration) + && EqualityComparer.Default.Equals(this.LeftHandOperand, other.LeftHandOperand) + && EqualityComparer.Default.Equals(this.RightHandOperand, other.RightHandOperand); + + public override readonly int GetHashCode() + { + int hashCode = 157372928; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.DataTypeConfiguration); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.LeftHandOperand); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.RightHandOperand); + return hashCode; + } } } \ No newline at end of file diff --git a/src/Rules.Framework/Evaluation/Compiled/RuleConditionsExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/RuleConditionsExpressionBuilder.cs index 76b1ce36..89b3b320 100644 --- a/src/Rules.Framework/Evaluation/Compiled/RuleConditionsExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/RuleConditionsExpressionBuilder.cs @@ -9,7 +9,13 @@ namespace Rules.Framework.Evaluation.Compiled using Rules.Framework.Core.ConditionNodes; using Rules.Framework.Evaluation.Compiled.ExpressionBuilders; - internal sealed class RuleConditionsExpressionBuilder : IRuleConditionsExpressionBuilder + internal class RuleConditionsExpressionBuilder + { + protected static readonly MethodInfo multiplicityEvaluateMethod = typeof(MultiplicityEvaluator) + .GetMethod(nameof(MultiplicityEvaluator.Evaluate)); + } + + internal sealed class RuleConditionsExpressionBuilder : RuleConditionsExpressionBuilder, IRuleConditionsExpressionBuilder { private static readonly MethodInfo conditionsGetterMethod = typeof(EvaluationContext) .GetProperty("Conditions") @@ -22,9 +28,6 @@ internal sealed class RuleConditionsExpressionBuilder : IRuleCon .GetMethod(nameof(ConditionsValueLookupExtension.GetValueOrDefault)) .MakeGenericMethod(typeof(TConditionType)); - private static readonly MethodInfo multiplicityEvaluateMethod = typeof(MultiplicityEvaluator) - .GetMethod(nameof(MultiplicityEvaluator.Evaluate)); - private readonly IDataTypesConfigurationProvider dataTypesConfigurationProvider; private readonly IValueConditionNodeExpressionBuilderProvider valueConditionNodeExpressionBuilderProvider; @@ -121,7 +124,6 @@ private void BuildExpression(IConditionNode conditionNode, IExpr // Variables, constants, and labels. var leftOperandVariableExpression = builder.CreateVariable("leftOperand"); var rightOperandVariableExpression = builder.CreateVariable("rightOperand"); - var resultVariableExpression = builder.GetVariable("result"); var jumpToLabelTarget = builder.CreateLabelTarget("Label_EndValueConditionNode"); var parameterExpression = builder.GetParameter("evaluationContext"); diff --git a/src/Rules.Framework/Extensions/TypeExtensions.cs b/src/Rules.Framework/Extensions/TypeExtensions.cs index 1b5a06a0..80a1beb4 100644 --- a/src/Rules.Framework/Extensions/TypeExtensions.cs +++ b/src/Rules.Framework/Extensions/TypeExtensions.cs @@ -13,7 +13,7 @@ public static bool HasLanguageOperator(this Type type, LanguageOperator language { var languageOperatorsByType = languageOperatorsSupportByType.GetOrAdd(type, (t) => { - return ((TypeInfo)type).GetRuntimeMethods() + return ((TypeInfo)t).GetRuntimeMethods() .Select(m => { var splittedName = m.Name.Split('.'); diff --git a/tests/Rules.Framework.BenchmarkTests/Program.cs b/tests/Rules.Framework.BenchmarkTests/Program.cs index 01ed5c90..c1a11ec0 100644 --- a/tests/Rules.Framework.BenchmarkTests/Program.cs +++ b/tests/Rules.Framework.BenchmarkTests/Program.cs @@ -7,7 +7,7 @@ [assembly: SimpleJob(RuntimeMoniker.Net60)] -internal class Program +internal static class Program { private static void Main(string[] args) { diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Flush.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Flush.cs index 1f7c8f23..eb2c7024 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Flush.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Flush.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 using Rules.Framework.Builder; using Rules.Framework.Core; - internal partial class Benchmark3Data + internal partial class Benchmark3Data : IBenchmarkData { private IEnumerable> GetFlushRules() { diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.FourOfAKind.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.FourOfAKind.cs index 6295cc6a..d06eee03 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.FourOfAKind.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.FourOfAKind.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 using Rules.Framework.Builder; using Rules.Framework.Core; - internal partial class Benchmark3Data + internal partial class Benchmark3Data : IBenchmarkData { private IEnumerable> GetFourOfAKindRules() { diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.HighCard.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.HighCard.cs index 879584ae..7cd4ac35 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.HighCard.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.HighCard.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 using Rules.Framework.Builder; using Rules.Framework.Core; - internal partial class Benchmark3Data + internal partial class Benchmark3Data : IBenchmarkData { private IEnumerable> GetHighCardsRules() { diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Pair.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Pair.cs index 3ecde0f1..0a8c838e 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Pair.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Pair.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 using Rules.Framework.Builder; using Rules.Framework.Core; - internal partial class Benchmark3Data + internal partial class Benchmark3Data : IBenchmarkData { private IEnumerable> GetPairsRules() { diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.RoyalFlush.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.RoyalFlush.cs index aebd0d28..4439a5b9 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.RoyalFlush.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.RoyalFlush.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 using Rules.Framework.Builder; using Rules.Framework.Core; - internal partial class Benchmark3Data + internal partial class Benchmark3Data : IBenchmarkData { private IEnumerable> GetRoyalFlushRules() { diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Straight.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Straight.cs index dae975ac..774732ea 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Straight.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.Straight.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 using Rules.Framework.Builder; using Rules.Framework.Core; - internal partial class Benchmark3Data + internal partial class Benchmark3Data : IBenchmarkData { private IEnumerable> GetStraightRules() { diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.StraightFlush.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.StraightFlush.cs index e3e39a9e..574087be 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.StraightFlush.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.StraightFlush.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 using Rules.Framework.Builder; using Rules.Framework.Core; - internal partial class Benchmark3Data + internal partial class Benchmark3Data : IBenchmarkData { private IEnumerable> GetStraightFlushRules() { diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.ThreeOfAKind.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.ThreeOfAKind.cs index 7ac71dcc..e25e12d1 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.ThreeOfAKind.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3Data.ThreeOfAKind.cs @@ -5,7 +5,7 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 using Rules.Framework.Builder; using Rules.Framework.Core; - internal partial class Benchmark3Data + internal partial class Benchmark3Data : IBenchmarkData { private IEnumerable> GetThreeOfAKindRules() { diff --git a/tests/Rules.Framework.Tests/Core/ConditionNodes/ComposedConditionNodeTests.cs b/tests/Rules.Framework.Tests/Core/ConditionNodes/ComposedConditionNodeTests.cs index c7880288..60570d0d 100644 --- a/tests/Rules.Framework.Tests/Core/ConditionNodes/ComposedConditionNodeTests.cs +++ b/tests/Rules.Framework.Tests/Core/ConditionNodes/ComposedConditionNodeTests.cs @@ -1,6 +1,5 @@ namespace Rules.Framework.Tests.Core.ConditionNodes { - using System.Collections.Generic; using FluentAssertions; using Moq; using Rules.Framework.Core; @@ -10,35 +9,13 @@ namespace Rules.Framework.Tests.Core.ConditionNodes public class ComposedConditionNodeTests { - [Fact] - public void ComposedConditionNode_Init_GivenSetupWithChildConditionsAndLogicalOperator_ReturnsSettedValues() - { - // Arrange - LogicalOperators expectedLogicalOperator = LogicalOperators.Eval; - IEnumerable> expectedChildConditionNodes = new IConditionNode[] - { - new Mock>().Object, - new Mock>().Object - }; - - ComposedConditionNode sut = new ComposedConditionNode(expectedLogicalOperator, expectedChildConditionNodes); - - // Act - LogicalOperators actualLogicalOperator = sut.LogicalOperator; - IEnumerable> actualChildConditionNodes = sut.ChildConditionNodes; - - // Assert - actualLogicalOperator.Should().Be(expectedLogicalOperator); - actualChildConditionNodes.Should().NotBeNull().And.BeSameAs(expectedChildConditionNodes); - } - [Fact] public void Clone_NoConditions_ReturnsCloneInstance() { // Arrange - LogicalOperators expectedLogicalOperator = LogicalOperators.Eval; - IConditionNode conditionNode1 = Mock.Of>(); - IConditionNode conditionNode2 = Mock.Of>(); + var expectedLogicalOperator = LogicalOperators.Eval; + var conditionNode1 = Mock.Of>(); + var conditionNode2 = Mock.Of>(); Mock.Get(conditionNode1) .Setup(x => x.Clone()) .Returns(conditionNode1); @@ -46,23 +23,45 @@ public void Clone_NoConditions_ReturnsCloneInstance() .Setup(x => x.Clone()) .Returns(conditionNode2); - IEnumerable > expectedChildConditionNodes = new IConditionNode[] { conditionNode1, conditionNode2 }; + var expectedChildConditionNodes = new[] { conditionNode1, conditionNode2 }; - ComposedConditionNode sut = new ComposedConditionNode(expectedLogicalOperator, expectedChildConditionNodes); + var sut = new ComposedConditionNode(expectedLogicalOperator, expectedChildConditionNodes); sut.Properties["test"] = "test"; // Act - IConditionNode actual = sut.Clone(); + var actual = sut.Clone(); // Assert actual.Should() .NotBeNull() .And .BeOfType>(); - ComposedConditionNode valueConditionNode = actual.As>(); + var valueConditionNode = actual.As>(); valueConditionNode.LogicalOperator.Should().Be(expectedLogicalOperator); valueConditionNode.ChildConditionNodes.Should().BeEquivalentTo(expectedChildConditionNodes); valueConditionNode.Properties.Should().BeEmpty(); } + + [Fact] + public void ComposedConditionNode_Init_GivenSetupWithChildConditionsAndLogicalOperator_ReturnsSettedValues() + { + // Arrange + var expectedLogicalOperator = LogicalOperators.Eval; + var expectedChildConditionNodes = new[] + { + new Mock>().Object, + new Mock>().Object + }; + + var sut = new ComposedConditionNode(expectedLogicalOperator, expectedChildConditionNodes); + + // Act + var actualLogicalOperator = sut.LogicalOperator; + var actualChildConditionNodes = sut.ChildConditionNodes; + + // Assert + actualLogicalOperator.Should().Be(expectedLogicalOperator); + actualChildConditionNodes.Should().NotBeNull().And.BeSameAs(expectedChildConditionNodes); + } } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Classic/ClassicConditionsEvalEngineTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ClassicConditionsEvalEngineTests.cs index 9fd985cb..8ad81834 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Classic/ClassicConditionsEvalEngineTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ClassicConditionsEvalEngineTests.cs @@ -17,14 +17,14 @@ public class ClassicConditionsEvalEngineTests public void Eval_GivenComposedConditionNodeWithAndOperatorAndMissingConditionWithSearchMode_EvalsAndReturnsResult() { // Arrange - ValueConditionNode condition1 = new(DataTypes.Boolean, ConditionType.IsVip, Operators.Equal, true); - ValueConditionNode condition2 = new(DataTypes.String, ConditionType.IsoCurrency, Operators.NotEqual, "SGD"); + var condition1 = new ValueConditionNode(DataTypes.Boolean, ConditionType.IsVip, Operators.Equal, true); + var condition2 = new ValueConditionNode(DataTypes.String, ConditionType.IsoCurrency, Operators.NotEqual, "SGD"); - ComposedConditionNode composedConditionNode = new( + var composedConditionNode = new ComposedConditionNode( LogicalOperators.And, new IConditionNode[] { condition1, condition2 }); - Dictionary conditions = new() + var conditions = new Dictionary { { ConditionType.IsoCurrency, @@ -46,19 +46,19 @@ public void Eval_GivenComposedConditionNodeWithAndOperatorAndMissingConditionWit mockDeferredEval.SetupSequence(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact))) .Returns(() => { - return (c) => false; + return (_) => false; }) .Returns(() => { - return (c) => true; + return (_) => true; }) .Throws(new NotImplementedException("Shouldn't have gotten any more deferred evals.")); var conditionsTreeAnalyzer = Mock.Of>(); - ClassicConditionsEvalEngine sut = new ClassicConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); + var sut = new ClassicConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); // Act - bool actual = sut.Eval(composedConditionNode, conditions, evaluationOptions); + var actual = sut.Eval(composedConditionNode, conditions, evaluationOptions); // Assert actual.Should().BeFalse(); @@ -70,14 +70,14 @@ public void Eval_GivenComposedConditionNodeWithAndOperatorAndMissingConditionWit public void Eval_GivenComposedConditionNodeWithAndOperatorWithExactMatch_EvalsAndReturnsResult() { // Arrange - ValueConditionNode condition1 = new(DataTypes.Boolean, ConditionType.IsVip, Operators.Equal, true); - ValueConditionNode condition2 = new(DataTypes.String, ConditionType.IsoCurrency, Operators.NotEqual, "SGD"); + var condition1 = new ValueConditionNode(DataTypes.Boolean, ConditionType.IsVip, Operators.Equal, true); + var condition2 = new ValueConditionNode(DataTypes.String, ConditionType.IsoCurrency, Operators.NotEqual, "SGD"); - ComposedConditionNode composedConditionNode = new( + var composedConditionNode = new ComposedConditionNode( LogicalOperators.Eval, new IConditionNode[] { condition1, condition2 }); - Dictionary conditions = new() + var conditions = new Dictionary { { ConditionType.IsoCurrency, @@ -94,19 +94,19 @@ public void Eval_GivenComposedConditionNodeWithAndOperatorWithExactMatch_EvalsAn MatchMode = MatchModes.Exact }; - Mock mockDeferredEval = new Mock(); - - IConditionsTreeAnalyzer conditionsTreeAnalyzer = Mock.Of>(); + var deferredEval = Mock.Of(); + var conditionsTreeAnalyzer = Mock.Of>(); - ClassicConditionsEvalEngine sut = new ClassicConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); + var sut = new ClassicConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); // Act - NotSupportedException notSupportedException = Assert.Throws(() => sut.Eval(composedConditionNode, conditions, evaluationOptions)); + var notSupportedException = Assert.Throws(() => sut.Eval(composedConditionNode, conditions, evaluationOptions)); // Assert notSupportedException.Should().NotBeNull(); notSupportedException.Message.Should().Be("Unsupported logical operator: 'Eval'."); - mockDeferredEval.Verify(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact)), Times.Never()); + Mock.Get(deferredEval) + .Verify(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact)), Times.Never()); } [Fact] @@ -120,7 +120,7 @@ public void Eval_GivenComposedConditionNodeWithEvalOperator_ThrowsNotSupportedEx LogicalOperators.Eval, new IConditionNode[] { condition1, condition2 }); - Dictionary conditions = new() + var conditions = new Dictionary { { ConditionType.IsoCurrency, @@ -137,10 +137,10 @@ public void Eval_GivenComposedConditionNodeWithEvalOperator_ThrowsNotSupportedEx MatchMode = MatchModes.Exact }; - var mockDeferredEval = new Mock(); + var deferredEval = Mock.Of(); var conditionsTreeAnalyzer = Mock.Of>(); - ClassicConditionsEvalEngine sut = new ClassicConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); + var sut = new ClassicConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); // Act var notSupportedException = Assert.Throws(() => sut.Eval(composedConditionNode, conditions, evaluationOptions)); @@ -148,7 +148,8 @@ public void Eval_GivenComposedConditionNodeWithEvalOperator_ThrowsNotSupportedEx // Assert notSupportedException.Should().NotBeNull(); notSupportedException.Message.Should().Be("Unsupported logical operator: 'Eval'."); - mockDeferredEval.Verify(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact)), Times.Never()); + Mock.Get(deferredEval) + .Verify(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact)), Times.Never()); } [Fact] @@ -162,7 +163,7 @@ public void Eval_GivenComposedConditionNodeWithOrOperatorWithExactMatch_EvalsAnd LogicalOperators.Or, new IConditionNode[] { condition1, condition2 }); - var conditions = new Dictionary() + var conditions = new Dictionary { { ConditionType.IsoCurrency, @@ -179,28 +180,30 @@ public void Eval_GivenComposedConditionNodeWithOrOperatorWithExactMatch_EvalsAnd MatchMode = MatchModes.Exact }; - var mockDeferredEval = new Mock(); - mockDeferredEval.SetupSequence(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact))) + var deferredEval = Mock.Of(); + Mock.Get(deferredEval) + .SetupSequence(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact))) .Returns(() => { - return (c) => true; + return (_) => true; }) .Returns(() => { - return (c) => true; + return (_) => true; }) .Throws(new NotImplementedException("Shouldn't have gotten any more deferred evals.")); var conditionsTreeAnalyzer = Mock.Of>(); - var sut = new ClassicConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); + var sut = new ClassicConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); // Act - bool actual = sut.Eval(composedConditionNode, conditions, evaluationOptions); + var actual = sut.Eval(composedConditionNode, conditions, evaluationOptions); // Assert actual.Should().BeTrue(); - mockDeferredEval.Verify(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact)), Times.Exactly(2)); + Mock.Get(deferredEval) + .Verify(x => x.GetDeferredEvalFor(It.IsAny>(), It.Is(mm => mm == MatchModes.Exact)), Times.Exactly(2)); } } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/EqualOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/EqualOperatorEvalStrategyTests.cs index 9391f199..42baafdc 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/EqualOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/EqualOperatorEvalStrategyTests.cs @@ -8,7 +8,7 @@ namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation public class EqualOperatorEvalStrategyTests { - public static IEnumerable UnsupportedOperandTypesCombinations = new[] + public static IEnumerable UnsupportedOperandTypesCombinations => new[] { new[] { 1, new object() }, new[] { new object(), 1 } diff --git a/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/GreaterThanOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/GreaterThanOperatorEvalStrategyTests.cs index bc8c1d3e..fa63c2a6 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/GreaterThanOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/GreaterThanOperatorEvalStrategyTests.cs @@ -8,7 +8,7 @@ namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation public class GreaterThanOperatorEvalStrategyTests { - public static IEnumerable UnsupportedOperandTypesCombinations = new[] + public static IEnumerable UnsupportedOperandTypesCombinations => new[] { new[] { 1, new object() }, new[] { new object(), 1 } diff --git a/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategyTests.cs index a9237b5d..5b216801 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategyTests.cs @@ -8,7 +8,7 @@ namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation public class GreaterThanOrEqualOperatorEvalStrategyTests { - public static IEnumerable UnsupportedOperandTypesCombinations = new[] + public static IEnumerable UnsupportedOperandTypesCombinations => new[] { new[] { 1, new object() }, new[] { new object(), 1 } diff --git a/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/LesserThanOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/LesserThanOperatorEvalStrategyTests.cs index b0607de7..1dc57849 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/LesserThanOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/LesserThanOperatorEvalStrategyTests.cs @@ -8,7 +8,7 @@ namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation public class LesserThanOperatorEvalStrategyTests { - public static IEnumerable UnsupportedOperandTypesCombinations = new[] + public static IEnumerable UnsupportedOperandTypesCombinations => new[] { new[] { 1, new object() }, new[] { new object(), 1 } diff --git a/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategyTests.cs index e97457e9..51b2bf78 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategyTests.cs @@ -8,7 +8,7 @@ namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation public class LesserThanOrEqualOperatorEvalStrategyTests { - public static IEnumerable UnsupportedOperandTypesCombinations = new[] + public static IEnumerable UnsupportedOperandTypesCombinations => new[] { new[] { 1, new object() }, new[] { new object(), 1 } diff --git a/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotEqualOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotEqualOperatorEvalStrategyTests.cs index d1685ee1..367e1127 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotEqualOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotEqualOperatorEvalStrategyTests.cs @@ -8,7 +8,7 @@ namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation public class NotEqualOperatorEvalStrategyTests { - public static IEnumerable UnsupportedOperandTypesCombinations = new[] + public static IEnumerable UnsupportedOperandTypesCombinations => new[] { new[] { 1, new object() }, new[] { new object(), 1 } diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/CompilationRulesSourceMiddlewareTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/CompilationRulesSourceMiddlewareTests.cs index 5a9ad168..317ea96d 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/CompilationRulesSourceMiddlewareTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/CompilationRulesSourceMiddlewareTests.cs @@ -31,13 +31,13 @@ public async Task HandleAddRuleAsync_GivenRuleWithCompiledConditionAndNextDelega }; bool nextDelegateWasInvoked = false; - var nextDelegate = new AddRuleDelegate((args) => + var nextDelegate = new AddRuleDelegate((_) => { nextDelegateWasInvoked = true; return Task.CompletedTask; }); - Expression, bool>> expectedExpression = (evaluationContext) => true; + Expression, bool>> expectedExpression = (_) => true; var ruleConditionsExpressionBuilder = Mock.Of>(); Mock.Get(ruleConditionsExpressionBuilder) @@ -73,13 +73,13 @@ public async Task HandleAddRuleAsync_GivenRuleWithoutConditionsAndNextDelegate_I }; bool nextDelegateWasInvoked = false; - var nextDelegate = new AddRuleDelegate((args) => + var nextDelegate = new AddRuleDelegate((_) => { nextDelegateWasInvoked = true; return Task.CompletedTask; }); - Expression, bool>> expectedExpression = (evaluationContext) => true; + Expression, bool>> expectedExpression = (_) => true; var ruleConditionsExpressionBuilder = Mock.Of>(); Mock.Get(ruleConditionsExpressionBuilder) @@ -115,13 +115,13 @@ public async Task HandleAddRuleAsync_GivenRuleWithUncompiledConditionAndNextDele }; bool nextDelegateWasInvoked = false; - var nextDelegate = new AddRuleDelegate((args) => + var nextDelegate = new AddRuleDelegate((_) => { nextDelegateWasInvoked = true; return Task.CompletedTask; }); - Expression, bool>> expectedExpression = (evaluationContext) => true; + Expression, bool>> expectedExpression = (_) => true; var ruleConditionsExpressionBuilder = Mock.Of>(); Mock.Get(ruleConditionsExpressionBuilder) @@ -168,13 +168,13 @@ public async Task HandleGetRulesAsync_GivenArgsFilteringToRulesWithCompiledCondi }; bool nextDelegateWasInvoked = false; - var nextDelegate = new GetRulesDelegate((args) => + var nextDelegate = new GetRulesDelegate((_) => { nextDelegateWasInvoked = true; return Task.FromResult>>(expectedRules); }); - Expression, bool>> expectedExpression = (evaluationContext) => true; + Expression, bool>> expectedExpression = (_) => true; var ruleConditionsExpressionBuilder = Mock.Of>(); Mock.Get(ruleConditionsExpressionBuilder) @@ -218,13 +218,13 @@ public async Task HandleGetRulesAsync_GivenArgsFilteringToRulesWithoutConditions }; bool nextDelegateWasInvoked = false; - var nextDelegate = new GetRulesDelegate((args) => + var nextDelegate = new GetRulesDelegate((_) => { nextDelegateWasInvoked = true; return Task.FromResult>>(expectedRules); }); - Expression, bool>> expectedExpression = (evaluationContext) => true; + Expression, bool>> expectedExpression = (_) => true; var ruleConditionsExpressionBuilder = Mock.Of>(); Mock.Get(ruleConditionsExpressionBuilder) @@ -268,13 +268,13 @@ public async Task HandleGetRulesAsync_GivenArgsFilteringToRulesWithUncompiledCon }; bool nextDelegateWasInvoked = false; - var nextDelegate = new GetRulesDelegate((args) => + var nextDelegate = new GetRulesDelegate((_) => { nextDelegateWasInvoked = true; return Task.FromResult>>(expectedRules); }); - Expression, bool>> expectedExpression = (evaluationContext) => true; + Expression, bool>> expectedExpression = (_) => true; var ruleConditionsExpressionBuilder = Mock.Of>(); Mock.Get(ruleConditionsExpressionBuilder) @@ -323,13 +323,13 @@ public async Task HandleGetRulesFilteredAsync_GivenArgsFilteringToRulesWithCompi }; bool nextDelegateWasInvoked = false; - var nextDelegate = new GetRulesFilteredDelegate((args) => + var nextDelegate = new GetRulesFilteredDelegate((_) => { nextDelegateWasInvoked = true; return Task.FromResult>>(expectedRules); }); - Expression, bool>> expectedExpression = (evaluationContext) => true; + Expression, bool>> expectedExpression = (_) => true; var ruleConditionsExpressionBuilder = Mock.Of>(); Mock.Get(ruleConditionsExpressionBuilder) @@ -371,13 +371,13 @@ public async Task HandleGetRulesFilteredAsync_GivenArgsFilteringToRulesWithoutCo }; bool nextDelegateWasInvoked = false; - var nextDelegate = new GetRulesFilteredDelegate((args) => + var nextDelegate = new GetRulesFilteredDelegate((_) => { nextDelegateWasInvoked = true; return Task.FromResult>>(expectedRules); }); - Expression, bool>> expectedExpression = (evaluationContext) => true; + Expression, bool>> expectedExpression = (_) => true; var ruleConditionsExpressionBuilder = Mock.Of>(); Mock.Get(ruleConditionsExpressionBuilder) @@ -419,13 +419,13 @@ public async Task HandleGetRulesFilteredAsync_GivenArgsFilteringToRulesWithUncom }; bool nextDelegateWasInvoked = false; - var nextDelegate = new GetRulesFilteredDelegate((args) => + var nextDelegate = new GetRulesFilteredDelegate((_) => { nextDelegateWasInvoked = true; return Task.FromResult>>(expectedRules); }); - Expression, bool>> expectedExpression = (evaluationContext) => true; + Expression, bool>> expectedExpression = (_) => true; var ruleConditionsExpressionBuilder = Mock.Of>(); Mock.Get(ruleConditionsExpressionBuilder) @@ -473,13 +473,13 @@ public async Task HandleUpdateRuleAsync_GivenRuleWithCompiledConditionAndNextDel }; bool nextDelegateWasInvoked = false; - var nextDelegate = new UpdateRuleDelegate((args) => + var nextDelegate = new UpdateRuleDelegate((_) => { nextDelegateWasInvoked = true; return Task.CompletedTask; }); - Expression, bool>> expectedExpression = (evaluationContext) => true; + Expression, bool>> expectedExpression = (_) => true; var ruleConditionsExpressionBuilder = Mock.Of>(); Mock.Get(ruleConditionsExpressionBuilder) @@ -515,13 +515,13 @@ public async Task HandleUpdateRuleAsync_GivenRuleWithoutConditionsAndNextDelegat }; bool nextDelegateWasInvoked = false; - var nextDelegate = new UpdateRuleDelegate((args) => + var nextDelegate = new UpdateRuleDelegate((_) => { nextDelegateWasInvoked = true; return Task.CompletedTask; }); - Expression, bool>> expectedExpression = (evaluationContext) => true; + Expression, bool>> expectedExpression = (_) => true; var ruleConditionsExpressionBuilder = Mock.Of>(); Mock.Get(ruleConditionsExpressionBuilder) @@ -557,13 +557,13 @@ public async Task HandleUpdateRuleAsync_GivenRuleWithUncompiledConditionAndNextD }; bool nextDelegateWasInvoked = false; - var nextDelegate = new UpdateRuleDelegate((args) => + var nextDelegate = new UpdateRuleDelegate((_) => { nextDelegateWasInvoked = true; return Task.CompletedTask; }); - Expression, bool>> expectedExpression = (evaluationContext) => true; + Expression, bool>> expectedExpression = (_) => true; var ruleConditionsExpressionBuilder = Mock.Of>(); Mock.Get(ruleConditionsExpressionBuilder) diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/CompiledConditionsEvalEngineTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/CompiledConditionsEvalEngineTests.cs index 9e8b4515..75b70d16 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/CompiledConditionsEvalEngineTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/CompiledConditionsEvalEngineTests.cs @@ -19,7 +19,7 @@ public void Eval_GivenCompiledConditionNodeConditionsAndExcludeRulesWithoutSearc // Arrange var ruleResult = CreateTestRule(); var expectedRule = ruleResult.Rule; - Func, bool> expectedExpression = (evaluationContext) => true; + Func, bool> expectedExpression = (_) => true; expectedRule.RootCondition.Properties[ConditionNodeProperties.CompilationProperties.CompiledDelegateKey] = expectedExpression; var conditions = new Dictionary(); var evaluationOptions = new EvaluationOptions @@ -50,7 +50,7 @@ public void Eval_GivenCompiledConditionNodeConditionsAndExcludeRulesWithoutSearc // Arrange var ruleResult = CreateTestRule(); var expectedRule = ruleResult.Rule; - Func, bool> expectedExpression = (evaluationContext) => true; + Func, bool> expectedExpression = (_) => true; expectedRule.RootCondition.Properties[ConditionNodeProperties.CompilationProperties.CompiledDelegateKey] = expectedExpression; var conditions = new Dictionary(); var evaluationOptions = new EvaluationOptions @@ -81,7 +81,7 @@ public void Eval_GivenCompiledConditionNodeConditionsAndIncludeRulesWithoutSearc // Arrange var ruleResult = CreateTestRule(); var expectedRule = ruleResult.Rule; - Func, bool> expectedExpression = (evaluationContext) => true; + Func, bool> expectedExpression = (_) => true; expectedRule.RootCondition.Properties[ConditionNodeProperties.CompilationProperties.CompiledDelegateKey] = expectedExpression; var conditions = new Dictionary(); var evaluationOptions = new EvaluationOptions diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilderTests.cs index e6b83ce2..14e7c059 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/InOneToManyConditionExpressionBuilderTests.cs @@ -32,7 +32,7 @@ var inOneToManyConditionExpressionBuilder { DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0), LeftHandOperand = builder.GetParameter("leftHand"), - RightHandOperand = builder.Constant>(new int[] { 1, 2, 3 }), + RightHandOperand = builder.Constant>(new[] { 1, 2, 3 }), }; var conditionExpression = inOneToManyConditionExpressionBuilder .BuildConditionExpression(builder, args); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotInOneToManyConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotInOneToManyConditionExpressionBuilderTests.cs index 32227774..b29060c5 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotInOneToManyConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/NotInOneToManyConditionExpressionBuilderTests.cs @@ -32,7 +32,7 @@ var notInOneToManyConditionExpressionBuilder { DataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.Integer, typeof(int), 0), LeftHandOperand = builder.GetParameter("leftHand"), - RightHandOperand = builder.Constant>(new int[] { 1, 2, 3 }), + RightHandOperand = builder.Constant>(new[] { 1, 2, 3 }), }; var conditionExpression = notInOneToManyConditionExpressionBuilder .BuildConditionExpression(builder, args); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilderTests.cs index 07c1a70a..309f222c 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/StartsWithOneToOneConditionExpressionBuilderTests.cs @@ -45,11 +45,6 @@ var endsWithOneToOneConditionExpressionBuilder public void BuildConditionExpression_GivenLeftExpressionRightExpressionAndDataTypeConfigurationForString_ReturnsConditionExpression() { // Arrange - - var leftHandExpression = Expression.Parameter(typeof(string), "leftHand"); - var rightHandExpression = Expression.Constant("The", typeof(string)); - var dataTypeConfiguration = DataTypeConfiguration.Create(DataTypes.String, typeof(string), null); - var startsWithOneToOneConditionExpressionBuilder = new StartsWithOneToOneConditionExpressionBuilder(); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionBlockBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionBlockBuilderTests.cs index fb5cd2b3..bc448ce5 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionBlockBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionBlockBuilderTests.cs @@ -902,7 +902,6 @@ public void If_GivenNullTestExpressionAndThenExpression_ThrowsArgumentNullExcept { // Arrange var resultExpression = Expression.Variable(typeof(bool), "result"); - var testExpression = Expression.Equal(Expression.Variable(typeof(int), "x"), Expression.Constant(0, typeof(int))); var thenExpression = Expression.Assign(resultExpression, Expression.Constant(true)); var expressionConfiguration = new ExpressionConfiguration(); @@ -924,7 +923,6 @@ public void If_GivenTestExpressionAndNullThenExpression_ThrowsArgumentNullExcept // Arrange var resultExpression = Expression.Variable(typeof(bool), "result"); var testExpression = Expression.Equal(Expression.Variable(typeof(int), "x"), Expression.Constant(0, typeof(int))); - var thenExpression = Expression.Assign(resultExpression, Expression.Constant(true)); var expressionConfiguration = new ExpressionConfiguration(); var expressionBuilderFactory = Mock.Of(); @@ -1220,7 +1218,7 @@ public void OrElse_GivenNullCollectionOfExpressions_ThrowsArgumentNullException( public void Return_GivenNullReturnValueExpression_ThrowsArgumentNullException() { // Arrange - var expressionConfiguration = new ExpressionConfiguration() + var expressionConfiguration = new ExpressionConfiguration { ReturnLabelTarget = Expression.Label(typeof(string)), }; @@ -1242,7 +1240,7 @@ public void Return_GivenReturnValueExpression_CreatesReturnExpressionAndAddsToEx // Arrange var returnValueExpression = Expression.Constant("testValue"); - var expressionConfiguration = new ExpressionConfiguration() + var expressionConfiguration = new ExpressionConfiguration { ReturnLabelTarget = Expression.Label(typeof(string)), }; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionBuilderTests.cs index 44f436a1..6618a1dd 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionBuilderTests.cs @@ -175,15 +175,15 @@ public void SetImplementation_GivenNonNullBuilderAction_ReturnsConfiguredExpress ReturnType = typeof(int), }; - var testExpressions = new List() + var testExpressions = new List { Expression.Constant(1), }; - var testVariables = new Dictionary() + var testVariables = new Dictionary { { "result", Expression.Variable(typeof(int), "result") }, }; - var testLabelTargets = new Dictionary() + var testLabelTargets = new Dictionary { { "Return", Expression.Label(typeof(int)) }, }; diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilderTests.cs index 918bf22b..776f47a9 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToManyValueConditionNodeExpressionBuilderTests.cs @@ -35,7 +35,7 @@ public void Build_GivenLeftHandOperatorRightHandAndDataTypeConfiguration_Returns var conditionExpressionBuilder = Mock.Of(); Mock.Get(conditionExpressionBuilder) .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny())) - .Callback((b, args) => + .Callback((_, args) => { actualLeftExpression = args.LeftHandOperand; actualRightExpression = args.RightHandOperand; @@ -89,10 +89,10 @@ public void Build_GivenLeftHandOperatorRightHandAndDataTypeConfiguration_Returns FluentActions.Invoking(() => compiledLambdaExpression = lambdaExpression.Compile()) .Should() .NotThrow("expression should be compilable"); - FluentActions.Invoking(() => compiledLambdaExpression.Invoke(null, new string[] { "dummy" })) + FluentActions.Invoking(() => compiledLambdaExpression.Invoke(null, new[] { "dummy" })) .Should() .NotThrow("compiled expression should be executable"); - FluentActions.Invoking(() => compiledLambdaExpression.Invoke(new string[] { "abc" }, new string[] { "dummy" })) + FluentActions.Invoking(() => compiledLambdaExpression.Invoke(new[] { "abc" }, new[] { "dummy" })) .Should() .NotThrow("compiled expression should be able to deal with null left operand"); actualLeftExpression.Should().NotBeNull(); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilderTests.cs index 53e0a351..30238e25 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ManyToOneValueConditionNodeExpressionBuilderTests.cs @@ -35,7 +35,7 @@ public void Build_GivenLeftHandOperatorRightHandDataTypeConfiguration_ReturnsExp var conditionExpressionBuilder = Mock.Of(); Mock.Get(conditionExpressionBuilder) .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny())) - .Callback((b, args) => + .Callback((_, args) => { actualLeftExpression = args.LeftHandOperand; actualRightExpression = args.RightHandOperand; @@ -92,7 +92,7 @@ public void Build_GivenLeftHandOperatorRightHandDataTypeConfiguration_ReturnsExp FluentActions.Invoking(() => compiledLambdaExpression.Invoke(null, "dummy")) .Should() .NotThrow("compiled expression should be executable"); - FluentActions.Invoking(() => compiledLambdaExpression.Invoke(new string[] { "abc" }, "dummy")) + FluentActions.Invoking(() => compiledLambdaExpression.Invoke(new[] { "abc" }, "dummy")) .Should() .NotThrow("compiled expression should be able to deal with null left operand"); actualLeftExpression.Should().NotBeNull(); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilderTests.cs index e288a937..acbf7879 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToManyValueConditionNodeExpressionBuilderTests.cs @@ -35,7 +35,7 @@ public void Build_GivenLefthandOperatorRightHandAndDataTypeConfiguration_Returns var conditionExpressionBuilder = Mock.Of(); Mock.Get(conditionExpressionBuilder) .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny())) - .Callback((b, args) => + .Callback((_, args) => { actualLeftExpression = args.LeftHandOperand; actualRightExpression = args.RightHandOperand; @@ -89,10 +89,10 @@ public void Build_GivenLefthandOperatorRightHandAndDataTypeConfiguration_Returns FluentActions.Invoking(() => compiledLambdaExpression = lambdaExpression.Compile()) .Should() .NotThrow("expression should be compilable"); - FluentActions.Invoking(() => compiledLambdaExpression.Invoke(null, new string[] { "dummy" })) + FluentActions.Invoking(() => compiledLambdaExpression.Invoke(null, new[] { "dummy" })) .Should() .NotThrow("compiled expression should be executable"); - FluentActions.Invoking(() => compiledLambdaExpression.Invoke("abc", new string[] { "dummy" })) + FluentActions.Invoking(() => compiledLambdaExpression.Invoke("abc", new[] { "dummy" })) .Should() .NotThrow("compiled expression should be able to deal with null left operand"); actualLeftExpression.Should().NotBeNull(); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilderTests.cs index be58f0ef..73146b2a 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/OneToOneValueConditionNodeExpressionBuilderTests.cs @@ -34,7 +34,7 @@ public void Build_GivenExpressionBuilderAndArgs_BuildsExpression() var conditionExpressionBuilder = Mock.Of(); Mock.Get(conditionExpressionBuilder) .Setup(x => x.BuildConditionExpression(It.IsAny(), It.IsAny())) - .Callback((b, args) => + .Callback((_, args) => { actualLeftExpression = args.LeftHandOperand; actualRightExpression = args.RightHandOperand; From c7bbb7ef9cb005d3a2a2f427424b4e75b0674431 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Wed, 22 Feb 2023 22:48:33 +0000 Subject: [PATCH 22/54] chore: fix static analysis issues found by Codacy (2) --- .../Compiled/RuleConditionsExpressionBuilder.cs | 4 ++++ .../CompilationRulesSourceMiddlewareTests.cs | 17 +++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Rules.Framework/Evaluation/Compiled/RuleConditionsExpressionBuilder.cs b/src/Rules.Framework/Evaluation/Compiled/RuleConditionsExpressionBuilder.cs index 89b3b320..760d83d5 100644 --- a/src/Rules.Framework/Evaluation/Compiled/RuleConditionsExpressionBuilder.cs +++ b/src/Rules.Framework/Evaluation/Compiled/RuleConditionsExpressionBuilder.cs @@ -13,6 +13,10 @@ internal class RuleConditionsExpressionBuilder { protected static readonly MethodInfo multiplicityEvaluateMethod = typeof(MultiplicityEvaluator) .GetMethod(nameof(MultiplicityEvaluator.Evaluate)); + + protected RuleConditionsExpressionBuilder() + { + } } internal sealed class RuleConditionsExpressionBuilder : RuleConditionsExpressionBuilder, IRuleConditionsExpressionBuilder diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/CompilationRulesSourceMiddlewareTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/CompilationRulesSourceMiddlewareTests.cs index 317ea96d..6e2dd116 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/CompilationRulesSourceMiddlewareTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/CompilationRulesSourceMiddlewareTests.cs @@ -2,6 +2,7 @@ namespace Rules.Framework.Tests.Evaluation.Compiled { using System; using System.Collections.Generic; + using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; using FluentAssertions; @@ -293,10 +294,12 @@ public async Task HandleGetRulesAsync_GivenArgsFilteringToRulesWithUncompiledCon var actualRules = await compilationRulesSourceMiddleware.HandleGetRulesAsync(getRulesArgs, nextDelegate).ConfigureAwait(false); // Assert - expectedRule.RootCondition.Properties.Should().HaveCount(2); - expectedRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.IsCompiledKey) + actualRules.Should().HaveCount(1); + var actualRule = actualRules.First(); + actualRule.RootCondition.Properties.Should().HaveCount(2); + actualRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.IsCompiledKey) .WhoseValue.Should().Be(true); - expectedRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.CompiledDelegateKey) + actualRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.CompiledDelegateKey) .WhoseValue.Should().BeOfType, bool>>(); nextDelegateWasInvoked.Should().BeTrue(); @@ -444,10 +447,12 @@ public async Task HandleGetRulesFilteredAsync_GivenArgsFilteringToRulesWithUncom var actualRules = await compilationRulesSourceMiddleware.HandleGetRulesFilteredAsync(getRulesFilteredArgs, nextDelegate).ConfigureAwait(false); // Assert - expectedRule.RootCondition.Properties.Should().HaveCount(2); - expectedRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.IsCompiledKey) + actualRules.Should().HaveCount(1); + var actualRule = actualRules.First(); + actualRule.RootCondition.Properties.Should().HaveCount(2); + actualRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.IsCompiledKey) .WhoseValue.Should().Be(true); - expectedRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.CompiledDelegateKey) + actualRule.RootCondition.Properties.Should().ContainKey(ConditionNodeProperties.CompilationProperties.CompiledDelegateKey) .WhoseValue.Should().BeOfType, bool>>(); nextDelegateWasInvoked.Should().BeTrue(); From 1ecbd0067a92c48e280bd070526ea65ae1ddaa96 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Wed, 22 Feb 2023 22:53:03 +0000 Subject: [PATCH 23/54] chore: fix static analysis issues found by Codacy (3) --- .../Compiled/ExpressionBuilders/ExpressionBlockBuilderTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionBlockBuilderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionBlockBuilderTests.cs index bc448ce5..8fb502db 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionBlockBuilderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ExpressionBuilders/ExpressionBlockBuilderTests.cs @@ -921,7 +921,6 @@ public void If_GivenNullTestExpressionAndThenExpression_ThrowsArgumentNullExcept public void If_GivenTestExpressionAndNullThenExpression_ThrowsArgumentNullException() { // Arrange - var resultExpression = Expression.Variable(typeof(bool), "result"); var testExpression = Expression.Equal(Expression.Variable(typeof(int), "x"), Expression.Constant(0, typeof(int))); var expressionConfiguration = new ExpressionConfiguration(); From a3000e190c4106d7c19dce2d5580827f40da48cd Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 5 Mar 2023 14:29:48 +0000 Subject: [PATCH 24/54] ci: add benchmark report results to Markdown via custom PS script --- .gitignore | 1 + run-benchmarks.ps1 | 71 +++++++++++++++++ .../CustomBaselineClassifierColumn.cs | 36 +++++++++ .../CustomJsonExporter.cs | 47 +++++++++++ .../Rules.Framework.BenchmarkTests/Program.cs | 45 ++++++++--- .../Results2Markdown/Report.tt | 77 +++++++++++++++++++ .../Rules.Framework.BenchmarkTests.csproj | 1 + 7 files changed, 268 insertions(+), 10 deletions(-) create mode 100644 run-benchmarks.ps1 create mode 100644 tests/Rules.Framework.BenchmarkTests/CustomBaselineClassifierColumn.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/CustomJsonExporter.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt diff --git a/.gitignore b/.gitignore index 0ab3e391..31bf65c0 100644 --- a/.gitignore +++ b/.gitignore @@ -558,6 +558,7 @@ typings/ # Specific ones added coverage-outputs/** .env +tmp/** ### Rules Framework Web UI ### !src/Rules.Framework.WebUI/node_modules/ diff --git a/run-benchmarks.ps1 b/run-benchmarks.ps1 new file mode 100644 index 00000000..5259939f --- /dev/null +++ b/run-benchmarks.ps1 @@ -0,0 +1,71 @@ +param ([switch] $KeepBenchmarksFiles) + +$globalTools = dotnet tool list -g +$grabTool = $globalTools | Select-String -Pattern "dotnet-grab" +$t4Tool = $globalTools | Select-String -Pattern "dotnet-t4" + +if (!$grabTool) { + dotnet tool install dotnet-grab --global --ignore-failed-sources +} + +if (!$t4Tool) { + dotnet tool install dotnet-t4 --global --ignore-failed-sources +} + +$originalDir = (Get-Location).Path +$timestamp = [DateTime]::UtcNow.ToString("yyyyMMdd_hhmmss") + +$directoryFound = Get-ChildItem -Path $originalDir -Directory | Select-String -Pattern "tmp" +if (!$directoryFound) { + New-Item -Name "tmp" -ItemType Directory > $null +} + +$directoryFound = Get-ChildItem -Path "$originalDir\\tmp" -Directory | Select-String -Pattern "benchmarks" +if (!$directoryFound) { + New-Item -Name "benchmarks" -ItemType Directory -Path "$originalDir\\tmp" > $null +} + +$directoryFound = Get-ChildItem -Path "$originalDir\\tmp\\benchmarks" -Directory | Select-String -Pattern $timestamp +if (!$directoryFound) { + New-Item -Name $timestamp -ItemType Directory -Path "$originalDir\\tmp\\benchmarks" > $null +} + +$reportDir = "$originalDir\\tmp\\benchmarks\\$timestamp" + +# Ensure all packages restored before running benchmarks +dotnet restore rules-framework.sln + +# Build benchmarks binaries +dotnet build -c Release .\tests\Rules.Framework.BenchmarkTests\Rules.Framework.BenchmarkTests.csproj -o "$reportDir\\bin" --framework net6.0 + +Set-Location -Path $reportDir + +# Run benchmarks +bin\Rules.Framework.BenchmarkTests.exe -a artifacts + +# Determine results file +$filteredResultsFiles = Get-ChildItem -Path "$reportDir/artifacts/results" -File -Filter *.json +if ($filteredResultsFiles) { + $resultsFile = $filteredResultsFiles.Name + + # Get report dependencies + grab newtonsoft.json@13.0.2 + + # Generate report + t4 -p:ResultsFile="$reportDir/artifacts/results/$resultsFile" -P="$reportDir/packages/newtonsoft.json/13.0.2/lib/net6.0" -r="Newtonsoft.Json.dll" ` + -o "$reportDir/report.md" "$originalDir/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt" +} + +if (!$KeepBenchmarksFiles) { + if ($directoryFound = Get-ChildItem -Path $reportDir -Directory | Select-String -Pattern "artifacts") { + Remove-Item -Path "$reportDir/artifacts" -Recurse > $null + } + if ($directoryFound = Get-ChildItem -Path $reportDir -Directory | Select-String -Pattern "bin") { + Remove-Item -Path "$reportDir/bin" -Recurse > $null + } + if ($directoryFound = Get-ChildItem -Path $reportDir -Directory | Select-String -Pattern "packages") { + Remove-Item -Path "$reportDir/packages" -Recurse > $null + } +} + +Set-Location -Path $originalDir \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/CustomBaselineClassifierColumn.cs b/tests/Rules.Framework.BenchmarkTests/CustomBaselineClassifierColumn.cs new file mode 100644 index 00000000..4cd312b8 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/CustomBaselineClassifierColumn.cs @@ -0,0 +1,36 @@ +namespace Rules.Framework.BenchmarkTests +{ + using System; + using BenchmarkDotNet.Columns; + using BenchmarkDotNet.Reports; + using BenchmarkDotNet.Running; + + internal class CustomBaselineClassifierColumn : IColumn + { + private readonly Func classifyLogicFunc; + + public CustomBaselineClassifierColumn(Func classifyLogicFunc) + { + this.classifyLogicFunc = classifyLogicFunc; + } + + public bool AlwaysShow => true; + public ColumnCategory Category => ColumnCategory.Baseline; + public string ColumnName => "Baseline"; + public string Id => nameof(CustomBaselineClassifierColumn); + public bool IsNumeric => false; + public string Legend => "Sets wether a test case is a baseline."; + public int PriorityInCategory => 0; + public UnitType UnitType => UnitType.Dimensionless; + + public string GetValue(Summary summary, BenchmarkCase benchmarkCase) + => this.classifyLogicFunc.Invoke(benchmarkCase) ? "Yes" : "No"; + + public string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style) + => this.GetValue(summary, benchmarkCase); + + public bool IsAvailable(Summary summary) => true; + + public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase) => false; + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/CustomJsonExporter.cs b/tests/Rules.Framework.BenchmarkTests/CustomJsonExporter.cs new file mode 100644 index 00000000..0bff951f --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/CustomJsonExporter.cs @@ -0,0 +1,47 @@ +namespace Rules.Framework.BenchmarkTests +{ + using System.Collections.Generic; + using BenchmarkDotNet.Exporters; + using BenchmarkDotNet.Exporters.Json; + using BenchmarkDotNet.Reports; + + internal class CustomJsonExporter : JsonExporterBase + { + public CustomJsonExporter(string fileNameSuffix = "", bool indentJson = false, bool excludeMeasurements = false) + : base(indentJson, excludeMeasurements) + { + FileNameSuffix = fileNameSuffix; + } + + public static IExporter Default => FullCompressed; + + public static IExporter Full => new CustomJsonExporter("-full", indentJson: true); + + public static IExporter FullCompressed => new CustomJsonExporter("-full-compressed"); + + protected override string FileNameSuffix { get; } = string.Empty; + + protected override IReadOnlyDictionary GetDataToSerialize(Summary summary) + { + var dataToSerialize = base.GetDataToSerialize(summary); + + var baselineClassifierColumn = summary.Table.Columns.First(x => x.OriginalColumn.ColumnName == "Baseline"); + + var benchmarksDataToSerialize = ((IEnumerable>)dataToSerialize["Benchmarks"]).ToArray(); + + var newBenchmarks = new List>(); + for (int i = 0; i < benchmarksDataToSerialize.Length; i++) + { + var benchmarkData = (Dictionary)benchmarksDataToSerialize[i]; + benchmarkData["Baseline"] = baselineClassifierColumn.Content[i]; + newBenchmarks.Add(benchmarkData); + } + + var newDataToSerialize = dataToSerialize.ToDictionary(x => x.Key, x => x.Value); + + newDataToSerialize["Benchmarks"] = newBenchmarks; + + return newDataToSerialize; + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Program.cs b/tests/Rules.Framework.BenchmarkTests/Program.cs index c1a11ec0..77422cdf 100644 --- a/tests/Rules.Framework.BenchmarkTests/Program.cs +++ b/tests/Rules.Framework.BenchmarkTests/Program.cs @@ -4,24 +4,49 @@ using BenchmarkDotNet.Exporters; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Running; +using McMaster.Extensions.CommandLineUtils; +using Rules.Framework.BenchmarkTests; [assembly: SimpleJob(RuntimeMoniker.Net60)] internal static class Program { - private static void Main(string[] args) + private static int Main(string[] args) { - Console.WriteLine("Starting benchmark tests."); - Console.WriteLine(); + var app = new CommandLineApplication(); - var manualConfig = ManualConfig.CreateMinimumViable(); - manualConfig.AddDiagnoser(MemoryDiagnoser.Default); - manualConfig.AddHardwareCounters(HardwareCounter.BranchInstructions, HardwareCounter.BranchMispredictions); - manualConfig.AddExporter(HtmlExporter.Default); + app.HelpOption(); - _ = BenchmarkRunner.Run(typeof(Program).Assembly, manualConfig); + var artifactsPathOption = app.Option("-a|--artifacts-path ", "Sets the artifacts path", CommandOptionType.SingleValue, config => + { + config.DefaultValue = "artifacts"; + }); - Console.WriteLine("Press any key to exit..."); - Console.Read(); + app.OnExecute(() => + { + Console.WriteLine("Starting benchmark tests."); + Console.WriteLine(); + + var manualConfig = ManualConfig.CreateMinimumViable(); + manualConfig.AddDiagnoser(MemoryDiagnoser.Default); + manualConfig.AddHardwareCounters(HardwareCounter.BranchInstructions, HardwareCounter.BranchMispredictions); + manualConfig.AddExporter(HtmlExporter.Default); + manualConfig.AddExporter(CustomJsonExporter.Full); + manualConfig.WithOption(ConfigOptions.JoinSummary, true); + + var artifactsPath = artifactsPathOption.Value(); + if (artifactsPath is not null or "") + { + manualConfig.WithArtifactsPath(artifactsPath); + } + + var column = new CustomBaselineClassifierColumn( + bc => bc.Parameters.Items.Any(p => p.Name == "EnableCompilation" && (bool)p.Value == false)); + manualConfig.AddColumn(column); + + _ = BenchmarkRunner.Run(typeof(Program).Assembly, manualConfig); + }); + + return app.Execute(args); } } \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt b/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt new file mode 100644 index 00000000..a8e9fcaf --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt @@ -0,0 +1,77 @@ +<#@ template language="C#" #> +<#@ output extension=".md" #> +<#@ import namespace="System.IO" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="Newtonsoft.Json" #> +<#@ parameter name="ResultsFile" type="string" #> +<# + using var streamReader = File.OpenText(ResultsFile); + var rawJsonText = streamReader.ReadToEnd(); + dynamic results = JsonConvert.DeserializeObject(rawJsonText); + dynamic hostEnvInfo = results.HostEnvironmentInfo; + + dynamic baselines = ((IEnumerable)results.Benchmarks).Where(x => x.Baseline == "Yes"); + dynamic nonBaselines = ((IEnumerable)results.Benchmarks).Where(x => x.Baseline == "No"); +#> +# Benchmark Results Report + +Date & Time: <#= DateTime.UtcNow.ToString("yyyy-MM-dd hh:mm:ssZ") #> + +## Environment + +><#= hostEnvInfo.BenchmarkDotNetCaption #> Version=<#= hostEnvInfo.BenchmarkDotNetVersion #> +> +>Processor=<#= hostEnvInfo.ProcessorName #>, <#= hostEnvInfo.PhysicalCoreCount #> physical cores, <#= hostEnvInfo.LogicalCoreCount #> logical cores +> +>Architecture=<#= hostEnvInfo.Architecture #>, Runtime=<#= hostEnvInfo.RuntimeVersion #>, Configuration=<#= hostEnvInfo.Configuration #> +> +>.NET CLI Version=<#= hostEnvInfo.DotNetCliVersion #> + +## Statistics + +| Name | Parameters | Mean | Std Error | Branch Instructions/Op | Branch Mispredictions/Op | GC Gen0 | Allocated Memory | +| ---- | ---------- | ---- | --------- | ---------------------- | ------------------------ | ------- | ---------------- | +<# foreach (dynamic benchmark in results.Benchmarks) +{ + var name = $"{benchmark.Type}.{benchmark.Method}"; + var parameters = benchmark.Parameters; + var mean = Math.Round(Convert.ToDecimal(benchmark.Statistics.Mean), 2); + var stdError = Math.Round(Convert.ToDecimal(benchmark.Statistics.StandardError), 2); + var metrics = benchmark.Metrics as IEnumerable; + var branchInstructions = Math.Round(Convert.ToDecimal(metrics.FirstOrDefault(x => x.Descriptor.Id == "BranchInstructions")?.Value ?? 0), 0); + var branchMispredictions = Math.Round(Convert.ToDecimal(metrics.FirstOrDefault(x => x.Descriptor.Id == "BranchMispredictions")?.Value ?? 0), 0); + var gen0 = Math.Round(Convert.ToDecimal(metrics.FirstOrDefault(x => x.Descriptor.Id == "Gen0Collects")?.Value ?? 0), 0); + var allocatedMemory = Math.Round(Convert.ToDecimal(metrics.FirstOrDefault(x => x.Descriptor.Id == "Allocated Memory")?.Value ?? 0), 2); +#> +| <#= name #> | <#= parameters #> | <#= mean #> | <#= stdError #> | <#= branchInstructions #> | <#= branchMispredictions #> | <#= gen0 #> | <#= allocatedMemory #> | +<#} #> + +## Baseline Comparison + +| Name | Baseline | Compare To | Mean [Baseline] | Mean [Compare To] | Mean [Compare %] | Allocated Memory [Baseline] | Allocated Memory [Compare To] | Allocated Memory [Compare %] | +| ---- | -------- | ---------- | --------------- | ----------------- | ---------------- | --------------------------- | ----------------------------- | ---------------------------- | +<# +foreach (dynamic baseline in baselines) +{ + var key = $"{baseline.Type}.{baseline.Method}"; + var nonBaselinesGroup = ((IEnumerable)nonBaselines).Where(x => $"{x.Type}.{x.Method}" == key); + foreach (dynamic nonBaseline in nonBaselinesGroup) + { + var name = key; + var baselineParameters = baseline.Parameters; + var compareToParameters = nonBaseline.Parameters; + var baselineMean = Math.Round(Convert.ToDecimal(baseline.Statistics.Mean), 2); + var compareToMean = Math.Round(Convert.ToDecimal(nonBaseline.Statistics.Mean), 2); + var meanCompareRate = Math.Round(Convert.ToDecimal((baselineMean - compareToMean) / baselineMean * 100), 2); + var baselineMetrics = baseline.Metrics as IEnumerable; + var compareToMetrics = nonBaseline.Metrics as IEnumerable; + var baselineAllocatedMemory = Math.Round(Convert.ToDecimal(baselineMetrics.FirstOrDefault(x => x.Descriptor.Id == "Allocated Memory")?.Value ?? 0), 2); + var compareToAllocatedMemory = Math.Round(Convert.ToDecimal(compareToMetrics.FirstOrDefault(x => x.Descriptor.Id == "Allocated Memory")?.Value ?? 0), 2); + var allocatedMemoryCompareRate = Math.Round(Convert.ToDecimal((baselineAllocatedMemory - compareToAllocatedMemory) / baselineAllocatedMemory * 100), 2); +#> +| <#= name #> | <#= baselineParameters #> | <#= compareToParameters #> | <#= baselineMean #> | <#= compareToMean #> | <#= meanCompareRate #> % | <#= baselineAllocatedMemory #> | <#= compareToAllocatedMemory #> | <#= allocatedMemoryCompareRate #> % | +<# + } +} +#> \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj b/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj index 5d767723..09bb7944 100644 --- a/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj +++ b/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj @@ -11,6 +11,7 @@ + From 2d85a4dfd7567aa23a9b3e2f7386c1ed4e6d0bc1 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Mon, 6 Mar 2023 00:43:45 +0000 Subject: [PATCH 25/54] chore: refactor benchmark report results generation --- .../CustomJsonExporter.cs | 47 ----- .../Exporters/Json/BenchmarkReport.cs | 17 ++ .../Json/BenchmarkStatisticsComparisonItem.cs | 23 +++ .../Exporters/Json/BenchmarkStatisticsItem.cs | 23 +++ .../Json/BenchmarkStatisticsValue.cs | 11 ++ .../Exporters/Json/CustomJsonExporter.cs | 178 ++++++++++++++++++ .../Exporters/Json/Environment.cs | 23 +++ .../Rules.Framework.BenchmarkTests/Program.cs | 3 +- .../Results2Markdown/Report.tt | 69 +++---- 9 files changed, 306 insertions(+), 88 deletions(-) delete mode 100644 tests/Rules.Framework.BenchmarkTests/CustomJsonExporter.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Exporters/Json/BenchmarkReport.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Exporters/Json/BenchmarkStatisticsComparisonItem.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Exporters/Json/BenchmarkStatisticsItem.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Exporters/Json/BenchmarkStatisticsValue.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Exporters/Json/CustomJsonExporter.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Exporters/Json/Environment.cs diff --git a/tests/Rules.Framework.BenchmarkTests/CustomJsonExporter.cs b/tests/Rules.Framework.BenchmarkTests/CustomJsonExporter.cs deleted file mode 100644 index 0bff951f..00000000 --- a/tests/Rules.Framework.BenchmarkTests/CustomJsonExporter.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace Rules.Framework.BenchmarkTests -{ - using System.Collections.Generic; - using BenchmarkDotNet.Exporters; - using BenchmarkDotNet.Exporters.Json; - using BenchmarkDotNet.Reports; - - internal class CustomJsonExporter : JsonExporterBase - { - public CustomJsonExporter(string fileNameSuffix = "", bool indentJson = false, bool excludeMeasurements = false) - : base(indentJson, excludeMeasurements) - { - FileNameSuffix = fileNameSuffix; - } - - public static IExporter Default => FullCompressed; - - public static IExporter Full => new CustomJsonExporter("-full", indentJson: true); - - public static IExporter FullCompressed => new CustomJsonExporter("-full-compressed"); - - protected override string FileNameSuffix { get; } = string.Empty; - - protected override IReadOnlyDictionary GetDataToSerialize(Summary summary) - { - var dataToSerialize = base.GetDataToSerialize(summary); - - var baselineClassifierColumn = summary.Table.Columns.First(x => x.OriginalColumn.ColumnName == "Baseline"); - - var benchmarksDataToSerialize = ((IEnumerable>)dataToSerialize["Benchmarks"]).ToArray(); - - var newBenchmarks = new List>(); - for (int i = 0; i < benchmarksDataToSerialize.Length; i++) - { - var benchmarkData = (Dictionary)benchmarksDataToSerialize[i]; - benchmarkData["Baseline"] = baselineClassifierColumn.Content[i]; - newBenchmarks.Add(benchmarkData); - } - - var newDataToSerialize = dataToSerialize.ToDictionary(x => x.Key, x => x.Value); - - newDataToSerialize["Benchmarks"] = newBenchmarks; - - return newDataToSerialize; - } - } -} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Exporters/Json/BenchmarkReport.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Json/BenchmarkReport.cs new file mode 100644 index 00000000..764799a0 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Json/BenchmarkReport.cs @@ -0,0 +1,17 @@ +namespace Rules.Framework.BenchmarkTests.Exporters.Json +{ + using System; + + internal class BenchmarkReport + { + public DateTime Date { get; set; } + + public Environment? Environment { get; set; } + + public IEnumerable? Statistics { get; set; } + + public IEnumerable? StatisticsComparison { get; set; } + + public string Title { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Exporters/Json/BenchmarkStatisticsComparisonItem.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Json/BenchmarkStatisticsComparisonItem.cs new file mode 100644 index 00000000..0c5c26e6 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Json/BenchmarkStatisticsComparisonItem.cs @@ -0,0 +1,23 @@ +namespace Rules.Framework.BenchmarkTests.Exporters.Json +{ + internal class BenchmarkStatisticsComparisonItem + { + public BenchmarkStatisticsValue? AllocatedMemoryRate { get; set; } + + public BenchmarkStatisticsValue? BaselineAllocatedMemory { get; set; } + + public BenchmarkStatisticsValue? BaselineMeanTimeTaken { get; set; } + + public string BaselineParameters { get; set; } = string.Empty; + + public BenchmarkStatisticsValue? CompareAllocatedMemory { get; set; } + + public BenchmarkStatisticsValue? CompareMeanTimeTaken { get; set; } + + public string CompareParameters { get; set; } = string.Empty; + + public string Key { get; set; } = string.Empty; + + public BenchmarkStatisticsValue? MeanTimeTakenCompareRate { get; set; } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Exporters/Json/BenchmarkStatisticsItem.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Json/BenchmarkStatisticsItem.cs new file mode 100644 index 00000000..343ea4c4 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Json/BenchmarkStatisticsItem.cs @@ -0,0 +1,23 @@ +namespace Rules.Framework.BenchmarkTests.Exporters.Json +{ + internal class BenchmarkStatisticsItem + { + public BenchmarkStatisticsValue? AllocatedMemory { get; set; } + + public string Baseline { get; set; } = string.Empty; + + public BenchmarkStatisticsValue? BranchInstructionsPerOp { get; set; } + + public BenchmarkStatisticsValue? BranchMispredictionsPerOp { get; set; } + + public BenchmarkStatisticsValue? Gen0Collects { get; set; } + + public string Key { get; set; } = string.Empty; + + public BenchmarkStatisticsValue? MeanTimeTaken { get; set; } + + public string Parameters { get; set; } = string.Empty; + + public BenchmarkStatisticsValue? StandardError { get; set; } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Exporters/Json/BenchmarkStatisticsValue.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Json/BenchmarkStatisticsValue.cs new file mode 100644 index 00000000..c487752e --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Json/BenchmarkStatisticsValue.cs @@ -0,0 +1,11 @@ +namespace Rules.Framework.BenchmarkTests.Exporters.Json +{ + internal class BenchmarkStatisticsValue + { + public string Format { get; set; } = string.Empty; + + public string Unit { get; set; } = string.Empty; + + public decimal Value { get; set; } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Exporters/Json/CustomJsonExporter.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Json/CustomJsonExporter.cs new file mode 100644 index 00000000..33d6f779 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Json/CustomJsonExporter.cs @@ -0,0 +1,178 @@ +namespace Rules.Framework.BenchmarkTests.Exporters.Json +{ + using System.Collections.Generic; + using BenchmarkDotNet.Exporters; + using BenchmarkDotNet.Loggers; + using BenchmarkDotNet.Reports; + using Newtonsoft.Json; + + internal class CustomJsonExporter : ExporterBase + { + public CustomJsonExporter(bool indentJson) + { + this.IndentJson = indentJson; + } + + public static IExporter Compressed => new CustomJsonExporter(indentJson: false); + + public static IExporter Default => Indented; + + public static IExporter Indented => new CustomJsonExporter(indentJson: true); + + protected override string FileExtension => "json"; + + private bool IndentJson { get; } + + public override void ExportToLog(Summary summary, ILogger logger) + { + var report = CustomJsonExporter.CreateReport(summary); + + var settings = new JsonSerializerSettings + { + Formatting = Formatting.None + }; + + if (this.IndentJson) + { + settings.Formatting = Formatting.Indented; + } + + string jsonText = JsonConvert.SerializeObject(report, settings); + + logger.WriteLine(jsonText); + } + + private static decimal CalculateRate(decimal baselineValue, decimal compareValue) + => ((baselineValue - compareValue) / baselineValue) * 100; + + private static BenchmarkStatisticsComparisonItem CreateBenchmarkStatisticsComparisonItem(BenchmarkStatisticsItem? baselineStatisticsItem, BenchmarkStatisticsItem? nonBaselineStatisticsItem) => new BenchmarkStatisticsComparisonItem + { + AllocatedMemoryRate = new BenchmarkStatisticsValue + { + Format = "0.##", + Unit = "%", + Value = CalculateRate( + baselineStatisticsItem.AllocatedMemory?.Value ?? 0, + nonBaselineStatisticsItem.AllocatedMemory?.Value ?? 0), + }, + BaselineAllocatedMemory = baselineStatisticsItem.AllocatedMemory, + BaselineMeanTimeTaken = baselineStatisticsItem.MeanTimeTaken, + BaselineParameters = baselineStatisticsItem.Parameters, + CompareAllocatedMemory = nonBaselineStatisticsItem.AllocatedMemory, + CompareMeanTimeTaken = nonBaselineStatisticsItem.MeanTimeTaken, + CompareParameters = nonBaselineStatisticsItem.Parameters, + Key = baselineStatisticsItem.Key, + MeanTimeTakenCompareRate = new BenchmarkStatisticsValue + { + Format = "0.##", + Unit = "%", + Value = CalculateRate( + baselineStatisticsItem.MeanTimeTaken?.Value ?? 0, + nonBaselineStatisticsItem.MeanTimeTaken?.Value ?? 0), + } + }; + + private static BenchmarkStatisticsItem CreateBenchmarkStatisticsItem(SummaryTable.SummaryTableColumn baselineClassifierColumn, ref int current, BenchmarkDotNet.Reports.BenchmarkReport benchmarkReport) + { + var allocatedMemoryMetric = benchmarkReport.Metrics.First(m => m.Key == "Allocated Memory"); + var branchInstructionsMetric = benchmarkReport.Metrics.First(m => m.Key == "BranchInstructions"); + var branchMispredictionsMetric = benchmarkReport.Metrics.First(m => m.Key == "BranchMispredictions"); + var gen0CollectsMetric = benchmarkReport.Metrics.First(m => m.Key == "Gen0Collects"); + + var statisticsItem = new BenchmarkStatisticsItem + { + AllocatedMemory = new BenchmarkStatisticsValue + { + Format = allocatedMemoryMetric.Value.Descriptor.NumberFormat, + Unit = allocatedMemoryMetric.Value.Descriptor.Unit, + Value = Convert.ToDecimal(allocatedMemoryMetric.Value.Value), + }, + Baseline = baselineClassifierColumn.Content[current++], + BranchInstructionsPerOp = new BenchmarkStatisticsValue + { + Format = branchInstructionsMetric.Value.Descriptor.NumberFormat, + Unit = branchInstructionsMetric.Value.Descriptor.Unit, + Value = Convert.ToDecimal(branchInstructionsMetric.Value.Value), + }, + BranchMispredictionsPerOp = new BenchmarkStatisticsValue + { + Format = branchMispredictionsMetric.Value.Descriptor.NumberFormat, + Unit = branchMispredictionsMetric.Value.Descriptor.Unit, + Value = Convert.ToDecimal(branchMispredictionsMetric.Value.Value) + }, + Gen0Collects = new BenchmarkStatisticsValue + { + Format = gen0CollectsMetric.Value.Descriptor.NumberFormat, + Unit = gen0CollectsMetric.Value.Descriptor.Unit, + Value = Convert.ToDecimal(gen0CollectsMetric.Value.Value) + }, + Key = $"{benchmarkReport.BenchmarkCase.Descriptor.Type.Name}.{benchmarkReport.BenchmarkCase.Descriptor.WorkloadMethod.Name}", + MeanTimeTaken = new BenchmarkStatisticsValue + { + Format = "N0", + Unit = "ns", + Value = Convert.ToDecimal(benchmarkReport.ResultStatistics?.Mean ?? 0), + }, + Parameters = benchmarkReport.BenchmarkCase.Parameters.DisplayInfo, + StandardError = new BenchmarkStatisticsValue + { + Format = "N0", + Unit = "ns", + Value = Convert.ToDecimal(benchmarkReport.ResultStatistics?.StandardError ?? 0), + }, + }; + return statisticsItem; + } + + private static Environment CreateEnvironment(Summary summary) => new Environment + { + Architecture = summary.HostEnvironmentInfo.Architecture, + BenchmarkDotNetCaption = "BenchmarkDotNet", + BenchmarkDotNetVersion = summary.HostEnvironmentInfo.BenchmarkDotNetVersion, + BuildConfiguration = summary.HostEnvironmentInfo.Configuration, + DotNetCliVersion = summary.HostEnvironmentInfo.DotNetSdkVersion.Value, + DotNetRuntimeVersion = summary.HostEnvironmentInfo.RuntimeVersion, + LogicalCoreCount = summary.HostEnvironmentInfo.CpuInfo.Value.LogicalCoreCount.GetValueOrDefault(0), + PhysicalCoreCount = summary.HostEnvironmentInfo.CpuInfo.Value.PhysicalCoreCount.GetValueOrDefault(0), + ProcessorName = summary.HostEnvironmentInfo.CpuInfo.Value.ProcessorName, + }; + + private static BenchmarkReport CreateReport(Summary summary) + { + var report = new BenchmarkReport + { + Date = DateTime.UtcNow, + Environment = CreateEnvironment(summary), + Title = summary.Title, + }; + var statistics = new List(summary.Reports.Length); + + var baselineClassifierColumn = summary.Table.Columns.First(c => c.OriginalColumn.ColumnName == "Baseline"); + var current = 0; + foreach (var benchmarkReport in summary.Reports) + { + var statisticsItem = CreateBenchmarkStatisticsItem(baselineClassifierColumn, ref current, benchmarkReport); + + statistics.Add(statisticsItem); + } + report.Statistics = statistics; + + var baselineStatisticsItems = statistics.Where(i => string.Equals(i.Baseline, "Yes")); + var nonBaselineStatisticsItems = statistics.Where(i => string.Equals(i.Baseline, "No")); + var statisticsComparison = new List(); + + foreach (var baselineStatisticsItem in baselineStatisticsItems) + { + foreach (var nonBaselineStatisticsItem in nonBaselineStatisticsItems.Where(i => string.Equals(i.Key, baselineStatisticsItem.Key))) + { + var statisticsComparisonItem = CreateBenchmarkStatisticsComparisonItem(baselineStatisticsItem, nonBaselineStatisticsItem); + + statisticsComparison.Add(statisticsComparisonItem); + } + } + report.StatisticsComparison = statisticsComparison; + + return report; + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Exporters/Json/Environment.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Json/Environment.cs new file mode 100644 index 00000000..664edd42 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Json/Environment.cs @@ -0,0 +1,23 @@ +namespace Rules.Framework.BenchmarkTests.Exporters.Json +{ + internal class Environment + { + public string Architecture { get; set; } = string.Empty; + + public string BenchmarkDotNetCaption { get; set; } = string.Empty; + + public string BenchmarkDotNetVersion { get; set; } = string.Empty; + + public string BuildConfiguration { get; set; } = string.Empty; + + public string DotNetCliVersion { get; set; } = string.Empty; + + public string DotNetRuntimeVersion { get; set; } = string.Empty; + + public int LogicalCoreCount { get; set; } + + public int PhysicalCoreCount { get; set; } + + public string ProcessorName { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Program.cs b/tests/Rules.Framework.BenchmarkTests/Program.cs index 77422cdf..a6d8cd9b 100644 --- a/tests/Rules.Framework.BenchmarkTests/Program.cs +++ b/tests/Rules.Framework.BenchmarkTests/Program.cs @@ -6,6 +6,7 @@ using BenchmarkDotNet.Running; using McMaster.Extensions.CommandLineUtils; using Rules.Framework.BenchmarkTests; +using Rules.Framework.BenchmarkTests.Exporters.Json; [assembly: SimpleJob(RuntimeMoniker.Net60)] @@ -31,7 +32,7 @@ private static int Main(string[] args) manualConfig.AddDiagnoser(MemoryDiagnoser.Default); manualConfig.AddHardwareCounters(HardwareCounter.BranchInstructions, HardwareCounter.BranchMispredictions); manualConfig.AddExporter(HtmlExporter.Default); - manualConfig.AddExporter(CustomJsonExporter.Full); + manualConfig.AddExporter(CustomJsonExporter.Indented); manualConfig.WithOption(ConfigOptions.JoinSummary, true); var artifactsPath = artifactsPathOption.Value(); diff --git a/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt b/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt index a8e9fcaf..b53ade06 100644 --- a/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt +++ b/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt @@ -9,14 +9,11 @@ using var streamReader = File.OpenText(ResultsFile); var rawJsonText = streamReader.ReadToEnd(); dynamic results = JsonConvert.DeserializeObject(rawJsonText); - dynamic hostEnvInfo = results.HostEnvironmentInfo; - - dynamic baselines = ((IEnumerable)results.Benchmarks).Where(x => x.Baseline == "Yes"); - dynamic nonBaselines = ((IEnumerable)results.Benchmarks).Where(x => x.Baseline == "No"); + dynamic hostEnvInfo = results.Environment; #> # Benchmark Results Report -Date & Time: <#= DateTime.UtcNow.ToString("yyyy-MM-dd hh:mm:ssZ") #> +Date & Time: <#= results.Date.ToString("yyyy-MM-dd HH:mm:ss") #> ## Environment @@ -24,54 +21,46 @@ Date & Time: <#= DateTime.UtcNow.ToString("yyyy-MM-dd hh:mm:ssZ") #> > >Processor=<#= hostEnvInfo.ProcessorName #>, <#= hostEnvInfo.PhysicalCoreCount #> physical cores, <#= hostEnvInfo.LogicalCoreCount #> logical cores > ->Architecture=<#= hostEnvInfo.Architecture #>, Runtime=<#= hostEnvInfo.RuntimeVersion #>, Configuration=<#= hostEnvInfo.Configuration #> +>Architecture=<#= hostEnvInfo.Architecture #>, Runtime=<#= hostEnvInfo.DotNetRuntimeVersion #>, Configuration=<#= hostEnvInfo.BuildConfiguration #> > >.NET CLI Version=<#= hostEnvInfo.DotNetCliVersion #> ## Statistics -| Name | Parameters | Mean | Std Error | Branch Instructions/Op | Branch Mispredictions/Op | GC Gen0 | Allocated Memory | -| ---- | ---------- | ---- | --------- | ---------------------- | ------------------------ | ------- | ---------------- | -<# foreach (dynamic benchmark in results.Benchmarks) +| Name | Parameters | Mean Time Taken | Std Error | Branch
Instructions/Op | Branch
Mispredictions/Op | GC Gen0 | Allocated Memory | +| ---- | ---------- | --------------- | --------- | -------------------------- | ---------------------------- | ------- | ---------------- | +<# foreach (dynamic statisticsItem in results.Statistics) { - var name = $"{benchmark.Type}.{benchmark.Method}"; - var parameters = benchmark.Parameters; - var mean = Math.Round(Convert.ToDecimal(benchmark.Statistics.Mean), 2); - var stdError = Math.Round(Convert.ToDecimal(benchmark.Statistics.StandardError), 2); - var metrics = benchmark.Metrics as IEnumerable; - var branchInstructions = Math.Round(Convert.ToDecimal(metrics.FirstOrDefault(x => x.Descriptor.Id == "BranchInstructions")?.Value ?? 0), 0); - var branchMispredictions = Math.Round(Convert.ToDecimal(metrics.FirstOrDefault(x => x.Descriptor.Id == "BranchMispredictions")?.Value ?? 0), 0); - var gen0 = Math.Round(Convert.ToDecimal(metrics.FirstOrDefault(x => x.Descriptor.Id == "Gen0Collects")?.Value ?? 0), 0); - var allocatedMemory = Math.Round(Convert.ToDecimal(metrics.FirstOrDefault(x => x.Descriptor.Id == "Allocated Memory")?.Value ?? 0), 2); + var name = statisticsItem.Key; + var parameters = statisticsItem.Parameters; + var mean = $"{statisticsItem.MeanTimeTaken.Value.ToString((string)statisticsItem.MeanTimeTaken.Format)} {statisticsItem.MeanTimeTaken.Unit}"; + var stdError = $"{statisticsItem.StandardError.Value.ToString((string)statisticsItem.StandardError.Format)} {statisticsItem.StandardError.Unit}"; + var branchInstructionsPerOp = statisticsItem.BranchInstructionsPerOp.Value.ToString((string)statisticsItem.BranchInstructionsPerOp.Format); + var branchMispredictionsPerOp = statisticsItem.BranchMispredictionsPerOp.Value.ToString((string)statisticsItem.BranchMispredictionsPerOp.Format); + var gen0Collects = statisticsItem.Gen0Collects.Value.ToString((string)statisticsItem.Gen0Collects.Format); + var allocatedMemory = $"{statisticsItem.AllocatedMemory.Value.ToString((string)statisticsItem.AllocatedMemory.Format)} {statisticsItem.AllocatedMemory.Unit}"; #> -| <#= name #> | <#= parameters #> | <#= mean #> | <#= stdError #> | <#= branchInstructions #> | <#= branchMispredictions #> | <#= gen0 #> | <#= allocatedMemory #> | +| <#= name #> | <#= parameters #> | <#= mean #> | <#= stdError #> | <#= branchInstructionsPerOp #> | <#= branchMispredictionsPerOp #> | <#= gen0Collects #> | <#= allocatedMemory #> | <#} #> -## Baseline Comparison +## Statistics Comparison -| Name | Baseline | Compare To | Mean [Baseline] | Mean [Compare To] | Mean [Compare %] | Allocated Memory [Baseline] | Allocated Memory [Compare To] | Allocated Memory [Compare %] | -| ---- | -------- | ---------- | --------------- | ----------------- | ---------------- | --------------------------- | ----------------------------- | ---------------------------- | +| Name | Baseline | Compare | Mean Time Taken
[Baseline] | Mean Time Taken
[Compare] | Mean Time Taken
[Comparison %] | Allocated Memory
[Baseline] | Allocated Memory
[Compare] | Allocated Memory
[Comparison %] | +| ---- | -------- | ------- | ------------------------------ | ----------------------------- | ---------------------------------- | ------------------------------- | ------------------------------ | ----------------------------------- | <# -foreach (dynamic baseline in baselines) +foreach (dynamic statisticsComparisonItem in results.StatisticsComparison) { - var key = $"{baseline.Type}.{baseline.Method}"; - var nonBaselinesGroup = ((IEnumerable)nonBaselines).Where(x => $"{x.Type}.{x.Method}" == key); - foreach (dynamic nonBaseline in nonBaselinesGroup) - { - var name = key; - var baselineParameters = baseline.Parameters; - var compareToParameters = nonBaseline.Parameters; - var baselineMean = Math.Round(Convert.ToDecimal(baseline.Statistics.Mean), 2); - var compareToMean = Math.Round(Convert.ToDecimal(nonBaseline.Statistics.Mean), 2); - var meanCompareRate = Math.Round(Convert.ToDecimal((baselineMean - compareToMean) / baselineMean * 100), 2); - var baselineMetrics = baseline.Metrics as IEnumerable; - var compareToMetrics = nonBaseline.Metrics as IEnumerable; - var baselineAllocatedMemory = Math.Round(Convert.ToDecimal(baselineMetrics.FirstOrDefault(x => x.Descriptor.Id == "Allocated Memory")?.Value ?? 0), 2); - var compareToAllocatedMemory = Math.Round(Convert.ToDecimal(compareToMetrics.FirstOrDefault(x => x.Descriptor.Id == "Allocated Memory")?.Value ?? 0), 2); - var allocatedMemoryCompareRate = Math.Round(Convert.ToDecimal((baselineAllocatedMemory - compareToAllocatedMemory) / baselineAllocatedMemory * 100), 2); + var name = statisticsComparisonItem.Key; + var baselineParameters = statisticsComparisonItem.BaselineParameters; + var compareToParameters = statisticsComparisonItem.CompareParameters; + var baselineMean = $"{statisticsComparisonItem.BaselineMeanTimeTaken.Value.ToString((string)statisticsComparisonItem.BaselineMeanTimeTaken.Format)} {statisticsComparisonItem.BaselineMeanTimeTaken.Unit}"; + var compareToMean = $"{statisticsComparisonItem.CompareMeanTimeTaken.Value.ToString((string)statisticsComparisonItem.CompareMeanTimeTaken.Format)} {statisticsComparisonItem.CompareMeanTimeTaken.Unit}"; + var meanCompareRate = $"{statisticsComparisonItem.MeanTimeTakenCompareRate.Value.ToString((string)statisticsComparisonItem.MeanTimeTakenCompareRate.Format)} {statisticsComparisonItem.MeanTimeTakenCompareRate.Unit}"; + var baselineAllocatedMemory = $"{statisticsComparisonItem.BaselineAllocatedMemory.Value.ToString((string)statisticsComparisonItem.BaselineAllocatedMemory.Format)} {statisticsComparisonItem.BaselineAllocatedMemory.Unit}"; + var compareToAllocatedMemory = $"{statisticsComparisonItem.CompareAllocatedMemory.Value.ToString((string)statisticsComparisonItem.CompareAllocatedMemory.Format)} {statisticsComparisonItem.CompareAllocatedMemory.Unit}"; + var allocatedMemoryCompareRate = $"{statisticsComparisonItem.AllocatedMemoryRate.Value.ToString((string)statisticsComparisonItem.AllocatedMemoryRate.Format)} {statisticsComparisonItem.AllocatedMemoryRate.Unit}"; #> -| <#= name #> | <#= baselineParameters #> | <#= compareToParameters #> | <#= baselineMean #> | <#= compareToMean #> | <#= meanCompareRate #> % | <#= baselineAllocatedMemory #> | <#= compareToAllocatedMemory #> | <#= allocatedMemoryCompareRate #> % | +| <#= name #> | <#= baselineParameters #> | <#= compareToParameters #> | <#= baselineMean #> | <#= compareToMean #> | <#= meanCompareRate #> | <#= baselineAllocatedMemory #> | <#= compareToAllocatedMemory #> | <#= allocatedMemoryCompareRate #> | <# - } } #> \ No newline at end of file From 280d04a6e259f89734f904380675043652b028ab Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Thu, 9 Mar 2023 22:12:35 +0000 Subject: [PATCH 26/54] chore: add new job to run benchmarks and publish results to github pr comment --- .github/workflows/dotnet-build.yml | 60 ++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index ce04491f..c3d3d017 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -185,3 +185,63 @@ jobs: CUSTOM_TAG: ${{ env.BUILD_VERSION }} TAG_CONTEXT: branch RELEASE_BRANCHES: .* + + benchmarks: + runs-on: ubuntu-latest + steps: + - name: Setup Grab tool + run: dotnet tool install dotnet-grab --global --ignore-failed-sources + + - name: Setup T4 tool + run: dotnet tool install dotnet-t4 --global --ignore-failed-sources + + - name: Determine folders + id: determine-folders + run: | + HEAD_FOLDER=$(pwd) + HEAD_BENCH_FOLDER=$HEAD_FOLDER/tmp/benchmarks/head + MASTER_BENCH_FOLDER=$HEAD_FOLDER/tmp/benchmarks/master + BENCH_FOLDER=$HEAD_FOLDER/tmp/benchmarks + + echo "::set-output name=head-folder::$HEAD_FOLDER" + echo "::set-output name=head-benchmarks-folder::$HEAD_BENCH_FOLDER" + echo "::set-output name=master-benchmarks-folder::$MASTER_BENCH_FOLDER" + echo "::set-output name=benchmarks-folder::$BENCH_FOLDER" + + - name: Create benchmarks results folder (HEAD) + run: mkdir -p ${{ steps.determine-folders.output.head-benchmarks-folder }} + + - name: Restore dependencies (HEAD) + run: dotnet restore rules-framework.sln + + - name: Build benchmarks (HEAD) + run: dotnet build -c Release tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -o "${{ steps.determine-folders.output.head-benchmarks-folder }}/bin" --framework net6.0 + + - name: Change working directory (HEAD) + run: cd ${{ steps.determine-folders.output.head-benchmarks-folder }} + + - name: Run benchmarks (HEAD) + run: | + chmod +x bin/Rules.Framework.BenchmarkTests + bin/Rules.Framework.BenchmarkTests -a artifacts + + - name: Determine results file (HEAD) + id: determine-results-file + run: | + JSON_FILE=$(find artifacts/results -name '*.json') + echo "::set-output name=file::${{ steps.determine-folders.output.head-benchmarks-folder }}/$JSON_FILE" + + - name: Change working directory (benchmarks) + run: cd ${{ steps.determine-folders.output.benchmarks-folder }} + + - name: Get report dependencies + run: grab newtonsoft.json@13.0.2 + + - name: Generate report + run: t4 -p:ResultsFile="${{ steps.determine-results-file.outputs.file }}" -P="packages/newtonsoft.json/13.0.2/lib/net6.0" -r="Newtonsoft.Json.dll" -o "report.md" "${{ steps.determine-folders.output.head-folder }}/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt" + + - name: Publish report results + uses: peter-evans/create-or-update-comment@v2 + with: + issue-number: ${{ github.event.issue.number }} + body-file: report.md \ No newline at end of file From 3e87b392aa7ed505caa796a76a23b56c9b9d2ce3 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Thu, 9 Mar 2023 23:42:33 +0000 Subject: [PATCH 27/54] fix: change outputs notation --- .github/workflows/dotnet-build.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index c3d3d017..c1df5b52 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -203,22 +203,22 @@ jobs: MASTER_BENCH_FOLDER=$HEAD_FOLDER/tmp/benchmarks/master BENCH_FOLDER=$HEAD_FOLDER/tmp/benchmarks - echo "::set-output name=head-folder::$HEAD_FOLDER" - echo "::set-output name=head-benchmarks-folder::$HEAD_BENCH_FOLDER" - echo "::set-output name=master-benchmarks-folder::$MASTER_BENCH_FOLDER" - echo "::set-output name=benchmarks-folder::$BENCH_FOLDER" + echo "head_folder=$HEAD_FOLDER" >> $GITHUB_OUTPUT + echo "head_benchmarks_folder=$HEAD_BENCH_FOLDER" >> $GITHUB_OUTPUT + echo "master_benchmarks_folder=$MASTER_BENCH_FOLDER" >> $GITHUB_OUTPUT + echo "benchmarks_folder::$BENCH_FOLDER" >> $GITHUB_OUTPUT - name: Create benchmarks results folder (HEAD) - run: mkdir -p ${{ steps.determine-folders.output.head-benchmarks-folder }} + run: mkdir -p ${{ steps.determine-folders.output.head_benchmarks_folder }} - name: Restore dependencies (HEAD) run: dotnet restore rules-framework.sln - name: Build benchmarks (HEAD) - run: dotnet build -c Release tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -o "${{ steps.determine-folders.output.head-benchmarks-folder }}/bin" --framework net6.0 + run: dotnet build -c Release tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -o "${{ steps.determine-folders.output.head_benchmarks_folder }}/bin" --framework net6.0 - name: Change working directory (HEAD) - run: cd ${{ steps.determine-folders.output.head-benchmarks-folder }} + run: cd ${{ steps.determine-folders.output.head_benchmarks_folder }} - name: Run benchmarks (HEAD) run: | @@ -229,16 +229,16 @@ jobs: id: determine-results-file run: | JSON_FILE=$(find artifacts/results -name '*.json') - echo "::set-output name=file::${{ steps.determine-folders.output.head-benchmarks-folder }}/$JSON_FILE" + echo "file=${{ steps.determine-folders.output.head_benchmarks_folder }}/$JSON_FILE" >> $GITHUB_OUTPUT - name: Change working directory (benchmarks) - run: cd ${{ steps.determine-folders.output.benchmarks-folder }} + run: cd ${{ steps.determine-folders.output.benchmarks_folder }} - name: Get report dependencies run: grab newtonsoft.json@13.0.2 - name: Generate report - run: t4 -p:ResultsFile="${{ steps.determine-results-file.outputs.file }}" -P="packages/newtonsoft.json/13.0.2/lib/net6.0" -r="Newtonsoft.Json.dll" -o "report.md" "${{ steps.determine-folders.output.head-folder }}/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt" + run: t4 -p:ResultsFile="${{ steps.determine-results-file.outputs.file }}" -P="packages/newtonsoft.json/13.0.2/lib/net6.0" -r="Newtonsoft.Json.dll" -o "report.md" "${{ steps.determine-folders.output.head_folder }}/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt" - name: Publish report results uses: peter-evans/create-or-update-comment@v2 From c6fa682b5698b4c6249a71448f88ddd98f80b0d5 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Thu, 9 Mar 2023 23:44:30 +0000 Subject: [PATCH 28/54] fix: resolve notation issue --- .github/workflows/dotnet-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index c1df5b52..f54aa646 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -206,7 +206,7 @@ jobs: echo "head_folder=$HEAD_FOLDER" >> $GITHUB_OUTPUT echo "head_benchmarks_folder=$HEAD_BENCH_FOLDER" >> $GITHUB_OUTPUT echo "master_benchmarks_folder=$MASTER_BENCH_FOLDER" >> $GITHUB_OUTPUT - echo "benchmarks_folder::$BENCH_FOLDER" >> $GITHUB_OUTPUT + echo "benchmarks_folder=$BENCH_FOLDER" >> $GITHUB_OUTPUT - name: Create benchmarks results folder (HEAD) run: mkdir -p ${{ steps.determine-folders.output.head_benchmarks_folder }} From 78e195fa089ed66cb2ccb9dea1372736fccc68ca Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Thu, 9 Mar 2023 23:49:28 +0000 Subject: [PATCH 29/54] fix: resolve mispelled outputs --- .github/workflows/dotnet-build.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index f54aa646..43f9aa45 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -209,16 +209,16 @@ jobs: echo "benchmarks_folder=$BENCH_FOLDER" >> $GITHUB_OUTPUT - name: Create benchmarks results folder (HEAD) - run: mkdir -p ${{ steps.determine-folders.output.head_benchmarks_folder }} + run: mkdir -p ${{ steps.determine-folders.outputs.head_benchmarks_folder }} - name: Restore dependencies (HEAD) run: dotnet restore rules-framework.sln - name: Build benchmarks (HEAD) - run: dotnet build -c Release tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -o "${{ steps.determine-folders.output.head_benchmarks_folder }}/bin" --framework net6.0 + run: dotnet build -c Release tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -o "${{ steps.determine-folders.outputs.head_benchmarks_folder }}/bin" --framework net6.0 - name: Change working directory (HEAD) - run: cd ${{ steps.determine-folders.output.head_benchmarks_folder }} + run: cd ${{ steps.determine-folders.outputs.head_benchmarks_folder }} - name: Run benchmarks (HEAD) run: | @@ -229,16 +229,16 @@ jobs: id: determine-results-file run: | JSON_FILE=$(find artifacts/results -name '*.json') - echo "file=${{ steps.determine-folders.output.head_benchmarks_folder }}/$JSON_FILE" >> $GITHUB_OUTPUT + echo "file=${{ steps.determine-folders.outputs.head_benchmarks_folder }}/$JSON_FILE" >> $GITHUB_OUTPUT - name: Change working directory (benchmarks) - run: cd ${{ steps.determine-folders.output.benchmarks_folder }} + run: cd ${{ steps.determine-folders.outputs.benchmarks_folder }} - name: Get report dependencies run: grab newtonsoft.json@13.0.2 - name: Generate report - run: t4 -p:ResultsFile="${{ steps.determine-results-file.outputs.file }}" -P="packages/newtonsoft.json/13.0.2/lib/net6.0" -r="Newtonsoft.Json.dll" -o "report.md" "${{ steps.determine-folders.output.head_folder }}/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt" + run: t4 -p:ResultsFile="${{ steps.determine-results-file.outputs.file }}" -P="packages/newtonsoft.json/13.0.2/lib/net6.0" -r="Newtonsoft.Json.dll" -o "report.md" "${{ steps.determine-folders.outputs.head_folder }}/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt" - name: Publish report results uses: peter-evans/create-or-update-comment@v2 From d32c3f50abfdac1faa658eed0cc9c12a726594e5 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Thu, 9 Mar 2023 23:55:04 +0000 Subject: [PATCH 30/54] fix: add missing git checkout action --- .github/workflows/dotnet-build.yml | 118 +++++++++++++++-------------- 1 file changed, 62 insertions(+), 56 deletions(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index 43f9aa45..573627b6 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -189,59 +189,65 @@ jobs: benchmarks: runs-on: ubuntu-latest steps: - - name: Setup Grab tool - run: dotnet tool install dotnet-grab --global --ignore-failed-sources - - - name: Setup T4 tool - run: dotnet tool install dotnet-t4 --global --ignore-failed-sources - - - name: Determine folders - id: determine-folders - run: | - HEAD_FOLDER=$(pwd) - HEAD_BENCH_FOLDER=$HEAD_FOLDER/tmp/benchmarks/head - MASTER_BENCH_FOLDER=$HEAD_FOLDER/tmp/benchmarks/master - BENCH_FOLDER=$HEAD_FOLDER/tmp/benchmarks - - echo "head_folder=$HEAD_FOLDER" >> $GITHUB_OUTPUT - echo "head_benchmarks_folder=$HEAD_BENCH_FOLDER" >> $GITHUB_OUTPUT - echo "master_benchmarks_folder=$MASTER_BENCH_FOLDER" >> $GITHUB_OUTPUT - echo "benchmarks_folder=$BENCH_FOLDER" >> $GITHUB_OUTPUT - - - name: Create benchmarks results folder (HEAD) - run: mkdir -p ${{ steps.determine-folders.outputs.head_benchmarks_folder }} - - - name: Restore dependencies (HEAD) - run: dotnet restore rules-framework.sln - - - name: Build benchmarks (HEAD) - run: dotnet build -c Release tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -o "${{ steps.determine-folders.outputs.head_benchmarks_folder }}/bin" --framework net6.0 - - - name: Change working directory (HEAD) - run: cd ${{ steps.determine-folders.outputs.head_benchmarks_folder }} - - - name: Run benchmarks (HEAD) - run: | - chmod +x bin/Rules.Framework.BenchmarkTests - bin/Rules.Framework.BenchmarkTests -a artifacts - - - name: Determine results file (HEAD) - id: determine-results-file - run: | - JSON_FILE=$(find artifacts/results -name '*.json') - echo "file=${{ steps.determine-folders.outputs.head_benchmarks_folder }}/$JSON_FILE" >> $GITHUB_OUTPUT - - - name: Change working directory (benchmarks) - run: cd ${{ steps.determine-folders.outputs.benchmarks_folder }} - - - name: Get report dependencies - run: grab newtonsoft.json@13.0.2 - - - name: Generate report - run: t4 -p:ResultsFile="${{ steps.determine-results-file.outputs.file }}" -P="packages/newtonsoft.json/13.0.2/lib/net6.0" -r="Newtonsoft.Json.dll" -o "report.md" "${{ steps.determine-folders.outputs.head_folder }}/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt" - - - name: Publish report results - uses: peter-evans/create-or-update-comment@v2 - with: - issue-number: ${{ github.event.issue.number }} - body-file: report.md \ No newline at end of file + - name: Setup Grab tool + run: dotnet tool install dotnet-grab --global --ignore-failed-sources + + - name: Setup T4 tool + run: dotnet tool install dotnet-t4 --global --ignore-failed-sources + + - name: Determine folders + id: determine-folders + run: | + HEAD_FOLDER=$(pwd) + HEAD_BENCH_FOLDER=$HEAD_FOLDER/tmp/benchmarks/head + MASTER_BENCH_FOLDER=$HEAD_FOLDER/tmp/benchmarks/master + BENCH_FOLDER=$HEAD_FOLDER/tmp/benchmarks + + echo "head_folder=$HEAD_FOLDER" >> $GITHUB_OUTPUT + echo "head_benchmarks_folder=$HEAD_BENCH_FOLDER" >> $GITHUB_OUTPUT + echo "master_benchmarks_folder=$MASTER_BENCH_FOLDER" >> $GITHUB_OUTPUT + echo "benchmarks_folder=$BENCH_FOLDER" >> $GITHUB_OUTPUT + + - name: Git Checkout + uses: actions/checkout@v2 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - name: Create benchmarks results folder (HEAD) + run: mkdir -p ${{ steps.determine-folders.outputs.head_benchmarks_folder }} + + - name: Restore dependencies (HEAD) + run: dotnet restore rules-framework.sln + + - name: Build benchmarks (HEAD) + run: dotnet build -c Release tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -o "${{ steps.determine-folders.outputs.head_benchmarks_folder }}/bin" --framework net6.0 + + - name: Change working directory (HEAD) + run: cd ${{ steps.determine-folders.outputs.head_benchmarks_folder }} + + - name: Run benchmarks (HEAD) + run: | + chmod +x bin/Rules.Framework.BenchmarkTests + bin/Rules.Framework.BenchmarkTests -a artifacts + + - name: Determine results file (HEAD) + id: determine-results-file + run: | + JSON_FILE=$(find artifacts/results -name '*.json') + echo "file=${{ steps.determine-folders.outputs.head_benchmarks_folder }}/$JSON_FILE" >> $GITHUB_OUTPUT + + - name: Change working directory (benchmarks) + run: cd ${{ steps.determine-folders.outputs.benchmarks_folder }} + + - name: Get report dependencies + run: grab newtonsoft.json@13.0.2 + + - name: Generate report + run: t4 -p:ResultsFile="${{ steps.determine-results-file.outputs.file }}" -P="packages/newtonsoft.json/13.0.2/lib/net6.0" -r="Newtonsoft.Json.dll" -o "report.md" "${{ steps.determine-folders.outputs.head_folder }}/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt" + + - name: Publish report results + uses: peter-evans/create-or-update-comment@v2 + with: + issue-number: ${{ github.event.issue.number }} + body-file: report.md \ No newline at end of file From e4da36d2ef1c61dc5f4f8eaa03f53778c9e1e71a Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Fri, 10 Mar 2023 00:01:52 +0000 Subject: [PATCH 31/54] fix: use dotnet command to run benchmarks --- .github/workflows/dotnet-build.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index 573627b6..5049c87f 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -227,9 +227,7 @@ jobs: run: cd ${{ steps.determine-folders.outputs.head_benchmarks_folder }} - name: Run benchmarks (HEAD) - run: | - chmod +x bin/Rules.Framework.BenchmarkTests - bin/Rules.Framework.BenchmarkTests -a artifacts + run: dotnet bin/Rules.Framework.BenchmarkTests.dll -a artifacts - name: Determine results file (HEAD) id: determine-results-file From de984983fada696a910c55863322dc14a52e1b49 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Fri, 10 Mar 2023 00:13:10 +0000 Subject: [PATCH 32/54] fix: publish benchmarks and run binary --- .github/workflows/dotnet-build.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index 5049c87f..4e8f8e72 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -220,14 +220,16 @@ jobs: - name: Restore dependencies (HEAD) run: dotnet restore rules-framework.sln - - name: Build benchmarks (HEAD) - run: dotnet build -c Release tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -o "${{ steps.determine-folders.outputs.head_benchmarks_folder }}/bin" --framework net6.0 + - name: Build & Publish benchmarks (HEAD) + run: dotnet publish -c Release tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -o "${{ steps.determine-folders.outputs.head_benchmarks_folder }}/bin" --framework net6.0 - name: Change working directory (HEAD) run: cd ${{ steps.determine-folders.outputs.head_benchmarks_folder }} - name: Run benchmarks (HEAD) - run: dotnet bin/Rules.Framework.BenchmarkTests.dll -a artifacts + run: | + chmod +x ./bin/Rules.Framework.BenchmarkTests + ./bin/Rules.Framework.BenchmarkTests -a artifacts - name: Determine results file (HEAD) id: determine-results-file From c34d2d9906939e4f11bc16f8b59528d759145c21 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sat, 11 Mar 2023 22:43:46 +0000 Subject: [PATCH 33/54] fix: change to run benchmark with privileges --- .github/workflows/dotnet-build.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index 4e8f8e72..e7106c88 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -227,9 +227,7 @@ jobs: run: cd ${{ steps.determine-folders.outputs.head_benchmarks_folder }} - name: Run benchmarks (HEAD) - run: | - chmod +x ./bin/Rules.Framework.BenchmarkTests - ./bin/Rules.Framework.BenchmarkTests -a artifacts + run: sudo ./bin/Rules.Framework.BenchmarkTests -a artifacts - name: Determine results file (HEAD) id: determine-results-file From e57aa964d8fa5f4c9cf9af71d8b236113e199350 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sat, 11 Mar 2023 22:47:56 +0000 Subject: [PATCH 34/54] Merge branch 'master' into feat/49/allow_pre_compilation_of_rules_conditions_only_exact_match_modes --- .github/workflows/dotnet-build.yml | 18 -------- .github/workflows/dotnet-publish.yml | 3 -- rules-framework.sln | 7 ---- .../Rules.Framework.InMemory.Sample.csproj | 2 +- .../Rules.Framework.WebUI.Sample.csproj | 1 - .../AssemblyMetadata.cs | 5 --- .../GlobalSuppressions.cs | 8 ---- .../Rules.Framework.Providers.InMemory.csproj | 41 ------------------- .../Rules.Framework.Providers.MongoDb.csproj | 5 ++- .../Rules.Framework.WebUI.csproj | 2 +- src/Rules.Framework/AssemblyMetadata.cs | 2 + src/Rules.Framework/GlobalSuppressions.cs | 3 +- .../ComposedConditionNodeDataModel.cs | 0 .../DataModel/ConditionNodeDataModel.cs | 0 .../InMemory}/DataModel/RuleDataModel.cs | 0 .../DataModel/ValueConditionNodeDataModel.cs | 0 .../InMemory}/IInMemoryRulesStorage.cs | 0 .../Providers/InMemory}/IRuleFactory.cs | 0 .../InMemoryProviderRulesDataSource.cs | 0 ...oviderRulesDataSourceSelectorExtensions.cs | 0 .../InMemory}/InMemoryRulesStorage.cs | 0 .../Providers/InMemory}/RuleFactory.cs | 0 .../InMemory}/ServiceCollectionExtensions.cs | 0 src/Rules.Framework/Rules.Framework.csproj | 4 +- ...Providers.InMemory.IntegrationTests.csproj | 2 +- ....Framework.Providers.InMemory.Tests.csproj | 2 +- .../Rules.Framework.WebUI.Tests.csproj | 1 - 27 files changed, 15 insertions(+), 91 deletions(-) delete mode 100644 src/Rules.Framework.Providers.InMemory/AssemblyMetadata.cs delete mode 100644 src/Rules.Framework.Providers.InMemory/GlobalSuppressions.cs delete mode 100644 src/Rules.Framework.Providers.InMemory/Rules.Framework.Providers.InMemory.csproj rename src/{Rules.Framework.Providers.InMemory => Rules.Framework/Providers/InMemory}/DataModel/ComposedConditionNodeDataModel.cs (100%) rename src/{Rules.Framework.Providers.InMemory => Rules.Framework/Providers/InMemory}/DataModel/ConditionNodeDataModel.cs (100%) rename src/{Rules.Framework.Providers.InMemory => Rules.Framework/Providers/InMemory}/DataModel/RuleDataModel.cs (100%) rename src/{Rules.Framework.Providers.InMemory => Rules.Framework/Providers/InMemory}/DataModel/ValueConditionNodeDataModel.cs (100%) rename src/{Rules.Framework.Providers.InMemory => Rules.Framework/Providers/InMemory}/IInMemoryRulesStorage.cs (100%) rename src/{Rules.Framework.Providers.InMemory => Rules.Framework/Providers/InMemory}/IRuleFactory.cs (100%) rename src/{Rules.Framework.Providers.InMemory => Rules.Framework/Providers/InMemory}/InMemoryProviderRulesDataSource.cs (100%) rename src/{Rules.Framework.Providers.InMemory => Rules.Framework/Providers/InMemory}/InMemoryProviderRulesDataSourceSelectorExtensions.cs (100%) rename src/{Rules.Framework.Providers.InMemory => Rules.Framework/Providers/InMemory}/InMemoryRulesStorage.cs (100%) rename src/{Rules.Framework.Providers.InMemory => Rules.Framework/Providers/InMemory}/RuleFactory.cs (100%) rename src/{Rules.Framework.Providers.InMemory => Rules.Framework/Providers/InMemory}/ServiceCollectionExtensions.cs (100%) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index e7106c88..9bd06922 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -145,27 +145,9 @@ jobs: - name: Clean run: dotnet clean -c Release - - name: Replace Rules.Framework package references w/ project references - run: | - dotnet remove src/Rules.Framework.Providers.InMemory/Rules.Framework.Providers.InMemory.csproj package Rules.Framework - dotnet remove src/Rules.Framework.Providers.MongoDb/Rules.Framework.Providers.MongoDb.csproj package Rules.Framework - dotnet remove src/Rules.Framework.WebUI/Rules.Framework.WebUI.csproj package Rules.Framework - dotnet add src/Rules.Framework.Providers.InMemory/Rules.Framework.Providers.InMemory.csproj reference src/Rules.Framework/Rules.Framework.csproj - dotnet add src/Rules.Framework.Providers.MongoDb/Rules.Framework.Providers.MongoDb.csproj reference src/Rules.Framework/Rules.Framework.csproj - dotnet add src/Rules.Framework.WebUI/Rules.Framework.WebUI.csproj reference src/Rules.Framework/Rules.Framework.csproj - - name: Clear Nuget locals run: dotnet nuget locals all --clear - - name: Restore dependencies w/ Rules.Framework project reference - run: dotnet restore - - - name: Build w/ Rules.Framework project reference - run: dotnet build --no-restore -c Release - - - name: Test w/ Rules.Framework project reference (no coverage) - run: dotnet test --no-build --verbosity normal -m:1 -c Release - - name: Delete build tag if exists uses: dev-drprasad/delete-tag-and-release@v0.2.0 continue-on-error: true diff --git a/.github/workflows/dotnet-publish.yml b/.github/workflows/dotnet-publish.yml index 8a550089..fe9e6c04 100644 --- a/.github/workflows/dotnet-publish.yml +++ b/.github/workflows/dotnet-publish.yml @@ -42,9 +42,6 @@ jobs: - name: Pack Rules.Framework.Providers.MongoDb run: dotnet pack src/Rules.Framework.Providers.MongoDb/Rules.Framework.Providers.MongoDb.csproj --include-symbols -c Release /p:Version=$BUILD_VERSION - - name: Pack Rules.Framework.Providers.InMemory - run: dotnet pack src/Rules.Framework.Providers.InMemory/Rules.Framework.Providers.InMemory.csproj --include-symbols -c Release /p:Version=$BUILD_VERSION - - name: Pack Rules.Framework.WebUI run: dotnet pack src/Rules.Framework.WebUI/Rules.Framework.WebUI.csproj --include-symbols -c Release /p:Version=$BUILD_VERSION diff --git a/rules-framework.sln b/rules-framework.sln index e3057512..4f3df216 100644 --- a/rules-framework.sln +++ b/rules-framework.sln @@ -24,8 +24,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rules.Framework.Providers.M EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rules.Framework.Providers.MongoDb.Tests", "tests\Rules.Framework.Providers.MongoDb.Tests\Rules.Framework.Providers.MongoDb.Tests.csproj", "{C2957B7C-D601-4850-BCCE-D9AEF07225AB}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rules.Framework.Providers.InMemory", "src\Rules.Framework.Providers.InMemory\Rules.Framework.Providers.InMemory.csproj", "{D3A17A64-CC74-4EA0-95EA-9E141913BF70}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rules.Framework.IntegrationTests.Common", "tests\Rules.Framework.IntegrationTests.Common\Rules.Framework.IntegrationTests.Common.csproj", "{7C04ECEF-6504-453D-ABD8-FD3D5A2CF7F6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rules.Framework.Providers.InMemory.IntegrationTests", "tests\Rules.Framework.Providers.InMemory.IntegrationTests\Rules.Framework.Providers.InMemory.IntegrationTests.csproj", "{69BBA04D-116C-48EA-A8FD-ECC8CA0D57BE}" @@ -74,10 +72,6 @@ Global {C2957B7C-D601-4850-BCCE-D9AEF07225AB}.Debug|Any CPU.Build.0 = Debug|Any CPU {C2957B7C-D601-4850-BCCE-D9AEF07225AB}.Release|Any CPU.ActiveCfg = Release|Any CPU {C2957B7C-D601-4850-BCCE-D9AEF07225AB}.Release|Any CPU.Build.0 = Release|Any CPU - {D3A17A64-CC74-4EA0-95EA-9E141913BF70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D3A17A64-CC74-4EA0-95EA-9E141913BF70}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D3A17A64-CC74-4EA0-95EA-9E141913BF70}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D3A17A64-CC74-4EA0-95EA-9E141913BF70}.Release|Any CPU.Build.0 = Release|Any CPU {7C04ECEF-6504-453D-ABD8-FD3D5A2CF7F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7C04ECEF-6504-453D-ABD8-FD3D5A2CF7F6}.Debug|Any CPU.Build.0 = Debug|Any CPU {7C04ECEF-6504-453D-ABD8-FD3D5A2CF7F6}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -121,7 +115,6 @@ Global {460130DF-2360-494C-BB6F-BD87B033DF9D} = {AEE746EC-CEAA-4892-8C29-0CAAB97A23A8} {8E14BC18-583F-43EF-99CF-08932D6303A0} = {74E24C97-8EE4-4B69-AECD-4765FD2C751F} {C2957B7C-D601-4850-BCCE-D9AEF07225AB} = {74E24C97-8EE4-4B69-AECD-4765FD2C751F} - {D3A17A64-CC74-4EA0-95EA-9E141913BF70} = {AEE746EC-CEAA-4892-8C29-0CAAB97A23A8} {7C04ECEF-6504-453D-ABD8-FD3D5A2CF7F6} = {74E24C97-8EE4-4B69-AECD-4765FD2C751F} {69BBA04D-116C-48EA-A8FD-ECC8CA0D57BE} = {74E24C97-8EE4-4B69-AECD-4765FD2C751F} {BB319C5B-9BF5-4DDB-B522-CFC64D7272C4} = {74E24C97-8EE4-4B69-AECD-4765FD2C751F} diff --git a/samples/Rules.Framework.InMemory.Sample/Rules.Framework.InMemory.Sample.csproj b/samples/Rules.Framework.InMemory.Sample/Rules.Framework.InMemory.Sample.csproj index 06a2272c..48f467ee 100644 --- a/samples/Rules.Framework.InMemory.Sample/Rules.Framework.InMemory.Sample.csproj +++ b/samples/Rules.Framework.InMemory.Sample/Rules.Framework.InMemory.Sample.csproj @@ -6,7 +6,7 @@ - + diff --git a/samples/Rules.Framework.WebUI.Sample/Rules.Framework.WebUI.Sample.csproj b/samples/Rules.Framework.WebUI.Sample/Rules.Framework.WebUI.Sample.csproj index 2648646a..805f8ed4 100644 --- a/samples/Rules.Framework.WebUI.Sample/Rules.Framework.WebUI.Sample.csproj +++ b/samples/Rules.Framework.WebUI.Sample/Rules.Framework.WebUI.Sample.csproj @@ -15,7 +15,6 @@ - diff --git a/src/Rules.Framework.Providers.InMemory/AssemblyMetadata.cs b/src/Rules.Framework.Providers.InMemory/AssemblyMetadata.cs deleted file mode 100644 index 836f599e..00000000 --- a/src/Rules.Framework.Providers.InMemory/AssemblyMetadata.cs +++ /dev/null @@ -1,5 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] -[assembly: InternalsVisibleTo("Rules.Framework.Providers.InMemory.Tests")] -[assembly: InternalsVisibleTo("Rules.Framework.Providers.InMemory.IntegrationTests")] \ No newline at end of file diff --git a/src/Rules.Framework.Providers.InMemory/GlobalSuppressions.cs b/src/Rules.Framework.Providers.InMemory/GlobalSuppressions.cs deleted file mode 100644 index b974ecaa..00000000 --- a/src/Rules.Framework.Providers.InMemory/GlobalSuppressions.cs +++ /dev/null @@ -1,8 +0,0 @@ -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - -using System.Diagnostics.CodeAnalysis; - -[assembly: SuppressMessage("Major Code Smell", "S2326:Unused type parameters should be removed", Justification = "", Scope = "type", Target = "~T:Rules.Framework.Providers.InMemory.DataModel.ConditionNodeDataModel`1")] diff --git a/src/Rules.Framework.Providers.InMemory/Rules.Framework.Providers.InMemory.csproj b/src/Rules.Framework.Providers.InMemory/Rules.Framework.Providers.InMemory.csproj deleted file mode 100644 index cf21a898..00000000 --- a/src/Rules.Framework.Providers.InMemory/Rules.Framework.Providers.InMemory.csproj +++ /dev/null @@ -1,41 +0,0 @@ - - - - netstandard2.0 - 9.0 - true - - - - - - - LICENSE.md - - - Git - rules rulesframework memory inmemory - A data source provider implementation using InMemory for rules framework. - - - - - - - True - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - \ No newline at end of file diff --git a/src/Rules.Framework.Providers.MongoDb/Rules.Framework.Providers.MongoDb.csproj b/src/Rules.Framework.Providers.MongoDb/Rules.Framework.Providers.MongoDb.csproj index bf4b92a9..82179085 100644 --- a/src/Rules.Framework.Providers.MongoDb/Rules.Framework.Providers.MongoDb.csproj +++ b/src/Rules.Framework.Providers.MongoDb/Rules.Framework.Providers.MongoDb.csproj @@ -37,6 +37,9 @@ - + + + + \ No newline at end of file diff --git a/src/Rules.Framework.WebUI/Rules.Framework.WebUI.csproj b/src/Rules.Framework.WebUI/Rules.Framework.WebUI.csproj index 455024bc..151ced4e 100644 --- a/src/Rules.Framework.WebUI/Rules.Framework.WebUI.csproj +++ b/src/Rules.Framework.WebUI/Rules.Framework.WebUI.csproj @@ -47,7 +47,7 @@ - + diff --git a/src/Rules.Framework/AssemblyMetadata.cs b/src/Rules.Framework/AssemblyMetadata.cs index fc62b7b0..a88ba7ef 100644 --- a/src/Rules.Framework/AssemblyMetadata.cs +++ b/src/Rules.Framework/AssemblyMetadata.cs @@ -2,4 +2,6 @@ [assembly: InternalsVisibleTo("Rules.Framework.Tests")] [assembly: InternalsVisibleTo("Rules.Framework.IntegrationTests")] +[assembly: InternalsVisibleTo("Rules.Framework.Providers.InMemory.Tests")] +[assembly: InternalsVisibleTo("Rules.Framework.Providers.InMemory.IntegrationTests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] \ No newline at end of file diff --git a/src/Rules.Framework/GlobalSuppressions.cs b/src/Rules.Framework/GlobalSuppressions.cs index 88bf164e..40cfbe36 100644 --- a/src/Rules.Framework/GlobalSuppressions.cs +++ b/src/Rules.Framework/GlobalSuppressions.cs @@ -16,4 +16,5 @@ [assembly: SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "", Scope = "member", Target = "~M:Rules.Framework.RulesEngine`2.AddRuleAsync(Rules.Framework.Core.Rule{`0,`1})~System.Threading.Tasks.Task")] [assembly: SuppressMessage("Naming", "CA1717:Only FlagsAttribute enums should have plural names", Justification = "", Scope = "type", Target = "~T:Rules.Framework.PriorityOptions")] [assembly: SuppressMessage("Major Bug", "S2259:Null pointers should not be dereferenced", Justification = "That is validated somes lines above, and if it is null, a RuleOperationResult with error is returned right away.", Scope = "member", Target = "~M:Rules.Framework.RulesEngine`2.UpdateRuleInternalAsync(Rules.Framework.Core.Rule{`0,`1})~System.Threading.Tasks.Task{Rules.Framework.RuleOperationResult}")] -[assembly: SuppressMessage("Minor Code Smell", "S3963:\"static\" fields should be initialized inline", Justification = "To ensure initialization logic ordered as specified in class constructor.", Scope = "member", Target = "~M:Rules.Framework.Evaluation.OperatorsMetadata.#cctor")] \ No newline at end of file +[assembly: SuppressMessage("Minor Code Smell", "S3963:\"static\" fields should be initialized inline", Justification = "To ensure initialization logic ordered as specified in class constructor.", Scope = "member", Target = "~M:Rules.Framework.Evaluation.OperatorsMetadata.#cctor")] +[assembly: SuppressMessage("Major Code Smell", "S2326:Unused type parameters should be removed", Justification = "", Scope = "type", Target = "~T:Rules.Framework.Providers.InMemory.DataModel.ConditionNodeDataModel`1")] \ No newline at end of file diff --git a/src/Rules.Framework.Providers.InMemory/DataModel/ComposedConditionNodeDataModel.cs b/src/Rules.Framework/Providers/InMemory/DataModel/ComposedConditionNodeDataModel.cs similarity index 100% rename from src/Rules.Framework.Providers.InMemory/DataModel/ComposedConditionNodeDataModel.cs rename to src/Rules.Framework/Providers/InMemory/DataModel/ComposedConditionNodeDataModel.cs diff --git a/src/Rules.Framework.Providers.InMemory/DataModel/ConditionNodeDataModel.cs b/src/Rules.Framework/Providers/InMemory/DataModel/ConditionNodeDataModel.cs similarity index 100% rename from src/Rules.Framework.Providers.InMemory/DataModel/ConditionNodeDataModel.cs rename to src/Rules.Framework/Providers/InMemory/DataModel/ConditionNodeDataModel.cs diff --git a/src/Rules.Framework.Providers.InMemory/DataModel/RuleDataModel.cs b/src/Rules.Framework/Providers/InMemory/DataModel/RuleDataModel.cs similarity index 100% rename from src/Rules.Framework.Providers.InMemory/DataModel/RuleDataModel.cs rename to src/Rules.Framework/Providers/InMemory/DataModel/RuleDataModel.cs diff --git a/src/Rules.Framework.Providers.InMemory/DataModel/ValueConditionNodeDataModel.cs b/src/Rules.Framework/Providers/InMemory/DataModel/ValueConditionNodeDataModel.cs similarity index 100% rename from src/Rules.Framework.Providers.InMemory/DataModel/ValueConditionNodeDataModel.cs rename to src/Rules.Framework/Providers/InMemory/DataModel/ValueConditionNodeDataModel.cs diff --git a/src/Rules.Framework.Providers.InMemory/IInMemoryRulesStorage.cs b/src/Rules.Framework/Providers/InMemory/IInMemoryRulesStorage.cs similarity index 100% rename from src/Rules.Framework.Providers.InMemory/IInMemoryRulesStorage.cs rename to src/Rules.Framework/Providers/InMemory/IInMemoryRulesStorage.cs diff --git a/src/Rules.Framework.Providers.InMemory/IRuleFactory.cs b/src/Rules.Framework/Providers/InMemory/IRuleFactory.cs similarity index 100% rename from src/Rules.Framework.Providers.InMemory/IRuleFactory.cs rename to src/Rules.Framework/Providers/InMemory/IRuleFactory.cs diff --git a/src/Rules.Framework.Providers.InMemory/InMemoryProviderRulesDataSource.cs b/src/Rules.Framework/Providers/InMemory/InMemoryProviderRulesDataSource.cs similarity index 100% rename from src/Rules.Framework.Providers.InMemory/InMemoryProviderRulesDataSource.cs rename to src/Rules.Framework/Providers/InMemory/InMemoryProviderRulesDataSource.cs diff --git a/src/Rules.Framework.Providers.InMemory/InMemoryProviderRulesDataSourceSelectorExtensions.cs b/src/Rules.Framework/Providers/InMemory/InMemoryProviderRulesDataSourceSelectorExtensions.cs similarity index 100% rename from src/Rules.Framework.Providers.InMemory/InMemoryProviderRulesDataSourceSelectorExtensions.cs rename to src/Rules.Framework/Providers/InMemory/InMemoryProviderRulesDataSourceSelectorExtensions.cs diff --git a/src/Rules.Framework.Providers.InMemory/InMemoryRulesStorage.cs b/src/Rules.Framework/Providers/InMemory/InMemoryRulesStorage.cs similarity index 100% rename from src/Rules.Framework.Providers.InMemory/InMemoryRulesStorage.cs rename to src/Rules.Framework/Providers/InMemory/InMemoryRulesStorage.cs diff --git a/src/Rules.Framework.Providers.InMemory/RuleFactory.cs b/src/Rules.Framework/Providers/InMemory/RuleFactory.cs similarity index 100% rename from src/Rules.Framework.Providers.InMemory/RuleFactory.cs rename to src/Rules.Framework/Providers/InMemory/RuleFactory.cs diff --git a/src/Rules.Framework.Providers.InMemory/ServiceCollectionExtensions.cs b/src/Rules.Framework/Providers/InMemory/ServiceCollectionExtensions.cs similarity index 100% rename from src/Rules.Framework.Providers.InMemory/ServiceCollectionExtensions.cs rename to src/Rules.Framework/Providers/InMemory/ServiceCollectionExtensions.cs diff --git a/src/Rules.Framework/Rules.Framework.csproj b/src/Rules.Framework/Rules.Framework.csproj index 7e94eb79..6ab79db5 100644 --- a/src/Rules.Framework/Rules.Framework.csproj +++ b/src/Rules.Framework/Rules.Framework.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -30,6 +30,7 @@ + @@ -41,5 +42,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + \ No newline at end of file diff --git a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Rules.Framework.Providers.InMemory.IntegrationTests.csproj b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Rules.Framework.Providers.InMemory.IntegrationTests.csproj index a22c5067..be32545f 100644 --- a/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Rules.Framework.Providers.InMemory.IntegrationTests.csproj +++ b/tests/Rules.Framework.Providers.InMemory.IntegrationTests/Rules.Framework.Providers.InMemory.IntegrationTests.csproj @@ -22,7 +22,7 @@ - + diff --git a/tests/Rules.Framework.Providers.InMemory.Tests/Rules.Framework.Providers.InMemory.Tests.csproj b/tests/Rules.Framework.Providers.InMemory.Tests/Rules.Framework.Providers.InMemory.Tests.csproj index 4359e9c3..99d5ce9e 100644 --- a/tests/Rules.Framework.Providers.InMemory.Tests/Rules.Framework.Providers.InMemory.Tests.csproj +++ b/tests/Rules.Framework.Providers.InMemory.Tests/Rules.Framework.Providers.InMemory.Tests.csproj @@ -26,6 +26,6 @@ - + \ No newline at end of file diff --git a/tests/Rules.Framework.WebUI.Tests/Rules.Framework.WebUI.Tests.csproj b/tests/Rules.Framework.WebUI.Tests/Rules.Framework.WebUI.Tests.csproj index dd238a84..19306f4b 100644 --- a/tests/Rules.Framework.WebUI.Tests/Rules.Framework.WebUI.Tests.csproj +++ b/tests/Rules.Framework.WebUI.Tests/Rules.Framework.WebUI.Tests.csproj @@ -12,7 +12,6 @@ - all From 1bbc1fcf49f8c6ee597b65e96b88f3393321ecef Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 12 Mar 2023 14:03:57 +0000 Subject: [PATCH 35/54] chore: add list commands --- .github/workflows/dotnet-build.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index 9bd06922..e0beb6ac 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -206,7 +206,10 @@ jobs: run: dotnet publish -c Release tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -o "${{ steps.determine-folders.outputs.head_benchmarks_folder }}/bin" --framework net6.0 - name: Change working directory (HEAD) - run: cd ${{ steps.determine-folders.outputs.head_benchmarks_folder }} + run: | + cd ${{ steps.determine-folders.outputs.head_benchmarks_folder }} + ls -la + ls -la bin - name: Run benchmarks (HEAD) run: sudo ./bin/Rules.Framework.BenchmarkTests -a artifacts From eb2da919d76d25962fabc5fad04719c1105db272 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 12 Mar 2023 14:12:51 +0000 Subject: [PATCH 36/54] chore: run benchmarks with dotnet and dll --- .github/workflows/dotnet-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index e0beb6ac..aa08920e 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -212,7 +212,7 @@ jobs: ls -la bin - name: Run benchmarks (HEAD) - run: sudo ./bin/Rules.Framework.BenchmarkTests -a artifacts + run: sudo dotnet ./bin/Rules.Framework.BenchmarkTests.dll -a artifacts - name: Determine results file (HEAD) id: determine-results-file From 8afffcdd5ec0445fadfbd6149d6f736357711220 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 12 Mar 2023 14:22:30 +0000 Subject: [PATCH 37/54] chore: run benchmarks as self-contained and remove bin folder --- .github/workflows/dotnet-build.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index aa08920e..770007da 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -203,16 +203,15 @@ jobs: run: dotnet restore rules-framework.sln - name: Build & Publish benchmarks (HEAD) - run: dotnet publish -c Release tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -o "${{ steps.determine-folders.outputs.head_benchmarks_folder }}/bin" --framework net6.0 + run: dotnet publish -c Release tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -o "${{ steps.determine-folders.outputs.head_benchmarks_folder }}" --framework net6.0 -r linux-x64 - name: Change working directory (HEAD) run: | cd ${{ steps.determine-folders.outputs.head_benchmarks_folder }} ls -la - ls -la bin - name: Run benchmarks (HEAD) - run: sudo dotnet ./bin/Rules.Framework.BenchmarkTests.dll -a artifacts + run: sudo ./Rules.Framework.BenchmarkTests -a artifacts - name: Determine results file (HEAD) id: determine-results-file From 4d10a3ebd35f208cb24bf7a71fd8f3054457cd4b Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 12 Mar 2023 16:17:10 +0000 Subject: [PATCH 38/54] chore: run benchmarks as framework dependent --- .github/workflows/dotnet-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index 770007da..86de841f 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -203,7 +203,7 @@ jobs: run: dotnet restore rules-framework.sln - name: Build & Publish benchmarks (HEAD) - run: dotnet publish -c Release tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -o "${{ steps.determine-folders.outputs.head_benchmarks_folder }}" --framework net6.0 -r linux-x64 + run: dotnet publish -c Release tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -o "${{ steps.determine-folders.outputs.head_benchmarks_folder }}" --framework net6.0 --no-self-contained - name: Change working directory (HEAD) run: | @@ -211,7 +211,7 @@ jobs: ls -la - name: Run benchmarks (HEAD) - run: sudo ./Rules.Framework.BenchmarkTests -a artifacts + run: sudo dotnet Rules.Framework.BenchmarkTests.dll -a artifacts - name: Determine results file (HEAD) id: determine-results-file From b1c1f991e94fe9f2cecff6a27127751762136ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Garc=C3=AAs?= Date: Sun, 12 Mar 2023 16:25:46 +0000 Subject: [PATCH 39/54] chore: change build & run benchmarks to run from csproj --- .github/workflows/dotnet-build.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index 86de841f..facefcd4 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -202,17 +202,17 @@ jobs: - name: Restore dependencies (HEAD) run: dotnet restore rules-framework.sln - - name: Build & Publish benchmarks (HEAD) - run: dotnet publish -c Release tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -o "${{ steps.determine-folders.outputs.head_benchmarks_folder }}" --framework net6.0 --no-self-contained + - name: Build benchmarks (HEAD) + run: dotnet build -c Release tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj --framework net6.0 + + - name: Run benchmarks (HEAD) + run: sudo dotnet run tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -- -a "${{ steps.determine-folders.outputs.head_benchmarks_folder }}/artifacts" - name: Change working directory (HEAD) run: | cd ${{ steps.determine-folders.outputs.head_benchmarks_folder }} ls -la - - name: Run benchmarks (HEAD) - run: sudo dotnet Rules.Framework.BenchmarkTests.dll -a artifacts - - name: Determine results file (HEAD) id: determine-results-file run: | @@ -232,4 +232,4 @@ jobs: uses: peter-evans/create-or-update-comment@v2 with: issue-number: ${{ github.event.issue.number }} - body-file: report.md \ No newline at end of file + body-file: report.md From ac7ee230bb63298f8f74fad61b45b7c20421e757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Garc=C3=AAs?= Date: Sun, 12 Mar 2023 16:27:58 +0000 Subject: [PATCH 40/54] fix: add missing project switch --- .github/workflows/dotnet-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index facefcd4..b5a856e7 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -206,7 +206,7 @@ jobs: run: dotnet build -c Release tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj --framework net6.0 - name: Run benchmarks (HEAD) - run: sudo dotnet run tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -- -a "${{ steps.determine-folders.outputs.head_benchmarks_folder }}/artifacts" + run: sudo dotnet run --project tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -- -a "${{ steps.determine-folders.outputs.head_benchmarks_folder }}/artifacts" - name: Change working directory (HEAD) run: | From 4f099b475b18be19f5a86659bc23afb354ade2de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Garc=C3=AAs?= Date: Sun, 12 Mar 2023 16:31:28 +0000 Subject: [PATCH 41/54] fix: add framework to run command --- .github/workflows/dotnet-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index b5a856e7..957ecf35 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -206,7 +206,7 @@ jobs: run: dotnet build -c Release tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj --framework net6.0 - name: Run benchmarks (HEAD) - run: sudo dotnet run --project tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -- -a "${{ steps.determine-folders.outputs.head_benchmarks_folder }}/artifacts" + run: sudo dotnet run --project tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj --framework net6.0 -- -a "${{ steps.determine-folders.outputs.head_benchmarks_folder }}/artifacts" - name: Change working directory (HEAD) run: | From 2c9c0c6b53d4bfa6c405e94e99e32726a151d3d5 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 12 Mar 2023 20:16:58 +0000 Subject: [PATCH 42/54] feat: produce benchmarks report on markdown format --- .github/workflows/dotnet-build.yml | 19 +- run-benchmarks.ps1 | 18 +- .../Exporters/Json/CustomJsonExporter.cs | 45 ++-- .../Exporters/Markdown/BenchmarkReport.cs | 17 ++ .../BenchmarkStatisticsComparisonItem.cs | 23 ++ .../Markdown/BenchmarkStatisticsItem.cs | 23 ++ .../Markdown/BenchmarkStatisticsValue.cs | 11 + .../Markdown/CustomMarkdownExporter.cs | 231 ++++++++++++++++++ .../Exporters/Markdown/Environment.cs | 23 ++ .../Markdown/StringBuilderExtensions.cs | 18 ++ .../Rules.Framework.BenchmarkTests/Program.cs | 12 +- .../Results2Markdown/Report.tt | 31 ++- 12 files changed, 409 insertions(+), 62 deletions(-) create mode 100644 tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/BenchmarkReport.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/BenchmarkStatisticsComparisonItem.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/BenchmarkStatisticsItem.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/BenchmarkStatisticsValue.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/CustomMarkdownExporter.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/Environment.cs create mode 100644 tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/StringBuilderExtensions.cs diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index 86de841f..78dc1291 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -182,13 +182,9 @@ jobs: run: | HEAD_FOLDER=$(pwd) HEAD_BENCH_FOLDER=$HEAD_FOLDER/tmp/benchmarks/head - MASTER_BENCH_FOLDER=$HEAD_FOLDER/tmp/benchmarks/master - BENCH_FOLDER=$HEAD_FOLDER/tmp/benchmarks echo "head_folder=$HEAD_FOLDER" >> $GITHUB_OUTPUT echo "head_benchmarks_folder=$HEAD_BENCH_FOLDER" >> $GITHUB_OUTPUT - echo "master_benchmarks_folder=$MASTER_BENCH_FOLDER" >> $GITHUB_OUTPUT - echo "benchmarks_folder=$BENCH_FOLDER" >> $GITHUB_OUTPUT - name: Git Checkout uses: actions/checkout@v2 @@ -216,20 +212,11 @@ jobs: - name: Determine results file (HEAD) id: determine-results-file run: | - JSON_FILE=$(find artifacts/results -name '*.json') - echo "file=${{ steps.determine-folders.outputs.head_benchmarks_folder }}/$JSON_FILE" >> $GITHUB_OUTPUT - - - name: Change working directory (benchmarks) - run: cd ${{ steps.determine-folders.outputs.benchmarks_folder }} - - - name: Get report dependencies - run: grab newtonsoft.json@13.0.2 - - - name: Generate report - run: t4 -p:ResultsFile="${{ steps.determine-results-file.outputs.file }}" -P="packages/newtonsoft.json/13.0.2/lib/net6.0" -r="Newtonsoft.Json.dll" -o "report.md" "${{ steps.determine-folders.outputs.head_folder }}/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt" + MD_FILE=$(find artifacts/results -name '*.md') + echo "file=${{ steps.determine-folders.outputs.head_benchmarks_folder }}/$MD_FILE" >> $GITHUB_OUTPUT - name: Publish report results uses: peter-evans/create-or-update-comment@v2 with: issue-number: ${{ github.event.issue.number }} - body-file: report.md \ No newline at end of file + body-file: ${{ steps.determine-results-file.outputs.file }} \ No newline at end of file diff --git a/run-benchmarks.ps1 b/run-benchmarks.ps1 index 5259939f..4e232138 100644 --- a/run-benchmarks.ps1 +++ b/run-benchmarks.ps1 @@ -44,27 +44,23 @@ Set-Location -Path $reportDir bin\Rules.Framework.BenchmarkTests.exe -a artifacts # Determine results file -$filteredResultsFiles = Get-ChildItem -Path "$reportDir/artifacts/results" -File -Filter *.json +$filteredResultsFiles = Get-ChildItem -Path artifacts/results -File -Filter *.md if ($filteredResultsFiles) { $resultsFile = $filteredResultsFiles.Name - # Get report dependencies - grab newtonsoft.json@13.0.2 + # Copy results file + Copy-Item -Path artifacts/results/$resultsFile -Destination . - # Generate report - t4 -p:ResultsFile="$reportDir/artifacts/results/$resultsFile" -P="$reportDir/packages/newtonsoft.json/13.0.2/lib/net6.0" -r="Newtonsoft.Json.dll" ` - -o "$reportDir/report.md" "$originalDir/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt" + # Rename file + Rename-Item -Path $resultsFile -NewName report.md } if (!$KeepBenchmarksFiles) { if ($directoryFound = Get-ChildItem -Path $reportDir -Directory | Select-String -Pattern "artifacts") { - Remove-Item -Path "$reportDir/artifacts" -Recurse > $null + Remove-Item -Path artifacts -Recurse > $null } if ($directoryFound = Get-ChildItem -Path $reportDir -Directory | Select-String -Pattern "bin") { - Remove-Item -Path "$reportDir/bin" -Recurse > $null - } - if ($directoryFound = Get-ChildItem -Path $reportDir -Directory | Select-String -Pattern "packages") { - Remove-Item -Path "$reportDir/packages" -Recurse > $null + Remove-Item -Path bin -Recurse > $null } } diff --git a/tests/Rules.Framework.BenchmarkTests/Exporters/Json/CustomJsonExporter.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Json/CustomJsonExporter.cs index 33d6f779..fb43a89d 100644 --- a/tests/Rules.Framework.BenchmarkTests/Exporters/Json/CustomJsonExporter.cs +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Json/CustomJsonExporter.cs @@ -74,38 +74,20 @@ private static decimal CalculateRate(decimal baselineValue, decimal compareValue private static BenchmarkStatisticsItem CreateBenchmarkStatisticsItem(SummaryTable.SummaryTableColumn baselineClassifierColumn, ref int current, BenchmarkDotNet.Reports.BenchmarkReport benchmarkReport) { - var allocatedMemoryMetric = benchmarkReport.Metrics.First(m => m.Key == "Allocated Memory"); - var branchInstructionsMetric = benchmarkReport.Metrics.First(m => m.Key == "BranchInstructions"); - var branchMispredictionsMetric = benchmarkReport.Metrics.First(m => m.Key == "BranchMispredictions"); - var gen0CollectsMetric = benchmarkReport.Metrics.First(m => m.Key == "Gen0Collects"); + var allocatedMemoryMetric = benchmarkReport.Metrics.FirstOrDefault(m => m.Key == "Allocated Memory"); + var branchInstructionsMetric = benchmarkReport.Metrics.FirstOrDefault(m => m.Key == "BranchInstructions"); + var branchMispredictionsMetric = benchmarkReport.Metrics.FirstOrDefault(m => m.Key == "BranchMispredictions"); + var gen0CollectsMetric = benchmarkReport.Metrics.FirstOrDefault(m => m.Key == "Gen0Collects"); var statisticsItem = new BenchmarkStatisticsItem { - AllocatedMemory = new BenchmarkStatisticsValue - { - Format = allocatedMemoryMetric.Value.Descriptor.NumberFormat, - Unit = allocatedMemoryMetric.Value.Descriptor.Unit, - Value = Convert.ToDecimal(allocatedMemoryMetric.Value.Value), - }, + AllocatedMemory = allocatedMemoryMetric.Key is null ? null : CreateBenchmarkStatisticsValue(allocatedMemoryMetric), Baseline = baselineClassifierColumn.Content[current++], - BranchInstructionsPerOp = new BenchmarkStatisticsValue - { - Format = branchInstructionsMetric.Value.Descriptor.NumberFormat, - Unit = branchInstructionsMetric.Value.Descriptor.Unit, - Value = Convert.ToDecimal(branchInstructionsMetric.Value.Value), - }, - BranchMispredictionsPerOp = new BenchmarkStatisticsValue - { - Format = branchMispredictionsMetric.Value.Descriptor.NumberFormat, - Unit = branchMispredictionsMetric.Value.Descriptor.Unit, - Value = Convert.ToDecimal(branchMispredictionsMetric.Value.Value) - }, - Gen0Collects = new BenchmarkStatisticsValue - { - Format = gen0CollectsMetric.Value.Descriptor.NumberFormat, - Unit = gen0CollectsMetric.Value.Descriptor.Unit, - Value = Convert.ToDecimal(gen0CollectsMetric.Value.Value) - }, + BranchInstructionsPerOp = + branchInstructionsMetric.Key is null ? null : CreateBenchmarkStatisticsValue(branchInstructionsMetric), + BranchMispredictionsPerOp = + branchMispredictionsMetric.Key is null ? null : CreateBenchmarkStatisticsValue(branchMispredictionsMetric), + Gen0Collects = gen0CollectsMetric.Key is null ? null : CreateBenchmarkStatisticsValue(gen0CollectsMetric), Key = $"{benchmarkReport.BenchmarkCase.Descriptor.Type.Name}.{benchmarkReport.BenchmarkCase.Descriptor.WorkloadMethod.Name}", MeanTimeTaken = new BenchmarkStatisticsValue { @@ -124,6 +106,13 @@ private static BenchmarkStatisticsItem CreateBenchmarkStatisticsItem(SummaryTabl return statisticsItem; } + private static BenchmarkStatisticsValue CreateBenchmarkStatisticsValue(KeyValuePair allocatedMemoryMetric) => new BenchmarkStatisticsValue + { + Format = allocatedMemoryMetric.Value.Descriptor.NumberFormat, + Unit = allocatedMemoryMetric.Value.Descriptor.Unit, + Value = Convert.ToDecimal(allocatedMemoryMetric.Value.Value), + }; + private static Environment CreateEnvironment(Summary summary) => new Environment { Architecture = summary.HostEnvironmentInfo.Architecture, diff --git a/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/BenchmarkReport.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/BenchmarkReport.cs new file mode 100644 index 00000000..5a14e963 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/BenchmarkReport.cs @@ -0,0 +1,17 @@ +namespace Rules.Framework.BenchmarkTests.Exporters.Markdown +{ + using System; + + internal class BenchmarkReport + { + public DateTime Date { get; set; } + + public Environment? Environment { get; set; } + + public IEnumerable? Statistics { get; set; } + + public IEnumerable? StatisticsComparison { get; set; } + + public string Title { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/BenchmarkStatisticsComparisonItem.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/BenchmarkStatisticsComparisonItem.cs new file mode 100644 index 00000000..8d96bff5 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/BenchmarkStatisticsComparisonItem.cs @@ -0,0 +1,23 @@ +namespace Rules.Framework.BenchmarkTests.Exporters.Markdown +{ + internal class BenchmarkStatisticsComparisonItem + { + public BenchmarkStatisticsValue? AllocatedMemoryRate { get; set; } + + public BenchmarkStatisticsValue? BaselineAllocatedMemory { get; set; } + + public BenchmarkStatisticsValue? BaselineMeanTimeTaken { get; set; } + + public string BaselineParameters { get; set; } = string.Empty; + + public BenchmarkStatisticsValue? CompareAllocatedMemory { get; set; } + + public BenchmarkStatisticsValue? CompareMeanTimeTaken { get; set; } + + public string CompareParameters { get; set; } = string.Empty; + + public string Key { get; set; } = string.Empty; + + public BenchmarkStatisticsValue? MeanTimeTakenCompareRate { get; set; } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/BenchmarkStatisticsItem.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/BenchmarkStatisticsItem.cs new file mode 100644 index 00000000..fcb02d84 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/BenchmarkStatisticsItem.cs @@ -0,0 +1,23 @@ +namespace Rules.Framework.BenchmarkTests.Exporters.Markdown +{ + internal class BenchmarkStatisticsItem + { + public BenchmarkStatisticsValue? AllocatedMemory { get; set; } + + public string Baseline { get; set; } = string.Empty; + + public BenchmarkStatisticsValue? BranchInstructionsPerOp { get; set; } + + public BenchmarkStatisticsValue? BranchMispredictionsPerOp { get; set; } + + public BenchmarkStatisticsValue? Gen0Collects { get; set; } + + public string Key { get; set; } = string.Empty; + + public BenchmarkStatisticsValue? MeanTimeTaken { get; set; } + + public string Parameters { get; set; } = string.Empty; + + public BenchmarkStatisticsValue? StandardError { get; set; } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/BenchmarkStatisticsValue.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/BenchmarkStatisticsValue.cs new file mode 100644 index 00000000..3b4d8080 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/BenchmarkStatisticsValue.cs @@ -0,0 +1,11 @@ +namespace Rules.Framework.BenchmarkTests.Exporters.Markdown +{ + internal class BenchmarkStatisticsValue + { + public string Format { get; set; } = string.Empty; + + public string Unit { get; set; } = string.Empty; + + public decimal Value { get; set; } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/CustomMarkdownExporter.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/CustomMarkdownExporter.cs new file mode 100644 index 00000000..182371ca --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/CustomMarkdownExporter.cs @@ -0,0 +1,231 @@ +namespace Rules.Framework.BenchmarkTests.Exporters.Markdown +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using BenchmarkDotNet.Exporters; + using BenchmarkDotNet.Loggers; + using BenchmarkDotNet.Reports; + + internal class CustomMarkdownExporter : ExporterBase + { + public CustomMarkdownExporter() + { + } + + public static IExporter Default => new CustomMarkdownExporter(); + + protected override string FileExtension => "md"; + + public override void ExportToLog(Summary summary, ILogger logger) + { + var report = CustomMarkdownExporter.CreateReport(summary); + + string markdownReport = CustomMarkdownExporter.ParseReportAsMarkdown(report); + + logger.WriteLine(markdownReport); + } + + private static decimal CalculateRate(decimal baselineValue, decimal compareValue) + => ((baselineValue - compareValue) / baselineValue) * 100; + + private static BenchmarkStatisticsComparisonItem CreateBenchmarkStatisticsComparisonItem(BenchmarkStatisticsItem? baselineStatisticsItem, BenchmarkStatisticsItem? nonBaselineStatisticsItem) => new BenchmarkStatisticsComparisonItem + { + AllocatedMemoryRate = new BenchmarkStatisticsValue + { + Format = "0.##", + Unit = "%", + Value = CalculateRate( + baselineStatisticsItem.AllocatedMemory?.Value ?? 0, + nonBaselineStatisticsItem.AllocatedMemory?.Value ?? 0), + }, + BaselineAllocatedMemory = baselineStatisticsItem.AllocatedMemory, + BaselineMeanTimeTaken = baselineStatisticsItem.MeanTimeTaken, + BaselineParameters = baselineStatisticsItem.Parameters, + CompareAllocatedMemory = nonBaselineStatisticsItem.AllocatedMemory, + CompareMeanTimeTaken = nonBaselineStatisticsItem.MeanTimeTaken, + CompareParameters = nonBaselineStatisticsItem.Parameters, + Key = baselineStatisticsItem.Key, + MeanTimeTakenCompareRate = new BenchmarkStatisticsValue + { + Format = "0.##", + Unit = "%", + Value = CalculateRate( + baselineStatisticsItem.MeanTimeTaken?.Value ?? 0, + nonBaselineStatisticsItem.MeanTimeTaken?.Value ?? 0), + } + }; + + private static BenchmarkStatisticsItem CreateBenchmarkStatisticsItem(SummaryTable.SummaryTableColumn baselineClassifierColumn, ref int current, BenchmarkDotNet.Reports.BenchmarkReport benchmarkReport) + { + var allocatedMemoryMetric = benchmarkReport.Metrics.FirstOrDefault(m => m.Key == "Allocated Memory"); + var branchInstructionsMetric = benchmarkReport.Metrics.FirstOrDefault(m => m.Key == "BranchInstructions"); + var branchMispredictionsMetric = benchmarkReport.Metrics.FirstOrDefault(m => m.Key == "BranchMispredictions"); + var gen0CollectsMetric = benchmarkReport.Metrics.FirstOrDefault(m => m.Key == "Gen0Collects"); + + var statisticsItem = new BenchmarkStatisticsItem + { + AllocatedMemory = allocatedMemoryMetric.Key is null ? null : CreateBenchmarkStatisticsValue(allocatedMemoryMetric), + Baseline = baselineClassifierColumn.Content[current++], + BranchInstructionsPerOp = + branchInstructionsMetric.Key is null ? null : CreateBenchmarkStatisticsValue(branchInstructionsMetric), + BranchMispredictionsPerOp = + branchMispredictionsMetric.Key is null ? null : CreateBenchmarkStatisticsValue(branchMispredictionsMetric), + Gen0Collects = gen0CollectsMetric.Key is null ? null : CreateBenchmarkStatisticsValue(gen0CollectsMetric), + Key = $"{benchmarkReport.BenchmarkCase.Descriptor.Type.Name}.{benchmarkReport.BenchmarkCase.Descriptor.WorkloadMethod.Name}", + MeanTimeTaken = new BenchmarkStatisticsValue + { + Format = "N0", + Unit = "ns", + Value = Convert.ToDecimal(benchmarkReport.ResultStatistics?.Mean ?? 0), + }, + Parameters = benchmarkReport.BenchmarkCase.Parameters.DisplayInfo, + StandardError = new BenchmarkStatisticsValue + { + Format = "N0", + Unit = "ns", + Value = Convert.ToDecimal(benchmarkReport.ResultStatistics?.StandardError ?? 0), + }, + }; + return statisticsItem; + } + + private static BenchmarkStatisticsValue CreateBenchmarkStatisticsValue(KeyValuePair allocatedMemoryMetric) => new BenchmarkStatisticsValue + { + Format = allocatedMemoryMetric.Value.Descriptor.NumberFormat, + Unit = allocatedMemoryMetric.Value.Descriptor.Unit, + Value = Convert.ToDecimal(allocatedMemoryMetric.Value.Value), + }; + + private static Environment CreateEnvironment(Summary summary) => new Environment + { + Architecture = summary.HostEnvironmentInfo.Architecture, + BenchmarkDotNetCaption = "BenchmarkDotNet", + BenchmarkDotNetVersion = summary.HostEnvironmentInfo.BenchmarkDotNetVersion, + BuildConfiguration = summary.HostEnvironmentInfo.Configuration, + DotNetCliVersion = summary.HostEnvironmentInfo.DotNetSdkVersion.Value, + DotNetRuntimeVersion = summary.HostEnvironmentInfo.RuntimeVersion, + LogicalCoreCount = summary.HostEnvironmentInfo.CpuInfo.Value.LogicalCoreCount.GetValueOrDefault(0), + PhysicalCoreCount = summary.HostEnvironmentInfo.CpuInfo.Value.PhysicalCoreCount.GetValueOrDefault(0), + ProcessorName = summary.HostEnvironmentInfo.CpuInfo.Value.ProcessorName, + }; + + private static BenchmarkReport CreateReport(Summary summary) + { + var report = new BenchmarkReport + { + Date = DateTime.UtcNow, + Environment = CreateEnvironment(summary), + Title = summary.Title, + }; + var statistics = new List(summary.Reports.Length); + + var baselineClassifierColumn = summary.Table.Columns.First(c => c.OriginalColumn.ColumnName == "Baseline"); + var current = 0; + foreach (var benchmarkReport in summary.Reports) + { + var statisticsItem = CreateBenchmarkStatisticsItem(baselineClassifierColumn, ref current, benchmarkReport); + + statistics.Add(statisticsItem); + } + report.Statistics = statistics; + + var baselineStatisticsItems = statistics.Where(i => string.Equals(i.Baseline, "Yes")); + var nonBaselineStatisticsItems = statistics.Where(i => string.Equals(i.Baseline, "No")); + var statisticsComparison = new List(); + + foreach (var baselineStatisticsItem in baselineStatisticsItems) + { + foreach (var nonBaselineStatisticsItem in nonBaselineStatisticsItems.Where(i => string.Equals(i.Key, baselineStatisticsItem.Key))) + { + var statisticsComparisonItem = CreateBenchmarkStatisticsComparisonItem(baselineStatisticsItem, nonBaselineStatisticsItem); + + statisticsComparison.Add(statisticsComparisonItem); + } + } + report.StatisticsComparison = statisticsComparison; + + return report; + } + + private static string ParseReportAsMarkdown(BenchmarkReport benchmarkReport) + { + var hostEnvInfo = benchmarkReport.Environment; + var stringBuilder = new StringBuilder("# Benchmark Results Report") + .AppendLine() + .AppendLine($"Date & Time: {benchmarkReport.Date:yyyy-MM-dd HH:mm:ss}") + .AppendLine() + .AppendLine("## Environment") + .AppendLine() + .AppendLine($">{hostEnvInfo.BenchmarkDotNetCaption} Version={benchmarkReport.Environment.BenchmarkDotNetVersion}") + .AppendLine(">") + .AppendLine($">Processor={hostEnvInfo.ProcessorName},{hostEnvInfo.PhysicalCoreCount} physical cores, {hostEnvInfo.LogicalCoreCount} logical cores") + .AppendLine(">") + .AppendLine($">Architecture={hostEnvInfo.Architecture}, Runtime={hostEnvInfo.DotNetRuntimeVersion}, Configuration={hostEnvInfo.BuildConfiguration}") + .AppendLine(">") + .AppendLine($">.NET CLI Version={hostEnvInfo.DotNetCliVersion}"); + + stringBuilder.AppendLine() + .AppendLine("## Statistics") + .AppendLine(); + + var hasBranchInstructionsPerOp = benchmarkReport.Statistics.FirstOrDefault()?.BranchInstructionsPerOp is not null; + var hasBranchMispredictionsPerOp = benchmarkReport.Statistics.FirstOrDefault()?.BranchMispredictionsPerOp is not null; + var hasGen0Collects = benchmarkReport.Statistics.FirstOrDefault()?.Gen0Collects is not null; + var hasAllocatedMemory = benchmarkReport.Statistics.FirstOrDefault()?.AllocatedMemory is not null; + + stringBuilder.Append("|Name|Parameters|Mean Time Taken|Std Error|") + .AppendIf("Branch
Instructions/Op|", () => hasBranchInstructionsPerOp) + .AppendIf("Branch
Mispredictions/Op|", () => hasBranchMispredictionsPerOp) + .AppendIf("GC Gen0|", () => hasGen0Collects) + .AppendIf("Allocated Memory|", () => hasAllocatedMemory) + .AppendLine() + .Append("|---|---|---|---|") + .AppendIf("---|", () => hasBranchInstructionsPerOp) + .AppendIf("---|", () => hasBranchMispredictionsPerOp) + .AppendIf("---|", () => hasGen0Collects) + .AppendIf("---|", () => hasAllocatedMemory) + .AppendLine(); + + foreach (var statisticsItem in benchmarkReport.Statistics) + { + stringBuilder.Append($"|{statisticsItem.Key}") + .Append($"|{statisticsItem.Parameters}") + .Append($"|{statisticsItem.MeanTimeTaken.Value.ToString((string)statisticsItem.MeanTimeTaken.Format)} {statisticsItem.MeanTimeTaken.Unit}") + .Append($"|{statisticsItem.StandardError.Value.ToString((string)statisticsItem.StandardError.Format)} {statisticsItem.StandardError.Unit}") + .AppendIf($"|{statisticsItem.BranchInstructionsPerOp.Value.ToString((string)statisticsItem.BranchInstructionsPerOp.Format)}", () => hasBranchInstructionsPerOp) + .AppendIf($"|{statisticsItem.BranchMispredictionsPerOp.Value.ToString((string)statisticsItem.BranchMispredictionsPerOp.Format)}", () => hasBranchMispredictionsPerOp) + .AppendIf($"|{statisticsItem.Gen0Collects.Value.ToString((string)statisticsItem.Gen0Collects.Format)}", () => hasGen0Collects) + .AppendIf($"|{statisticsItem.AllocatedMemory.Value.ToString((string)statisticsItem.AllocatedMemory.Format)} {statisticsItem.AllocatedMemory.Unit}", () => hasAllocatedMemory) + .AppendLine("|"); + } + + stringBuilder.AppendLine() + .AppendLine("## Statistics Comparison") + .AppendLine() + .Append("|Name|Baseline|Compare|Mean Time Taken
[Baseline]|Mean Time Taken
[Compare]|Mean Time Taken
[Comparison %]") + .AppendIf("|Allocated Memory
[Baseline]|Allocated Memory
[Compare]|Allocated Memory
[Comparison %]", () => hasAllocatedMemory) + .AppendLine("|") + .Append("|---|---|---|---|---|---") + .AppendIf("|---|---|---", () => hasAllocatedMemory) + .AppendLine("|"); + + foreach (var statisticsComparisonItem in benchmarkReport.StatisticsComparison) + { + stringBuilder.Append($"|{statisticsComparisonItem.Key}") + .Append($"|{statisticsComparisonItem.BaselineParameters}") + .Append($"|{statisticsComparisonItem.CompareParameters}") + .Append($"|{statisticsComparisonItem.BaselineMeanTimeTaken.Value.ToString((string)statisticsComparisonItem.BaselineMeanTimeTaken.Format)} {statisticsComparisonItem.BaselineMeanTimeTaken.Unit}") + .Append($"|{statisticsComparisonItem.CompareMeanTimeTaken.Value.ToString((string)statisticsComparisonItem.CompareMeanTimeTaken.Format)} {statisticsComparisonItem.CompareMeanTimeTaken.Unit}") + .Append($"|{statisticsComparisonItem.MeanTimeTakenCompareRate.Value.ToString((string)statisticsComparisonItem.MeanTimeTakenCompareRate.Format)} {statisticsComparisonItem.MeanTimeTakenCompareRate.Unit}") + .AppendIf($"|{statisticsComparisonItem.BaselineAllocatedMemory.Value.ToString((string)statisticsComparisonItem.BaselineAllocatedMemory.Format)} {statisticsComparisonItem.BaselineAllocatedMemory.Unit}", () => hasAllocatedMemory) + .AppendIf($"|{statisticsComparisonItem.CompareAllocatedMemory.Value.ToString((string)statisticsComparisonItem.CompareAllocatedMemory.Format)} {statisticsComparisonItem.CompareAllocatedMemory.Unit}", () => hasAllocatedMemory) + .AppendIf($"|{statisticsComparisonItem.AllocatedMemoryRate.Value.ToString((string)statisticsComparisonItem.AllocatedMemoryRate.Format)} {statisticsComparisonItem.AllocatedMemoryRate.Unit}", () => hasAllocatedMemory) + .AppendLine("|"); + } + + return stringBuilder.ToString(); + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/Environment.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/Environment.cs new file mode 100644 index 00000000..93d72904 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/Environment.cs @@ -0,0 +1,23 @@ +namespace Rules.Framework.BenchmarkTests.Exporters.Markdown +{ + internal class Environment + { + public string Architecture { get; set; } = string.Empty; + + public string BenchmarkDotNetCaption { get; set; } = string.Empty; + + public string BenchmarkDotNetVersion { get; set; } = string.Empty; + + public string BuildConfiguration { get; set; } = string.Empty; + + public string DotNetCliVersion { get; set; } = string.Empty; + + public string DotNetRuntimeVersion { get; set; } = string.Empty; + + public int LogicalCoreCount { get; set; } + + public int PhysicalCoreCount { get; set; } + + public string ProcessorName { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/StringBuilderExtensions.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/StringBuilderExtensions.cs new file mode 100644 index 00000000..79082b62 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/StringBuilderExtensions.cs @@ -0,0 +1,18 @@ +namespace Rules.Framework.BenchmarkTests.Exporters.Markdown +{ + using System; + using System.Text; + + internal static class StringBuilderExtensions + { + public static StringBuilder AppendIf(this StringBuilder builder, string text, Func conditionFunc) + { + if (conditionFunc.Invoke()) + { + return builder.Append(text); + } + + return builder; + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Program.cs b/tests/Rules.Framework.BenchmarkTests/Program.cs index a6d8cd9b..879d8585 100644 --- a/tests/Rules.Framework.BenchmarkTests/Program.cs +++ b/tests/Rules.Framework.BenchmarkTests/Program.cs @@ -1,3 +1,4 @@ +using System.Runtime.InteropServices; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnosers; @@ -6,7 +7,7 @@ using BenchmarkDotNet.Running; using McMaster.Extensions.CommandLineUtils; using Rules.Framework.BenchmarkTests; -using Rules.Framework.BenchmarkTests.Exporters.Json; +using Rules.Framework.BenchmarkTests.Exporters.Markdown; [assembly: SimpleJob(RuntimeMoniker.Net60)] @@ -30,9 +31,14 @@ private static int Main(string[] args) var manualConfig = ManualConfig.CreateMinimumViable(); manualConfig.AddDiagnoser(MemoryDiagnoser.Default); - manualConfig.AddHardwareCounters(HardwareCounter.BranchInstructions, HardwareCounter.BranchMispredictions); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + manualConfig.AddHardwareCounters(HardwareCounter.BranchInstructions, HardwareCounter.BranchMispredictions); + } + manualConfig.AddExporter(HtmlExporter.Default); - manualConfig.AddExporter(CustomJsonExporter.Indented); + manualConfig.AddExporter(CustomMarkdownExporter.Default); manualConfig.WithOption(ConfigOptions.JoinSummary, true); var artifactsPath = artifactsPathOption.Value(); diff --git a/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt b/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt index b53ade06..5d391909 100644 --- a/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt +++ b/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt @@ -10,6 +10,10 @@ var rawJsonText = streamReader.ReadToEnd(); dynamic results = JsonConvert.DeserializeObject(rawJsonText); dynamic hostEnvInfo = results.Environment; + var hasBranchInstructionsPerOp = ((IEnumerable)results.Statistics).FirstOrDefault()?.BranchInstructionsPerOp.Key is null; + var hasBranchMispredictionsPerOp = ((IEnumerable)results.Statistics).FirstOrDefault()?.BranchMispredictionsPerOp.Key is null; + var hasGen0Collects = ((IEnumerable)results.Statistics).FirstOrDefault()?.Gen0Collects.Key is null; + var hasAllocatedMemory = ((IEnumerable)results.Statistics).FirstOrDefault()?.AllocatedMemory.Key is null; #> # Benchmark Results Report @@ -35,10 +39,29 @@ Date & Time: <#= results.Date.ToString("yyyy-MM-dd HH:mm:ss") #> var parameters = statisticsItem.Parameters; var mean = $"{statisticsItem.MeanTimeTaken.Value.ToString((string)statisticsItem.MeanTimeTaken.Format)} {statisticsItem.MeanTimeTaken.Unit}"; var stdError = $"{statisticsItem.StandardError.Value.ToString((string)statisticsItem.StandardError.Format)} {statisticsItem.StandardError.Unit}"; - var branchInstructionsPerOp = statisticsItem.BranchInstructionsPerOp.Value.ToString((string)statisticsItem.BranchInstructionsPerOp.Format); - var branchMispredictionsPerOp = statisticsItem.BranchMispredictionsPerOp.Value.ToString((string)statisticsItem.BranchMispredictionsPerOp.Format); - var gen0Collects = statisticsItem.Gen0Collects.Value.ToString((string)statisticsItem.Gen0Collects.Format); - var allocatedMemory = $"{statisticsItem.AllocatedMemory.Value.ToString((string)statisticsItem.AllocatedMemory.Format)} {statisticsItem.AllocatedMemory.Unit}"; + var branchInstructionsPerOp = string.Empty; + if (hasBranchInstructionsPerOp) + { + branchInstructionsPerOp = statisticsItem.BranchInstructionsPerOp.Value.ToString((string)statisticsItem.BranchInstructionsPerOp.Format); + } + + var branchMispredictionsPerOp = string.Empty; + if (hasBranchMispredictionsPerOp) + { + branchMispredictionsPerOp = statisticsItem.BranchMispredictionsPerOp.Value.ToString((string)statisticsItem.BranchMispredictionsPerOp.Format); + } + + var gen0Collects = string.Empty; + if (hasGen0Collects) + { + gen0Collects = statisticsItem.Gen0Collects.Value.ToString((string)statisticsItem.Gen0Collects.Format); + } + + var allocatedMemory = string.Empty; + if (hasAllocatedMemory) + { + allocatedMemory = $"{statisticsItem.AllocatedMemory.Value.ToString((string)statisticsItem.AllocatedMemory.Format)} {statisticsItem.AllocatedMemory.Unit}"; + } #> | <#= name #> | <#= parameters #> | <#= mean #> | <#= stdError #> | <#= branchInstructionsPerOp #> | <#= branchMispredictionsPerOp #> | <#= gen0Collects #> | <#= allocatedMemory #> | <#} #> From 2131823cadeb7aa6697e5cbfdc7aab479e783b19 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 12 Mar 2023 20:27:15 +0000 Subject: [PATCH 43/54] fix: fix null reference when there are statistics missing --- .github/workflows/dotnet-build.yml | 8 +---- .../Markdown/CustomMarkdownExporter.cs | 34 +++++++++---------- .../Markdown/StringBuilderExtensions.cs | 4 +-- 3 files changed, 20 insertions(+), 26 deletions(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index 974aafad..dcae8957 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -171,12 +171,6 @@ jobs: benchmarks: runs-on: ubuntu-latest steps: - - name: Setup Grab tool - run: dotnet tool install dotnet-grab --global --ignore-failed-sources - - - name: Setup T4 tool - run: dotnet tool install dotnet-t4 --global --ignore-failed-sources - - name: Determine folders id: determine-folders run: | @@ -202,7 +196,7 @@ jobs: run: dotnet build -c Release tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj --framework net6.0 - name: Run benchmarks (HEAD) - run: sudo dotnet run --project tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj --framework net6.0 -- -a "${{ steps.determine-folders.outputs.head_benchmarks_folder }}/artifacts" + run: sudo dotnet run --project tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -c Release --framework net6.0 -- -a "${{ steps.determine-folders.outputs.head_benchmarks_folder }}/artifacts" - name: Change working directory (HEAD) run: | diff --git a/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/CustomMarkdownExporter.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/CustomMarkdownExporter.cs index 182371ca..3aa39679 100644 --- a/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/CustomMarkdownExporter.cs +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/CustomMarkdownExporter.cs @@ -176,16 +176,16 @@ private static string ParseReportAsMarkdown(BenchmarkReport benchmarkReport) var hasAllocatedMemory = benchmarkReport.Statistics.FirstOrDefault()?.AllocatedMemory is not null; stringBuilder.Append("|Name|Parameters|Mean Time Taken|Std Error|") - .AppendIf("Branch
Instructions/Op|", () => hasBranchInstructionsPerOp) - .AppendIf("Branch
Mispredictions/Op|", () => hasBranchMispredictionsPerOp) - .AppendIf("GC Gen0|", () => hasGen0Collects) - .AppendIf("Allocated Memory|", () => hasAllocatedMemory) + .AppendIf(() => "Branch
Instructions/Op|", () => hasBranchInstructionsPerOp) + .AppendIf(() => "Branch
Mispredictions/Op|", () => hasBranchMispredictionsPerOp) + .AppendIf(() => "GC Gen0|", () => hasGen0Collects) + .AppendIf(() => "Allocated Memory|", () => hasAllocatedMemory) .AppendLine() .Append("|---|---|---|---|") - .AppendIf("---|", () => hasBranchInstructionsPerOp) - .AppendIf("---|", () => hasBranchMispredictionsPerOp) - .AppendIf("---|", () => hasGen0Collects) - .AppendIf("---|", () => hasAllocatedMemory) + .AppendIf(() => "---|", () => hasBranchInstructionsPerOp) + .AppendIf(() => "---|", () => hasBranchMispredictionsPerOp) + .AppendIf(() => "---|", () => hasGen0Collects) + .AppendIf(() => "---|", () => hasAllocatedMemory) .AppendLine(); foreach (var statisticsItem in benchmarkReport.Statistics) @@ -194,10 +194,10 @@ private static string ParseReportAsMarkdown(BenchmarkReport benchmarkReport) .Append($"|{statisticsItem.Parameters}") .Append($"|{statisticsItem.MeanTimeTaken.Value.ToString((string)statisticsItem.MeanTimeTaken.Format)} {statisticsItem.MeanTimeTaken.Unit}") .Append($"|{statisticsItem.StandardError.Value.ToString((string)statisticsItem.StandardError.Format)} {statisticsItem.StandardError.Unit}") - .AppendIf($"|{statisticsItem.BranchInstructionsPerOp.Value.ToString((string)statisticsItem.BranchInstructionsPerOp.Format)}", () => hasBranchInstructionsPerOp) - .AppendIf($"|{statisticsItem.BranchMispredictionsPerOp.Value.ToString((string)statisticsItem.BranchMispredictionsPerOp.Format)}", () => hasBranchMispredictionsPerOp) - .AppendIf($"|{statisticsItem.Gen0Collects.Value.ToString((string)statisticsItem.Gen0Collects.Format)}", () => hasGen0Collects) - .AppendIf($"|{statisticsItem.AllocatedMemory.Value.ToString((string)statisticsItem.AllocatedMemory.Format)} {statisticsItem.AllocatedMemory.Unit}", () => hasAllocatedMemory) + .AppendIf(() => $"|{statisticsItem.BranchInstructionsPerOp.Value.ToString((string)statisticsItem.BranchInstructionsPerOp.Format)}", () => hasBranchInstructionsPerOp) + .AppendIf(() => $"|{statisticsItem.BranchMispredictionsPerOp.Value.ToString((string)statisticsItem.BranchMispredictionsPerOp.Format)}", () => hasBranchMispredictionsPerOp) + .AppendIf(() => $"|{statisticsItem.Gen0Collects.Value.ToString((string)statisticsItem.Gen0Collects.Format)}", () => hasGen0Collects) + .AppendIf(() => $"|{statisticsItem.AllocatedMemory.Value.ToString((string)statisticsItem.AllocatedMemory.Format)} {statisticsItem.AllocatedMemory.Unit}", () => hasAllocatedMemory) .AppendLine("|"); } @@ -205,10 +205,10 @@ private static string ParseReportAsMarkdown(BenchmarkReport benchmarkReport) .AppendLine("## Statistics Comparison") .AppendLine() .Append("|Name|Baseline|Compare|Mean Time Taken
[Baseline]|Mean Time Taken
[Compare]|Mean Time Taken
[Comparison %]") - .AppendIf("|Allocated Memory
[Baseline]|Allocated Memory
[Compare]|Allocated Memory
[Comparison %]", () => hasAllocatedMemory) + .AppendIf(() => "|Allocated Memory
[Baseline]|Allocated Memory
[Compare]|Allocated Memory
[Comparison %]", () => hasAllocatedMemory) .AppendLine("|") .Append("|---|---|---|---|---|---") - .AppendIf("|---|---|---", () => hasAllocatedMemory) + .AppendIf(() => "|---|---|---", () => hasAllocatedMemory) .AppendLine("|"); foreach (var statisticsComparisonItem in benchmarkReport.StatisticsComparison) @@ -219,9 +219,9 @@ private static string ParseReportAsMarkdown(BenchmarkReport benchmarkReport) .Append($"|{statisticsComparisonItem.BaselineMeanTimeTaken.Value.ToString((string)statisticsComparisonItem.BaselineMeanTimeTaken.Format)} {statisticsComparisonItem.BaselineMeanTimeTaken.Unit}") .Append($"|{statisticsComparisonItem.CompareMeanTimeTaken.Value.ToString((string)statisticsComparisonItem.CompareMeanTimeTaken.Format)} {statisticsComparisonItem.CompareMeanTimeTaken.Unit}") .Append($"|{statisticsComparisonItem.MeanTimeTakenCompareRate.Value.ToString((string)statisticsComparisonItem.MeanTimeTakenCompareRate.Format)} {statisticsComparisonItem.MeanTimeTakenCompareRate.Unit}") - .AppendIf($"|{statisticsComparisonItem.BaselineAllocatedMemory.Value.ToString((string)statisticsComparisonItem.BaselineAllocatedMemory.Format)} {statisticsComparisonItem.BaselineAllocatedMemory.Unit}", () => hasAllocatedMemory) - .AppendIf($"|{statisticsComparisonItem.CompareAllocatedMemory.Value.ToString((string)statisticsComparisonItem.CompareAllocatedMemory.Format)} {statisticsComparisonItem.CompareAllocatedMemory.Unit}", () => hasAllocatedMemory) - .AppendIf($"|{statisticsComparisonItem.AllocatedMemoryRate.Value.ToString((string)statisticsComparisonItem.AllocatedMemoryRate.Format)} {statisticsComparisonItem.AllocatedMemoryRate.Unit}", () => hasAllocatedMemory) + .AppendIf(() => $"|{statisticsComparisonItem.BaselineAllocatedMemory.Value.ToString((string)statisticsComparisonItem.BaselineAllocatedMemory.Format)} {statisticsComparisonItem.BaselineAllocatedMemory.Unit}", () => hasAllocatedMemory) + .AppendIf(() => $"|{statisticsComparisonItem.CompareAllocatedMemory.Value.ToString((string)statisticsComparisonItem.CompareAllocatedMemory.Format)} {statisticsComparisonItem.CompareAllocatedMemory.Unit}", () => hasAllocatedMemory) + .AppendIf(() => $"|{statisticsComparisonItem.AllocatedMemoryRate.Value.ToString((string)statisticsComparisonItem.AllocatedMemoryRate.Format)} {statisticsComparisonItem.AllocatedMemoryRate.Unit}", () => hasAllocatedMemory) .AppendLine("|"); } diff --git a/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/StringBuilderExtensions.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/StringBuilderExtensions.cs index 79082b62..c09ccd80 100644 --- a/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/StringBuilderExtensions.cs +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/StringBuilderExtensions.cs @@ -5,11 +5,11 @@ namespace Rules.Framework.BenchmarkTests.Exporters.Markdown internal static class StringBuilderExtensions { - public static StringBuilder AppendIf(this StringBuilder builder, string text, Func conditionFunc) + public static StringBuilder AppendIf(this StringBuilder builder, Func textFunc, Func conditionFunc) { if (conditionFunc.Invoke()) { - return builder.Append(text); + return builder.Append(textFunc.Invoke()); } return builder; From 059718c19b2461743f71b554b626785437010578 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 12 Mar 2023 22:47:45 +0000 Subject: [PATCH 44/54] chore: review file permissions and list all directories and sub-directories --- .github/workflows/dotnet-build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index dcae8957..f6bb82da 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -201,11 +201,12 @@ jobs: - name: Change working directory (HEAD) run: | cd ${{ steps.determine-folders.outputs.head_benchmarks_folder }} - ls -la + ls -lRA - name: Determine results file (HEAD) id: determine-results-file run: | + sudo chmod -R 777 artifacts MD_FILE=$(find artifacts/results -name '*.md') echo "file=${{ steps.determine-folders.outputs.head_benchmarks_folder }}/$MD_FILE" >> $GITHUB_OUTPUT From 86cb300263b78c77a864f892f4e2aca256442c40 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 12 Mar 2023 23:00:25 +0000 Subject: [PATCH 45/54] chore: prefix paths from current folder --- .github/workflows/dotnet-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index f6bb82da..5acafd6b 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -206,8 +206,8 @@ jobs: - name: Determine results file (HEAD) id: determine-results-file run: | - sudo chmod -R 777 artifacts - MD_FILE=$(find artifacts/results -name '*.md') + sudo chmod -R 777 ./artifacts + MD_FILE=$(find ./artifacts/results -name '*.md') echo "file=${{ steps.determine-folders.outputs.head_benchmarks_folder }}/$MD_FILE" >> $GITHUB_OUTPUT - name: Publish report results From 13592e0adc115357d8133f0328ce2876789bd6f2 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 12 Mar 2023 23:18:24 +0000 Subject: [PATCH 46/54] chore: change to path of results --- .github/workflows/dotnet-build.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index 5acafd6b..60c508ab 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -202,13 +202,14 @@ jobs: run: | cd ${{ steps.determine-folders.outputs.head_benchmarks_folder }} ls -lRA + cd artifacts/results - name: Determine results file (HEAD) id: determine-results-file run: | - sudo chmod -R 777 ./artifacts - MD_FILE=$(find ./artifacts/results -name '*.md') - echo "file=${{ steps.determine-folders.outputs.head_benchmarks_folder }}/$MD_FILE" >> $GITHUB_OUTPUT + MD_FILE=$(find . -name '*.md') + sudo chmod -R 777 $MD_FILE + echo "file=$MD_FILE" >> $GITHUB_OUTPUT - name: Publish report results uses: peter-evans/create-or-update-comment@v2 From af4247141529464ecc4f96bb3abc2a380f2e11f5 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 12 Mar 2023 23:26:58 +0000 Subject: [PATCH 47/54] chore: consolidate change directory and determine report file --- .github/workflows/dotnet-build.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index 60c508ab..bef251b4 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -198,18 +198,14 @@ jobs: - name: Run benchmarks (HEAD) run: sudo dotnet run --project tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj -c Release --framework net6.0 -- -a "${{ steps.determine-folders.outputs.head_benchmarks_folder }}/artifacts" - - name: Change working directory (HEAD) - run: | - cd ${{ steps.determine-folders.outputs.head_benchmarks_folder }} - ls -lRA - cd artifacts/results - - name: Determine results file (HEAD) id: determine-results-file run: | - MD_FILE=$(find . -name '*.md') + cd ${{ steps.determine-folders.outputs.head_benchmarks_folder }} + ls -lRA sudo chmod -R 777 $MD_FILE - echo "file=$MD_FILE" >> $GITHUB_OUTPUT + MD_FILE=$(find artifacts/results -name '*.md') + echo "file=${{ steps.determine-folders.outputs.head_benchmarks_folder }}/artifacts/results/$MD_FILE" >> $GITHUB_OUTPUT - name: Publish report results uses: peter-evans/create-or-update-comment@v2 From 0258643458f3a39a1ca348ab86897e455adf14f2 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 12 Mar 2023 23:32:55 +0000 Subject: [PATCH 48/54] fix: set artifacts folder on chmod --- .github/workflows/dotnet-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index bef251b4..69f5577a 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -203,7 +203,7 @@ jobs: run: | cd ${{ steps.determine-folders.outputs.head_benchmarks_folder }} ls -lRA - sudo chmod -R 777 $MD_FILE + sudo chmod -R 777 artifacts MD_FILE=$(find artifacts/results -name '*.md') echo "file=${{ steps.determine-folders.outputs.head_benchmarks_folder }}/artifacts/results/$MD_FILE" >> $GITHUB_OUTPUT From 8b6fe4f064affed385dfe7444f90c1c8fce95c93 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 12 Mar 2023 23:37:33 +0000 Subject: [PATCH 49/54] fix: fix report results file path --- .github/workflows/dotnet-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index 69f5577a..add90a4e 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -205,7 +205,7 @@ jobs: ls -lRA sudo chmod -R 777 artifacts MD_FILE=$(find artifacts/results -name '*.md') - echo "file=${{ steps.determine-folders.outputs.head_benchmarks_folder }}/artifacts/results/$MD_FILE" >> $GITHUB_OUTPUT + echo "file=${{ steps.determine-folders.outputs.head_benchmarks_folder }}/$MD_FILE" >> $GITHUB_OUTPUT - name: Publish report results uses: peter-evans/create-or-update-comment@v2 From 87bcbd427ec23a34ce68e0806ebfc492c5017fbc Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Sun, 12 Mar 2023 23:47:23 +0000 Subject: [PATCH 50/54] chore: change to use event number --- .github/workflows/dotnet-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index add90a4e..2da622ea 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -210,5 +210,5 @@ jobs: - name: Publish report results uses: peter-evans/create-or-update-comment@v2 with: - issue-number: ${{ github.event.issue.number }} + issue-number: ${{ github.event.number }} body-file: ${{ steps.determine-results-file.outputs.file }} \ No newline at end of file From e3e5aac922492ead04b5fe14a1c65e778c2fa10e Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Mon, 13 Mar 2023 00:02:31 +0000 Subject: [PATCH 51/54] feat: changes to add/update a single comment with benchmarks results --- .github/workflows/dotnet-build.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index 2da622ea..3d947278 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -207,8 +207,18 @@ jobs: MD_FILE=$(find artifacts/results -name '*.md') echo "file=${{ steps.determine-folders.outputs.head_benchmarks_folder }}/$MD_FILE" >> $GITHUB_OUTPUT - - name: Publish report results + - name: Find comment - report results + uses: peter-evans/find-comment@v2 + id: find-comment + with: + issue-number: ${{ github.event.number }} + comment-author: 'github-actions[bot]' + body-includes: Benchmark Results Report + + - name: Create or update comment - report results uses: peter-evans/create-or-update-comment@v2 with: + comment-id: ${{ steps.find-comment.outputs.comment-id }} issue-number: ${{ github.event.number }} - body-file: ${{ steps.determine-results-file.outputs.file }} \ No newline at end of file + body-file: ${{ steps.determine-results-file.outputs.file }} + edit-mode: replace \ No newline at end of file From 86c18b399027e3da3fb30e66d010b064a3639f80 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Mon, 13 Mar 2023 00:14:24 +0000 Subject: [PATCH 52/54] chore: fix static code analysis issues and remove old logic for running benchmarks --- run-benchmarks.ps1 | 12 --- .../Markdown/CustomMarkdownExporter.cs | 4 - .../Results2Markdown/Report.tt | 89 ------------------- 3 files changed, 105 deletions(-) delete mode 100644 tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt diff --git a/run-benchmarks.ps1 b/run-benchmarks.ps1 index 4e232138..f3e03a0b 100644 --- a/run-benchmarks.ps1 +++ b/run-benchmarks.ps1 @@ -1,17 +1,5 @@ param ([switch] $KeepBenchmarksFiles) -$globalTools = dotnet tool list -g -$grabTool = $globalTools | Select-String -Pattern "dotnet-grab" -$t4Tool = $globalTools | Select-String -Pattern "dotnet-t4" - -if (!$grabTool) { - dotnet tool install dotnet-grab --global --ignore-failed-sources -} - -if (!$t4Tool) { - dotnet tool install dotnet-t4 --global --ignore-failed-sources -} - $originalDir = (Get-Location).Path $timestamp = [DateTime]::UtcNow.ToString("yyyyMMdd_hhmmss") diff --git a/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/CustomMarkdownExporter.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/CustomMarkdownExporter.cs index 3aa39679..8056e89c 100644 --- a/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/CustomMarkdownExporter.cs +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/CustomMarkdownExporter.cs @@ -10,10 +10,6 @@ namespace Rules.Framework.BenchmarkTests.Exporters.Markdown internal class CustomMarkdownExporter : ExporterBase { - public CustomMarkdownExporter() - { - } - public static IExporter Default => new CustomMarkdownExporter(); protected override string FileExtension => "md"; diff --git a/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt b/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt deleted file mode 100644 index 5d391909..00000000 --- a/tests/Rules.Framework.BenchmarkTests/Results2Markdown/Report.tt +++ /dev/null @@ -1,89 +0,0 @@ -<#@ template language="C#" #> -<#@ output extension=".md" #> -<#@ import namespace="System.IO" #> -<#@ import namespace="System.Collections.Generic" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="Newtonsoft.Json" #> -<#@ parameter name="ResultsFile" type="string" #> -<# - using var streamReader = File.OpenText(ResultsFile); - var rawJsonText = streamReader.ReadToEnd(); - dynamic results = JsonConvert.DeserializeObject(rawJsonText); - dynamic hostEnvInfo = results.Environment; - var hasBranchInstructionsPerOp = ((IEnumerable)results.Statistics).FirstOrDefault()?.BranchInstructionsPerOp.Key is null; - var hasBranchMispredictionsPerOp = ((IEnumerable)results.Statistics).FirstOrDefault()?.BranchMispredictionsPerOp.Key is null; - var hasGen0Collects = ((IEnumerable)results.Statistics).FirstOrDefault()?.Gen0Collects.Key is null; - var hasAllocatedMemory = ((IEnumerable)results.Statistics).FirstOrDefault()?.AllocatedMemory.Key is null; -#> -# Benchmark Results Report - -Date & Time: <#= results.Date.ToString("yyyy-MM-dd HH:mm:ss") #> - -## Environment - -><#= hostEnvInfo.BenchmarkDotNetCaption #> Version=<#= hostEnvInfo.BenchmarkDotNetVersion #> -> ->Processor=<#= hostEnvInfo.ProcessorName #>, <#= hostEnvInfo.PhysicalCoreCount #> physical cores, <#= hostEnvInfo.LogicalCoreCount #> logical cores -> ->Architecture=<#= hostEnvInfo.Architecture #>, Runtime=<#= hostEnvInfo.DotNetRuntimeVersion #>, Configuration=<#= hostEnvInfo.BuildConfiguration #> -> ->.NET CLI Version=<#= hostEnvInfo.DotNetCliVersion #> - -## Statistics - -| Name | Parameters | Mean Time Taken | Std Error | Branch
Instructions/Op | Branch
Mispredictions/Op | GC Gen0 | Allocated Memory | -| ---- | ---------- | --------------- | --------- | -------------------------- | ---------------------------- | ------- | ---------------- | -<# foreach (dynamic statisticsItem in results.Statistics) -{ - var name = statisticsItem.Key; - var parameters = statisticsItem.Parameters; - var mean = $"{statisticsItem.MeanTimeTaken.Value.ToString((string)statisticsItem.MeanTimeTaken.Format)} {statisticsItem.MeanTimeTaken.Unit}"; - var stdError = $"{statisticsItem.StandardError.Value.ToString((string)statisticsItem.StandardError.Format)} {statisticsItem.StandardError.Unit}"; - var branchInstructionsPerOp = string.Empty; - if (hasBranchInstructionsPerOp) - { - branchInstructionsPerOp = statisticsItem.BranchInstructionsPerOp.Value.ToString((string)statisticsItem.BranchInstructionsPerOp.Format); - } - - var branchMispredictionsPerOp = string.Empty; - if (hasBranchMispredictionsPerOp) - { - branchMispredictionsPerOp = statisticsItem.BranchMispredictionsPerOp.Value.ToString((string)statisticsItem.BranchMispredictionsPerOp.Format); - } - - var gen0Collects = string.Empty; - if (hasGen0Collects) - { - gen0Collects = statisticsItem.Gen0Collects.Value.ToString((string)statisticsItem.Gen0Collects.Format); - } - - var allocatedMemory = string.Empty; - if (hasAllocatedMemory) - { - allocatedMemory = $"{statisticsItem.AllocatedMemory.Value.ToString((string)statisticsItem.AllocatedMemory.Format)} {statisticsItem.AllocatedMemory.Unit}"; - } -#> -| <#= name #> | <#= parameters #> | <#= mean #> | <#= stdError #> | <#= branchInstructionsPerOp #> | <#= branchMispredictionsPerOp #> | <#= gen0Collects #> | <#= allocatedMemory #> | -<#} #> - -## Statistics Comparison - -| Name | Baseline | Compare | Mean Time Taken
[Baseline] | Mean Time Taken
[Compare] | Mean Time Taken
[Comparison %] | Allocated Memory
[Baseline] | Allocated Memory
[Compare] | Allocated Memory
[Comparison %] | -| ---- | -------- | ------- | ------------------------------ | ----------------------------- | ---------------------------------- | ------------------------------- | ------------------------------ | ----------------------------------- | -<# -foreach (dynamic statisticsComparisonItem in results.StatisticsComparison) -{ - var name = statisticsComparisonItem.Key; - var baselineParameters = statisticsComparisonItem.BaselineParameters; - var compareToParameters = statisticsComparisonItem.CompareParameters; - var baselineMean = $"{statisticsComparisonItem.BaselineMeanTimeTaken.Value.ToString((string)statisticsComparisonItem.BaselineMeanTimeTaken.Format)} {statisticsComparisonItem.BaselineMeanTimeTaken.Unit}"; - var compareToMean = $"{statisticsComparisonItem.CompareMeanTimeTaken.Value.ToString((string)statisticsComparisonItem.CompareMeanTimeTaken.Format)} {statisticsComparisonItem.CompareMeanTimeTaken.Unit}"; - var meanCompareRate = $"{statisticsComparisonItem.MeanTimeTakenCompareRate.Value.ToString((string)statisticsComparisonItem.MeanTimeTakenCompareRate.Format)} {statisticsComparisonItem.MeanTimeTakenCompareRate.Unit}"; - var baselineAllocatedMemory = $"{statisticsComparisonItem.BaselineAllocatedMemory.Value.ToString((string)statisticsComparisonItem.BaselineAllocatedMemory.Format)} {statisticsComparisonItem.BaselineAllocatedMemory.Unit}"; - var compareToAllocatedMemory = $"{statisticsComparisonItem.CompareAllocatedMemory.Value.ToString((string)statisticsComparisonItem.CompareAllocatedMemory.Format)} {statisticsComparisonItem.CompareAllocatedMemory.Unit}"; - var allocatedMemoryCompareRate = $"{statisticsComparisonItem.AllocatedMemoryRate.Value.ToString((string)statisticsComparisonItem.AllocatedMemoryRate.Format)} {statisticsComparisonItem.AllocatedMemoryRate.Unit}"; -#> -| <#= name #> | <#= baselineParameters #> | <#= compareToParameters #> | <#= baselineMean #> | <#= compareToMean #> | <#= meanCompareRate #> | <#= baselineAllocatedMemory #> | <#= compareToAllocatedMemory #> | <#= allocatedMemoryCompareRate #> | -<# -} -#> \ No newline at end of file From 46cf19904a2cdae621db3b6b76e410a3b746eab5 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Mon, 10 Apr 2023 18:58:08 +0100 Subject: [PATCH 53/54] fix: resolve bad conflicts from merge --- .../Evaluation/Interpreted/DeferredEval.cs | 8 --- .../Evaluation/Interpreted/IDeferredEval.cs | 4 -- .../InterpretedConditionsEvalEngine.cs | 14 ----- ...InsensitiveEndsWithOperatorEvalStrategy.cs | 4 -- ...sensitiveStartsWithOperatorEvalStrategy.cs | 4 -- .../ContainsOperatorEvalStrategy.cs | 4 -- .../ConditionEvalDispatchProvider.cs | 4 -- .../ConditionEvalDispatcherBase.cs | 4 -- .../IConditionEvalDispatchProvider.cs | 4 -- .../Dispatchers/IConditionEvalDispatcher.cs | 4 -- .../ManyToManyConditionEvalDispatcher.cs | 4 -- .../ManyToOneConditionEvalDispatcher.cs | 4 -- .../OneToManyConditionEvalDispatcher.cs | 4 -- .../OneToOneConditionEvalDispatcher.cs | 4 -- .../EndsWithOperatorEvalStrategy.cs | 4 -- .../EqualOperatorEvalStrategy.cs | 4 -- .../GreaterThanOperatorEvalStrategy.cs | 4 -- .../GreaterThanOrEqualOperatorEvalStrategy.cs | 4 -- .../IManyToManyOperatorEvalStrategy.cs | 4 -- .../IManyToOneOperatorEvalStrategy.cs | 4 -- .../IOneToManyOperatorEvalStrategy.cs | 4 -- .../IOneToOneOperatorEvalStrategy.cs | 4 -- .../IOperatorEvalStrategyFactory.cs | 4 -- .../ValueEvaluation/InOperatorEvalStrategy.cs | 4 -- .../LesserThanOperatorEvalStrategy.cs | 4 -- .../LesserThanOrEqualOperatorEvalStrategy.cs | 4 -- .../NotContainsOperatorEvalStrategy.cs | 4 -- .../NotEndsWithOperatorEvalStrategy.cs | 4 -- .../NotEqualOperatorEvalStrategy.cs | 4 -- .../NotInOperatorEvalStrategy.cs | 4 -- .../NotStartsWithOperatorEvalStrategy.cs | 4 -- .../OperatorEvalStrategyFactory.cs | 4 -- .../StartsWithOperatorEvalStrategy.cs | 4 -- .../Scenarios/Scenario1/BodyMassIndexTests.cs | 6 +- .../BooleanConditionNodeTests.cs | 30 ---------- .../DecimalConditionNodeTests.cs | 30 ---------- .../IntegerConditionNodeTests.cs | 30 ---------- .../StringConditionNodeTests.cs | 30 ---------- .../NotContainsOperatorEvalStrategyTests.cs | 59 ------------------- .../Interpreted/DeferredEvalTests.cs | 10 ---- .../InterpretedConditionsEvalEngineTests.cs | 28 --------- ...sitiveEndsWithOperatorEvalStrategyTests.cs | 8 --- ...tiveStartsWithOperatorEvalStrategyTests.cs | 8 --- .../ContainsOperatorEvalStrategyTests.cs | 9 +-- .../ManyToManyConditionEvalDispatcherTests.cs | 5 -- .../ManyToOneConditionEvalDispatcherTests.cs | 5 -- .../OneToManyConditionEvalDispatcherTests.cs | 5 -- .../OneToOneConditionEvalDispatcherTests.cs | 5 -- .../EndsWithOperatorEvalStrategyTests.cs | 9 +-- .../EqualOperatorEvalStrategyTests.cs | 8 --- .../GreaterThanOperatorEvalStrategyTests.cs | 8 --- ...terThanOrEqualOperatorEvalStrategyTests.cs | 8 --- .../InOperatorEvalStrategyTests.cs | 8 --- .../LesserThanOperatorEvalStrategyTests.cs | 8 --- ...serThanOrEqualOperatorEvalStrategyTests.cs | 8 --- .../NotEndsWithOperatorEvalStrategyTests.cs | 28 ++++----- .../NotEqualOperatorEvalStrategyTests.cs | 8 --- .../NotInOperatorEvalStrategyTests.cs | 8 --- .../NotStartsWithOperatorEvalStrategyTests.cs | 4 -- .../OperatorEvalStrategyFactoryTests.cs | 8 --- .../StartsWithOperatorEvalStrategyTests.cs | 9 +-- 61 files changed, 18 insertions(+), 518 deletions(-) delete mode 100644 tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotContainsOperatorEvalStrategyTests.cs diff --git a/src/Rules.Framework/Evaluation/Interpreted/DeferredEval.cs b/src/Rules.Framework/Evaluation/Interpreted/DeferredEval.cs index 86f68581..4753e8c9 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/DeferredEval.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/DeferredEval.cs @@ -1,17 +1,9 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/DeferredEval.cs -namespace Rules.Framework.Evaluation.Classic -======== namespace Rules.Framework.Evaluation.Interpreted ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/DeferredEval.cs { using System; using System.Collections.Generic; using Rules.Framework.Core.ConditionNodes; -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/DeferredEval.cs - using Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers; -======== using Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers; ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/DeferredEval.cs internal sealed class DeferredEval : IDeferredEval { diff --git a/src/Rules.Framework/Evaluation/Interpreted/IDeferredEval.cs b/src/Rules.Framework/Evaluation/Interpreted/IDeferredEval.cs index 7696c443..7e6333dc 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/IDeferredEval.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/IDeferredEval.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/IDeferredEval.cs -namespace Rules.Framework.Evaluation.Classic -======== namespace Rules.Framework.Evaluation.Interpreted ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/IDeferredEval.cs { using System; using System.Collections.Generic; diff --git a/src/Rules.Framework/Evaluation/Interpreted/InterpretedConditionsEvalEngine.cs b/src/Rules.Framework/Evaluation/Interpreted/InterpretedConditionsEvalEngine.cs index 5ccbd26b..b0d04a2b 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/InterpretedConditionsEvalEngine.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/InterpretedConditionsEvalEngine.cs @@ -1,31 +1,17 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ClassicConditionsEvalEngine.cs -namespace Rules.Framework.Evaluation.Classic -======== namespace Rules.Framework.Evaluation.Interpreted ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/InterpretedConditionsEvalEngine.cs { using System; using System.Collections.Generic; using System.Linq; using Rules.Framework.Core; using Rules.Framework.Core.ConditionNodes; -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ClassicConditionsEvalEngine.cs - using Rules.Framework.Evaluation.Specification; - - internal sealed class ClassicConditionsEvalEngine : IConditionsEvalEngine -======== internal sealed class InterpretedConditionsEvalEngine : IConditionsEvalEngine ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/InterpretedConditionsEvalEngine.cs { private readonly IConditionsTreeAnalyzer conditionsTreeAnalyzer; private readonly IDeferredEval deferredEval; -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ClassicConditionsEvalEngine.cs - public ClassicConditionsEvalEngine( -======== public InterpretedConditionsEvalEngine( ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/InterpretedConditionsEvalEngine.cs IDeferredEval deferredEval, IConditionsTreeAnalyzer conditionsTreeAnalyzer) { diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategy.cs index 1a7be055..f6e34648 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategy.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategy.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategy.cs { using System; using System.Globalization; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategy.cs index b2cc89f1..b51817f4 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategy.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategy.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategy.cs { using System; using System.Globalization; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/ContainsOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/ContainsOperatorEvalStrategy.cs index cab7d355..9c890223 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/ContainsOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/ContainsOperatorEvalStrategy.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/ContainsOperatorEvalStrategy.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/ContainsOperatorEvalStrategy.cs { using System; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs index 5392925f..7cc820e3 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ConditionEvalDispatchProvider.cs { using System; using System.Collections.Generic; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs index 56cbbd9e..592c2577 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ConditionEvalDispatcherBase.cs { using System; using System.Collections; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/IConditionEvalDispatchProvider.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/IConditionEvalDispatchProvider.cs index f1571180..ac697806 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/IConditionEvalDispatchProvider.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/IConditionEvalDispatchProvider.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/IConditionEvalDispatchProvider.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/IConditionEvalDispatchProvider.cs { using Rules.Framework.Core; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/IConditionEvalDispatcher.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/IConditionEvalDispatcher.cs index f3249211..cde1ce2d 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/IConditionEvalDispatcher.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/IConditionEvalDispatcher.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/IConditionEvalDispatcher.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/IConditionEvalDispatcher.cs { using Rules.Framework.Core; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs index 9a2d2629..9b3462ec 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcher.cs { using System.Collections.Generic; using System.Linq; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs index ee4fd77b..44ca5841 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcher.cs { using System.Collections.Generic; using System.Linq; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs index 46da7924..af3998a8 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcher.cs { using System.Collections.Generic; using System.Linq; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs index ff666115..cd422b7f 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcher.cs { using Rules.Framework.Core; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/EndsWithOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/EndsWithOperatorEvalStrategy.cs index 6975f698..38b70219 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/EndsWithOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/EndsWithOperatorEvalStrategy.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/EndsWithOperatorEvalStrategy.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/EndsWithOperatorEvalStrategy.cs { using System; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/EqualOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/EqualOperatorEvalStrategy.cs index b3ea5ebe..c0f79fad 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/EqualOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/EqualOperatorEvalStrategy.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/EqualOperatorEvalStrategy.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/EqualOperatorEvalStrategy.cs { using System; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/GreaterThanOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/GreaterThanOperatorEvalStrategy.cs index 07c041d2..c23f7d84 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/GreaterThanOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/GreaterThanOperatorEvalStrategy.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/GreaterThanOperatorEvalStrategy.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/GreaterThanOperatorEvalStrategy.cs { using System; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategy.cs index 1945bf95..bb7fe3db 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategy.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategy.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategy.cs { using System; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IManyToManyOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IManyToManyOperatorEvalStrategy.cs index 7aede701..1d5911e0 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IManyToManyOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IManyToManyOperatorEvalStrategy.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/IManyToManyOperatorEvalStrategy.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IManyToManyOperatorEvalStrategy.cs { using System.Collections.Generic; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IManyToOneOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IManyToOneOperatorEvalStrategy.cs index 367b0e16..d3aa53e9 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IManyToOneOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IManyToOneOperatorEvalStrategy.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/IManyToOneOperatorEvalStrategy.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IManyToOneOperatorEvalStrategy.cs { using System.Collections.Generic; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IOneToManyOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IOneToManyOperatorEvalStrategy.cs index a2b6a1ab..c65c65b5 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IOneToManyOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IOneToManyOperatorEvalStrategy.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/IOneToManyOperatorEvalStrategy.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IOneToManyOperatorEvalStrategy.cs { using System.Collections.Generic; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IOneToOneOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IOneToOneOperatorEvalStrategy.cs index f57011fe..0c8b7a1d 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IOneToOneOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IOneToOneOperatorEvalStrategy.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/IOneToOneOperatorEvalStrategy.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IOneToOneOperatorEvalStrategy.cs { internal interface IOneToOneOperatorEvalStrategy { diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IOperatorEvalStrategyFactory.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IOperatorEvalStrategyFactory.cs index 1c6de66f..5610bd09 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IOperatorEvalStrategyFactory.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IOperatorEvalStrategyFactory.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/IOperatorEvalStrategyFactory.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/IOperatorEvalStrategyFactory.cs { using Rules.Framework.Core; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/InOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/InOperatorEvalStrategy.cs index 64218f53..c608d1d7 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/InOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/InOperatorEvalStrategy.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/InOperatorEvalStrategy.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/InOperatorEvalStrategy.cs { using System.Collections.Generic; using System.Linq; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/LesserThanOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/LesserThanOperatorEvalStrategy.cs index 33a71cb9..a4c65e82 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/LesserThanOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/LesserThanOperatorEvalStrategy.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/LesserThanOperatorEvalStrategy.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/LesserThanOperatorEvalStrategy.cs { using System; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategy.cs index 6b5dcbbb..3422f037 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategy.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategy.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategy.cs { using System; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotContainsOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotContainsOperatorEvalStrategy.cs index f1c32a5a..3cedc4e5 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotContainsOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotContainsOperatorEvalStrategy.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/NotContainsOperatorEvalStrategy.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotContainsOperatorEvalStrategy.cs { using System; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotEndsWithOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotEndsWithOperatorEvalStrategy.cs index f1c1dab9..dc8afd8f 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotEndsWithOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotEndsWithOperatorEvalStrategy.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/NotEndsWithOperatorEvalStrategy.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotEndsWithOperatorEvalStrategy.cs { using System; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotEqualOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotEqualOperatorEvalStrategy.cs index f16cb80c..9e301190 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotEqualOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotEqualOperatorEvalStrategy.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/NotEqualOperatorEvalStrategy.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotEqualOperatorEvalStrategy.cs { using System; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotInOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotInOperatorEvalStrategy.cs index fac9d4ff..7a42e5c2 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotInOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotInOperatorEvalStrategy.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/NotInOperatorEvalStrategy.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotInOperatorEvalStrategy.cs { using System.Collections.Generic; using System.Linq; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotStartsWithOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotStartsWithOperatorEvalStrategy.cs index b0a20ee9..a7a14fe8 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotStartsWithOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotStartsWithOperatorEvalStrategy.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/NotStartsWithOperatorEvalStrategy.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/NotStartsWithOperatorEvalStrategy.cs { using System; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/OperatorEvalStrategyFactory.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/OperatorEvalStrategyFactory.cs index e8b45f4d..6119f5cc 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/OperatorEvalStrategyFactory.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/OperatorEvalStrategyFactory.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/OperatorEvalStrategyFactory.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/OperatorEvalStrategyFactory.cs { using System; using System.Collections.Generic; diff --git a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/StartsWithOperatorEvalStrategy.cs b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/StartsWithOperatorEvalStrategy.cs index 2094b45f..9dfb18ac 100644 --- a/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/StartsWithOperatorEvalStrategy.cs +++ b/src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/StartsWithOperatorEvalStrategy.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:src/Rules.Framework/Evaluation/Classic/ValueEvaluation/StartsWithOperatorEvalStrategy.cs -namespace Rules.Framework.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:src/Rules.Framework/Evaluation/Interpreted/ValueEvaluation/StartsWithOperatorEvalStrategy.cs { using System; diff --git a/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario1/BodyMassIndexTests.cs b/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario1/BodyMassIndexTests.cs index 74bb3422..319ce00c 100644 --- a/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario1/BodyMassIndexTests.cs +++ b/tests/Rules.Framework.IntegrationTests/Scenarios/Scenario1/BodyMassIndexTests.cs @@ -146,9 +146,9 @@ public async Task AddRule_AddingNewRuleWithAgeConditionAtPriority1AndNewRuleAtPr var rules = await rulesDataSource.GetRulesByAsync(new RulesFilterArgs()).ConfigureAwait(false); rules.Should().NotBeNull().And.HaveCount(3); - rules.Should().ContainEquivalentOf(expectedRule1); - expectedRule1.Priority.Should().Be(1, "rule should to priority 1 if inserted at priority 1"); - expectedRule2.Priority.Should().Be(3, "rule should have priority 3 if inserted at priority 3, given that last rule after insert was at priority 2."); + rules.Should().ContainEquivalentOf(newRule1); + newRule1.Priority.Should().Be(1, "rule should to priority 1 if inserted at priority 1"); + newRule2.Priority.Should().Be(3, "rule should have priority 3 if inserted at priority 3, given that last rule after insert was at priority 2."); } [Theory] diff --git a/tests/Rules.Framework.Tests/Core/ConditionNodes/BooleanConditionNodeTests.cs b/tests/Rules.Framework.Tests/Core/ConditionNodes/BooleanConditionNodeTests.cs index c1eb6609..5351c9b1 100644 --- a/tests/Rules.Framework.Tests/Core/ConditionNodes/BooleanConditionNodeTests.cs +++ b/tests/Rules.Framework.Tests/Core/ConditionNodes/BooleanConditionNodeTests.cs @@ -64,35 +64,5 @@ public void Init_GivenSetupWithBooleanValue_ReturnsSettedValues() actualLogicalOperator.Should().Be(expectedLogicalOperator); actualDataType.Should().Be(expectedDataType); } - - [Fact] - public void Clone_NoConditions_ReturnsCloneInstance() - { - // Arrange - ConditionType expectedConditionType = ConditionType.IsoCountryCode; - Operators expectedOperator = Operators.NotEqual; - bool expectedOperand = false; - LogicalOperators expectedLogicalOperator = LogicalOperators.Eval; - DataTypes expectedDataType = DataTypes.Boolean; - - BooleanConditionNode sut = new BooleanConditionNode(expectedConditionType, expectedOperator, expectedOperand); - sut.Properties["test"] = "test"; - - // Act - IConditionNode actual = sut.Clone(); - - // Assert - actual.Should() - .NotBeNull() - .And - .BeOfType>(); - BooleanConditionNode valueConditionNode = actual.As>(); - valueConditionNode.ConditionType.Should().Be(expectedConditionType); - valueConditionNode.DataType.Should().Be(expectedDataType); - valueConditionNode.LogicalOperator.Should().Be(expectedLogicalOperator); - valueConditionNode.Operator.Should().Be(expectedOperator); - valueConditionNode.Operand.Should().Be(expectedOperand); - valueConditionNode.Properties.Should().BeEmpty(); - } } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Core/ConditionNodes/DecimalConditionNodeTests.cs b/tests/Rules.Framework.Tests/Core/ConditionNodes/DecimalConditionNodeTests.cs index 86423c7b..92d6e95f 100644 --- a/tests/Rules.Framework.Tests/Core/ConditionNodes/DecimalConditionNodeTests.cs +++ b/tests/Rules.Framework.Tests/Core/ConditionNodes/DecimalConditionNodeTests.cs @@ -64,35 +64,5 @@ public void Init_GivenSetupWithDecimalValue_ReturnsSettedValues() actualLogicalOperator.Should().Be(expectedLogicalOperator); actualDataType.Should().Be(expectedDataType); } - - [Fact] - public void Clone_NoConditions_ReturnsCloneInstance() - { - // Arrange - ConditionType expectedConditionType = ConditionType.PluviosityRate; - Operators expectedOperator = Operators.NotEqual; - decimal expectedOperand = 5682.2654m; - LogicalOperators expectedLogicalOperator = LogicalOperators.Eval; - DataTypes expectedDataType = DataTypes.Decimal; - - DecimalConditionNode sut = new DecimalConditionNode(expectedConditionType, expectedOperator, expectedOperand); - sut.Properties["test"] = "test"; - - // Act - IConditionNode actual = sut.Clone(); - - // Assert - actual.Should() - .NotBeNull() - .And - .BeOfType>(); - DecimalConditionNode valueConditionNode = actual.As>(); - valueConditionNode.ConditionType.Should().Be(expectedConditionType); - valueConditionNode.DataType.Should().Be(expectedDataType); - valueConditionNode.LogicalOperator.Should().Be(expectedLogicalOperator); - valueConditionNode.Operator.Should().Be(expectedOperator); - valueConditionNode.Operand.Should().Be(expectedOperand); - valueConditionNode.Properties.Should().BeEmpty(); - } } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Core/ConditionNodes/IntegerConditionNodeTests.cs b/tests/Rules.Framework.Tests/Core/ConditionNodes/IntegerConditionNodeTests.cs index 82b46f20..8fbe4b6e 100644 --- a/tests/Rules.Framework.Tests/Core/ConditionNodes/IntegerConditionNodeTests.cs +++ b/tests/Rules.Framework.Tests/Core/ConditionNodes/IntegerConditionNodeTests.cs @@ -64,35 +64,5 @@ public void Init_GivenSetupWithIntegerValue_ReturnsSettedValues() actualLogicalOperator.Should().Be(expectedLogicalOperator); actualDataType.Should().Be(expectedDataType); } - - [Fact] - public void Clone_NoConditions_ReturnsCloneInstance() - { - // Arrange - ConditionType expectedConditionType = ConditionType.IsoCountryCode; - Operators expectedOperator = Operators.NotEqual; - int expectedOperand = 1616; - LogicalOperators expectedLogicalOperator = LogicalOperators.Eval; - DataTypes expectedDataType = DataTypes.Integer; - - IntegerConditionNode sut = new IntegerConditionNode(expectedConditionType, expectedOperator, expectedOperand); - sut.Properties["test"] = "test"; - - // Act - IConditionNode actual = sut.Clone(); - - // Assert - actual.Should() - .NotBeNull() - .And - .BeOfType>(); - IntegerConditionNode valueConditionNode = actual.As>(); - valueConditionNode.ConditionType.Should().Be(expectedConditionType); - valueConditionNode.DataType.Should().Be(expectedDataType); - valueConditionNode.LogicalOperator.Should().Be(expectedLogicalOperator); - valueConditionNode.Operator.Should().Be(expectedOperator); - valueConditionNode.Operand.Should().Be(expectedOperand); - valueConditionNode.Properties.Should().BeEmpty(); - } } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Core/ConditionNodes/StringConditionNodeTests.cs b/tests/Rules.Framework.Tests/Core/ConditionNodes/StringConditionNodeTests.cs index 315b3f45..55727d71 100644 --- a/tests/Rules.Framework.Tests/Core/ConditionNodes/StringConditionNodeTests.cs +++ b/tests/Rules.Framework.Tests/Core/ConditionNodes/StringConditionNodeTests.cs @@ -64,35 +64,5 @@ public void Init_GivenSetupWithStringValue_ReturnsSettedValues() actualLogicalOperator.Should().Be(expectedLogicalOperator); actualDataType.Should().Be(expectedDataType); } - - [Fact] - public void Clone_NoConditions_ReturnsCloneInstance() - { - // Arrange - ConditionType expectedConditionType = ConditionType.IsoCountryCode; - Operators expectedOperator = Operators.NotEqual; - string expectedOperand = "Such operand, much wow."; - LogicalOperators expectedLogicalOperator = LogicalOperators.Eval; - DataTypes expectedDataType = DataTypes.String; - - StringConditionNode sut = new StringConditionNode(expectedConditionType, expectedOperator, expectedOperand); - sut.Properties["test"] = "test"; - - // Act - IConditionNode actual = sut.Clone(); - - // Assert - actual.Should() - .NotBeNull() - .And - .BeOfType>(); - StringConditionNode valueConditionNode = actual.As>(); - valueConditionNode.ConditionType.Should().Be(expectedConditionType); - valueConditionNode.DataType.Should().Be(expectedDataType); - valueConditionNode.LogicalOperator.Should().Be(expectedLogicalOperator); - valueConditionNode.Operator.Should().Be(expectedOperator); - valueConditionNode.Operand.Should().Be(expectedOperand); - valueConditionNode.Properties.Should().BeEmpty(); - } } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotContainsOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotContainsOperatorEvalStrategyTests.cs deleted file mode 100644 index 81349921..00000000 --- a/tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotContainsOperatorEvalStrategyTests.cs +++ /dev/null @@ -1,59 +0,0 @@ -namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation -{ - using System; - using FluentAssertions; - using Rules.Framework.Evaluation.Classic.ValueEvaluation; - using Xunit; - - public class NotContainsOperatorEvalStrategyTests - { - [Fact] - public void Eval_GivenIntegers1And2_ThrowsNotSupportedException() - { - // Arrange - var expectedLeftOperand = 1; - var expectedRightOperand = 2; - - var sut = new NotContainsOperatorEvalStrategy(); - - // Act - var notSupportedException = Assert.Throws(() => sut.Eval(expectedLeftOperand, expectedRightOperand)); - - // Arrange - notSupportedException.Should().NotBeNull(); - notSupportedException.Message.Should().Contain("System.Int32"); - } - - [Fact] - public void Eval_GivenStringsTheQuickBrownFoxJumpsOverTheLazyDogAndFox_ReturnsTrue() - { - // Arrange - var expectedLeftOperand = "The quick brown fox jumps over the lazy dog"; - var expectedRightOperand = "fox"; - - var sut = new NotContainsOperatorEvalStrategy(); - - // Act - var actual = sut.Eval(expectedLeftOperand, expectedRightOperand); - - // Arrange - actual.Should().BeFalse(); - } - - [Fact] - public void Eval_GivenStringsTheQuickBrownFoxJumpsOverTheLazyDogAndYellow_ReturnsFalse() - { - // Arrange - var expectedLeftOperand = "The quick brown fox jumps over the lazy dog"; - var expectedRightOperand = "yellow"; - - var sut = new NotContainsOperatorEvalStrategy(); - - // Act - var actual = sut.Eval(expectedLeftOperand, expectedRightOperand); - - // Arrange - actual.Should().BeTrue(); - } - } -} \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/DeferredEvalTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/DeferredEvalTests.cs index c598f5da..31da8be9 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/DeferredEvalTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/DeferredEvalTests.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/DeferredEvalTests.cs -namespace Rules.Framework.Tests.Evaluation.Classic -======== namespace Rules.Framework.Tests.Evaluation.Interpreted ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/DeferredEvalTests.cs { using System; using System.Collections.Generic; @@ -11,15 +7,9 @@ namespace Rules.Framework.Tests.Evaluation.Interpreted using Rules.Framework.Core; using Rules.Framework.Core.ConditionNodes; using Rules.Framework.Evaluation; -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/DeferredEvalTests.cs - using Rules.Framework.Evaluation.Classic; - using Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers; - using Rules.Framework.Tests.TestStubs; -======== using Rules.Framework.Evaluation.Interpreted; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers; using Rules.Framework.Tests.Stubs; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/DeferredEvalTests.cs using Xunit; public class DeferredEvalTests diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/InterpretedConditionsEvalEngineTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/InterpretedConditionsEvalEngineTests.cs index e0eaf7aa..8fc865db 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/InterpretedConditionsEvalEngineTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/InterpretedConditionsEvalEngineTests.cs @@ -1,8 +1,4 @@ -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ClassicConditionsEvalEngineTests.cs -namespace Rules.Framework.Tests.Evaluation.Classic -======== namespace Rules.Framework.Tests.Evaluation.Interpreted ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/InterpretedConditionsEvalEngineTests.cs { using System; using System.Collections.Generic; @@ -11,19 +7,11 @@ namespace Rules.Framework.Tests.Evaluation.Interpreted using Rules.Framework.Core; using Rules.Framework.Core.ConditionNodes; using Rules.Framework.Evaluation; -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ClassicConditionsEvalEngineTests.cs - using Rules.Framework.Evaluation.Classic; - using Rules.Framework.Tests.TestStubs; - using Xunit; - - public class ClassicConditionsEvalEngineTests -======== using Rules.Framework.Evaluation.Interpreted; using Rules.Framework.Tests.Stubs; using Xunit; public class InterpretedConditionsEvalEngineTests ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/InterpretedConditionsEvalEngineTests.cs { [Fact] public void Eval_GivenComposedConditionNodeWithAndOperatorAndMissingConditionWithSearchMode_EvalsAndReturnsResult() @@ -67,11 +55,7 @@ public void Eval_GivenComposedConditionNodeWithAndOperatorAndMissingConditionWit .Throws(new NotImplementedException("Shouldn't have gotten any more deferred evals.")); var conditionsTreeAnalyzer = Mock.Of>(); -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ClassicConditionsEvalEngineTests.cs - var sut = new ClassicConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); -======== var sut = new InterpretedConditionsEvalEngine(mockDeferredEval.Object, conditionsTreeAnalyzer); ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/InterpretedConditionsEvalEngineTests.cs // Act var actual = sut.Eval(composedConditionNode, conditions, evaluationOptions); @@ -113,11 +97,7 @@ public void Eval_GivenComposedConditionNodeWithAndOperatorWithExactMatch_EvalsAn var deferredEval = Mock.Of(); var conditionsTreeAnalyzer = Mock.Of>(); -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ClassicConditionsEvalEngineTests.cs - var sut = new ClassicConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); -======== var sut = new InterpretedConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/InterpretedConditionsEvalEngineTests.cs // Act var notSupportedException = Assert.Throws(() => sut.Eval(composedConditionNode, conditions, evaluationOptions)); @@ -160,11 +140,7 @@ public void Eval_GivenComposedConditionNodeWithEvalOperator_ThrowsNotSupportedEx var deferredEval = Mock.Of(); var conditionsTreeAnalyzer = Mock.Of>(); -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ClassicConditionsEvalEngineTests.cs - var sut = new ClassicConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); -======== var sut = new InterpretedConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/InterpretedConditionsEvalEngineTests.cs // Act var notSupportedException = Assert.Throws(() => sut.Eval(composedConditionNode, conditions, evaluationOptions)); @@ -218,11 +194,7 @@ public void Eval_GivenComposedConditionNodeWithOrOperatorWithExactMatch_EvalsAnd .Throws(new NotImplementedException("Shouldn't have gotten any more deferred evals.")); var conditionsTreeAnalyzer = Mock.Of>(); -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ClassicConditionsEvalEngineTests.cs - var sut = new ClassicConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); -======== var sut = new InterpretedConditionsEvalEngine(deferredEval, conditionsTreeAnalyzer); ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/InterpretedConditionsEvalEngineTests.cs // Act var actual = sut.Eval(composedConditionNode, conditions, evaluationOptions); diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategyTests.cs index af4390f9..50bb5e22 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategyTests.cs @@ -1,16 +1,8 @@ -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategyTests.cs -namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation -{ - using System; - using FluentAssertions; - using Rules.Framework.Evaluation.Classic.ValueEvaluation; -======== namespace Rules.Framework.Tests.Evaluation.Interpreted.ValueEvaluation { using System; using FluentAssertions; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/CaseInsensitiveEndsWithOperatorEvalStrategyTests.cs using Xunit; public class CaseInsensitiveEndsWithOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategyTests.cs index ca8be952..c512d66f 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategyTests.cs @@ -1,16 +1,8 @@ -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategyTests.cs -namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation -{ - using System; - using FluentAssertions; - using Rules.Framework.Evaluation.Classic.ValueEvaluation; -======== namespace Rules.Framework.Tests.Evaluation.Interpreted.ValueEvaluation { using System; using FluentAssertions; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/CaseInsensitiveStartsWithOperatorEvalStrategyTests.cs using Xunit; public class CaseInsensitiveStartsWithOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/ContainsOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/ContainsOperatorEvalStrategyTests.cs index 56b3ab58..5ca93263 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/ContainsOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/ContainsOperatorEvalStrategyTests.cs @@ -1,15 +1,8 @@ -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/ContainsOperatorEvalStrategyTests.cs -namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation -{ - using FluentAssertions; - using Rules.Framework.Evaluation.Classic.ValueEvaluation; -======== namespace Rules.Framework.Tests.Evaluation.Interpreted.ValueEvaluation { + using System; using FluentAssertions; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/ContainsOperatorEvalStrategyTests.cs - using System; using Xunit; public class ContainsOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcherTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcherTests.cs index 6713e7e8..489c3be2 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcherTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcherTests.cs @@ -7,13 +7,8 @@ namespace Rules.Framework.Tests.Evaluation.ValueEvaluation.Dispatchers using Moq; using Rules.Framework.Core; using Rules.Framework.Evaluation; -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcherTests.cs - using Rules.Framework.Evaluation.Classic.ValueEvaluation; - using Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers; -======== using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToManyConditionEvalDispatcherTests.cs using Xunit; public class ManyToManyConditionEvalDispatcherTests diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcherTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcherTests.cs index 3d343799..71f07e9d 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcherTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcherTests.cs @@ -7,13 +7,8 @@ namespace Rules.Framework.Tests.Evaluation.ValueEvaluation.Dispatchers using Moq; using Rules.Framework.Core; using Rules.Framework.Evaluation; -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcherTests.cs - using Rules.Framework.Evaluation.Classic.ValueEvaluation; - using Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers; -======== using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/ManyToOneConditionEvalDispatcherTests.cs using Xunit; public class ManyToOneConditionEvalDispatcherTests diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcherTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcherTests.cs index 2ceb69b4..28dacd55 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcherTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcherTests.cs @@ -6,13 +6,8 @@ namespace Rules.Framework.Tests.Evaluation.ValueEvaluation.Dispatchers using Moq; using Rules.Framework.Core; using Rules.Framework.Evaluation; -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcherTests.cs - using Rules.Framework.Evaluation.Classic.ValueEvaluation; - using Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers; -======== using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToManyConditionEvalDispatcherTests.cs using Xunit; public class OneToManyConditionEvalDispatcherTests diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcherTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcherTests.cs index a605a959..57f9cddd 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcherTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcherTests.cs @@ -5,13 +5,8 @@ namespace Rules.Framework.Tests.Evaluation.ValueEvaluation.Dispatchers using Moq; using Rules.Framework.Core; using Rules.Framework.Evaluation; -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcherTests.cs - using Rules.Framework.Evaluation.Classic.ValueEvaluation; - using Rules.Framework.Evaluation.Classic.ValueEvaluation.Dispatchers; -======== using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation.Dispatchers; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/Dispatchers/OneToOneConditionEvalDispatcherTests.cs using Xunit; public class OneToOneConditionEvalDispatcherTests diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/EndsWithOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/EndsWithOperatorEvalStrategyTests.cs index 8c3e7477..d912365e 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/EndsWithOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/EndsWithOperatorEvalStrategyTests.cs @@ -1,15 +1,8 @@ -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/EndsWithOperatorEvalStrategyTests.cs -namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation -{ - using FluentAssertions; - using Rules.Framework.Evaluation.Classic.ValueEvaluation; -======== namespace Rules.Framework.Tests.Evaluation.Interpreted.ValueEvaluation { + using System; using FluentAssertions; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/EndsWithOperatorEvalStrategyTests.cs - using System; using Xunit; public class EndsWithOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/EqualOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/EqualOperatorEvalStrategyTests.cs index 8411c541..06503012 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/EqualOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/EqualOperatorEvalStrategyTests.cs @@ -1,17 +1,9 @@ -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/EqualOperatorEvalStrategyTests.cs -namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Tests.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/EqualOperatorEvalStrategyTests.cs { using System; using System.Collections.Generic; using FluentAssertions; -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/EqualOperatorEvalStrategyTests.cs - using Rules.Framework.Evaluation.Classic.ValueEvaluation; -======== using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/EqualOperatorEvalStrategyTests.cs using Xunit; public class EqualOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/GreaterThanOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/GreaterThanOperatorEvalStrategyTests.cs index 0d16271a..684f4f11 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/GreaterThanOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/GreaterThanOperatorEvalStrategyTests.cs @@ -1,17 +1,9 @@ -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/GreaterThanOperatorEvalStrategyTests.cs -namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Tests.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/GreaterThanOperatorEvalStrategyTests.cs { using System; using System.Collections.Generic; using FluentAssertions; -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/GreaterThanOperatorEvalStrategyTests.cs - using Rules.Framework.Evaluation.Classic.ValueEvaluation; -======== using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/GreaterThanOperatorEvalStrategyTests.cs using Xunit; public class GreaterThanOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategyTests.cs index b1ea5aba..1a8eb196 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategyTests.cs @@ -1,17 +1,9 @@ -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategyTests.cs -namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Tests.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategyTests.cs { using System; using System.Collections.Generic; using FluentAssertions; -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategyTests.cs - using Rules.Framework.Evaluation.Classic.ValueEvaluation; -======== using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/GreaterThanOrEqualOperatorEvalStrategyTests.cs using Xunit; public class GreaterThanOrEqualOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/InOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/InOperatorEvalStrategyTests.cs index 84b54911..62a569cd 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/InOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/InOperatorEvalStrategyTests.cs @@ -1,17 +1,9 @@ -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/InOperatorEvalStrategyTests.cs -namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Tests.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/InOperatorEvalStrategyTests.cs { using System.Collections.Generic; using System.Linq; using FluentAssertions; -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/InOperatorEvalStrategyTests.cs - using Rules.Framework.Evaluation.Classic.ValueEvaluation; -======== using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/InOperatorEvalStrategyTests.cs using Xunit; public class InOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/LesserThanOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/LesserThanOperatorEvalStrategyTests.cs index 5402ea03..0c468e23 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/LesserThanOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/LesserThanOperatorEvalStrategyTests.cs @@ -1,17 +1,9 @@ -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/LesserThanOperatorEvalStrategyTests.cs -namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Tests.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/LesserThanOperatorEvalStrategyTests.cs { using System; using System.Collections.Generic; using FluentAssertions; -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/LesserThanOperatorEvalStrategyTests.cs - using Rules.Framework.Evaluation.Classic.ValueEvaluation; -======== using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/LesserThanOperatorEvalStrategyTests.cs using Xunit; public class LesserThanOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategyTests.cs index d4ffdaa5..1b90b94c 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategyTests.cs @@ -1,17 +1,9 @@ -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategyTests.cs -namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Tests.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategyTests.cs { using System; using System.Collections.Generic; using FluentAssertions; -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategyTests.cs - using Rules.Framework.Evaluation.Classic.ValueEvaluation; -======== using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/LesserThanOrEqualOperatorEvalStrategyTests.cs using Xunit; public class LesserThanOrEqualOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotEndsWithOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotEndsWithOperatorEvalStrategyTests.cs index 6864dc95..e723c9d3 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotEndsWithOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotEndsWithOperatorEvalStrategyTests.cs @@ -1,11 +1,7 @@ namespace Rules.Framework.Tests.Evaluation.ValueEvaluation { using System; -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotEndsWithOperatorEvalStrategyTests.cs - using Rules.Framework.Evaluation.Classic.ValueEvaluation; -======== using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotEndsWithOperatorEvalStrategyTests.cs using Xunit; public class NotEndsWithOperatorEvalStrategyTests @@ -30,18 +26,6 @@ public void Eval_GivenInvalidOperandCombination_ThrowsNotSupportedOperation(obje Assert.Contains(nameof(String), exception.Message); } - [Theory] - [InlineData("Sample text", "random")] - [InlineData("Sample text", "Text")] - public void Eval_GivenLeftOperandNotEndingWithRightOperand_ReturnsTrue(string leftOperand, string rightOperand) - { - // Act - var evaluation = evalStrategy.Eval(leftOperand, rightOperand); - - // Assert - Assert.True(evaluation); - } - [Fact] public void Eval_GivenLeftOperandEndingWithRightOperand_ReturnsFalse() { @@ -55,5 +39,17 @@ public void Eval_GivenLeftOperandEndingWithRightOperand_ReturnsFalse() // Assert Assert.False(evaluation); } + + [Theory] + [InlineData("Sample text", "random")] + [InlineData("Sample text", "Text")] + public void Eval_GivenLeftOperandNotEndingWithRightOperand_ReturnsTrue(string leftOperand, string rightOperand) + { + // Act + var evaluation = evalStrategy.Eval(leftOperand, rightOperand); + + // Assert + Assert.True(evaluation); + } } } \ No newline at end of file diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotEqualOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotEqualOperatorEvalStrategyTests.cs index 1e74ae58..253812c6 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotEqualOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotEqualOperatorEvalStrategyTests.cs @@ -1,17 +1,9 @@ -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotEqualOperatorEvalStrategyTests.cs -namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Tests.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotEqualOperatorEvalStrategyTests.cs { using System; using System.Collections.Generic; using FluentAssertions; -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotEqualOperatorEvalStrategyTests.cs - using Rules.Framework.Evaluation.Classic.ValueEvaluation; -======== using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotEqualOperatorEvalStrategyTests.cs using Xunit; public class NotEqualOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotInOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotInOperatorEvalStrategyTests.cs index 96877fc7..ac8d7abe 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotInOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotInOperatorEvalStrategyTests.cs @@ -1,16 +1,8 @@ -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotInOperatorEvalStrategyTests.cs -namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation -{ - using System.Linq; - using FluentAssertions; - using Rules.Framework.Evaluation.Classic.ValueEvaluation; -======== namespace Rules.Framework.Tests.Evaluation.Interpreted.ValueEvaluation { using System.Linq; using FluentAssertions; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotInOperatorEvalStrategyTests.cs using Xunit; public class NotInOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotStartsWithOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotStartsWithOperatorEvalStrategyTests.cs index 1c6d3bc3..1918de0e 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotStartsWithOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotStartsWithOperatorEvalStrategyTests.cs @@ -1,11 +1,7 @@ namespace Rules.Framework.Tests.Evaluation.ValueEvaluation { using System; -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/NotStartsWithOperatorEvalStrategyTests.cs - using Rules.Framework.Evaluation.Classic.ValueEvaluation; -======== using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/NotStartsWithOperatorEvalStrategyTests.cs using Xunit; public class NotStartsWithOperatorEvalStrategyTests diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/OperatorEvalStrategyFactoryTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/OperatorEvalStrategyFactoryTests.cs index 8a43cc10..24c6877c 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/OperatorEvalStrategyFactoryTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/OperatorEvalStrategyFactoryTests.cs @@ -1,17 +1,9 @@ -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/OperatorEvalStrategyFactoryTests.cs -namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation -======== namespace Rules.Framework.Tests.Evaluation.Interpreted.ValueEvaluation ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/OperatorEvalStrategyFactoryTests.cs { using System; using FluentAssertions; using Rules.Framework.Core; -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/OperatorEvalStrategyFactoryTests.cs - using Rules.Framework.Evaluation.Classic.ValueEvaluation; -======== using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/OperatorEvalStrategyFactoryTests.cs using Xunit; public class OperatorEvalStrategyFactoryTests diff --git a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/StartsWithOperatorEvalStrategyTests.cs b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/StartsWithOperatorEvalStrategyTests.cs index 7d87e2d5..68e7c3a8 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/StartsWithOperatorEvalStrategyTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/StartsWithOperatorEvalStrategyTests.cs @@ -1,15 +1,8 @@ -<<<<<<<< HEAD:tests/Rules.Framework.Tests/Evaluation/Classic/ValueEvaluation/StartsWithOperatorEvalStrategyTests.cs -namespace Rules.Framework.Tests.Evaluation.Classic.ValueEvaluation -{ - using FluentAssertions; - using Rules.Framework.Evaluation.Classic.ValueEvaluation; -======== namespace Rules.Framework.Tests.Evaluation.Interpreted.ValueEvaluation { + using System; using FluentAssertions; using Rules.Framework.Evaluation.Interpreted.ValueEvaluation; ->>>>>>>> master:tests/Rules.Framework.Tests/Evaluation/Interpreted/ValueEvaluation/StartsWithOperatorEvalStrategyTests.cs - using System; using Xunit; public class StartsWithOperatorEvalStrategyTests From 52698a1296470f510a2cc22f4fb89e5da34ca725 Mon Sep 17 00:00:00 2001 From: Luis Garces Date: Mon, 10 Apr 2023 19:07:35 +0100 Subject: [PATCH 54/54] chore: minor fixes to files --- tests/Rules.Framework.BenchmarkTests/Program.cs | 14 +++++++------- .../ConditionExpressionBuilderProviderTests.cs | 5 ----- .../Evaluation/DataTypeConfigurationTests.cs | 5 ----- .../DataTypesConfigurationProviderTests.cs | 3 --- .../Evaluation/EvaluationOptionsTests.cs | 5 ----- 5 files changed, 7 insertions(+), 25 deletions(-) diff --git a/tests/Rules.Framework.BenchmarkTests/Program.cs b/tests/Rules.Framework.BenchmarkTests/Program.cs index b3a52f9f..879d8585 100644 --- a/tests/Rules.Framework.BenchmarkTests/Program.cs +++ b/tests/Rules.Framework.BenchmarkTests/Program.cs @@ -26,18 +26,18 @@ private static int Main(string[] args) app.OnExecute(() => { - Console.WriteLine("Starting benchmark tests."); - Console.WriteLine(); + Console.WriteLine("Starting benchmark tests."); + Console.WriteLine(); - var manualConfig = ManualConfig.CreateMinimumViable(); - manualConfig.AddDiagnoser(MemoryDiagnoser.Default); + var manualConfig = ManualConfig.CreateMinimumViable(); + manualConfig.AddDiagnoser(MemoryDiagnoser.Default); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - manualConfig.AddHardwareCounters(HardwareCounter.BranchInstructions, HardwareCounter.BranchMispredictions); + manualConfig.AddHardwareCounters(HardwareCounter.BranchInstructions, HardwareCounter.BranchMispredictions); } - manualConfig.AddExporter(HtmlExporter.Default); + manualConfig.AddExporter(HtmlExporter.Default); manualConfig.AddExporter(CustomMarkdownExporter.Default); manualConfig.WithOption(ConfigOptions.JoinSummary, true); @@ -51,7 +51,7 @@ private static int Main(string[] args) bc => bc.Parameters.Items.Any(p => p.Name == "EnableCompilation" && (bool)p.Value == false)); manualConfig.AddColumn(column); - _ = BenchmarkRunner.Run(typeof(Program).Assembly, manualConfig); + _ = BenchmarkRunner.Run(typeof(Program).Assembly, manualConfig); }); return app.Execute(args); diff --git a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProviderTests.cs b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProviderTests.cs index 9737ccfd..c55b877d 100644 --- a/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProviderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/Compiled/ConditionBuilders/ConditionExpressionBuilderProviderTests.cs @@ -7,11 +7,6 @@ namespace Rules.Framework.Tests.Evaluation.Compiled.ConditionBuilders using Rules.Framework.Core; using Rules.Framework.Evaluation; using Rules.Framework.Evaluation.Compiled.ConditionBuilders; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; using Xunit; public class ConditionExpressionBuilderProviderTests diff --git a/tests/Rules.Framework.Tests/Evaluation/DataTypeConfigurationTests.cs b/tests/Rules.Framework.Tests/Evaluation/DataTypeConfigurationTests.cs index 69c22c8a..5918400c 100644 --- a/tests/Rules.Framework.Tests/Evaluation/DataTypeConfigurationTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/DataTypeConfigurationTests.cs @@ -4,11 +4,6 @@ namespace Rules.Framework.Tests.Evaluation using FluentAssertions; using Rules.Framework.Core; using Rules.Framework.Evaluation; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; using Xunit; public class DataTypeConfigurationTests diff --git a/tests/Rules.Framework.Tests/Evaluation/DataTypesConfigurationProviderTests.cs b/tests/Rules.Framework.Tests/Evaluation/DataTypesConfigurationProviderTests.cs index b68c7f0f..3acffd43 100644 --- a/tests/Rules.Framework.Tests/Evaluation/DataTypesConfigurationProviderTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/DataTypesConfigurationProviderTests.cs @@ -6,9 +6,6 @@ namespace Rules.Framework.Tests.Evaluation using FluentAssertions; using Rules.Framework.Core; using Rules.Framework.Evaluation; - using System; - using System.Collections.Generic; - using System.Linq; using Xunit; public class DataTypesConfigurationProviderTests diff --git a/tests/Rules.Framework.Tests/Evaluation/EvaluationOptionsTests.cs b/tests/Rules.Framework.Tests/Evaluation/EvaluationOptionsTests.cs index b895a322..577affd6 100644 --- a/tests/Rules.Framework.Tests/Evaluation/EvaluationOptionsTests.cs +++ b/tests/Rules.Framework.Tests/Evaluation/EvaluationOptionsTests.cs @@ -4,11 +4,6 @@ namespace Rules.Framework.Tests.Evaluation using System.Linq; using FluentAssertions; using Rules.Framework.Evaluation; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; using Xunit; public class EvaluationOptionsTests