diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index be4914d5..78a92b4b 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -35,4 +35,52 @@ jobs: run: | reportgenerator -reports:"../../coverage-outputs/**/*.xml" -targetdir:"../../coverage-outputs" -reporttypes:Cobertura rm -rfv ../../coverage-outputs/*/ - ls -la ../../coverage-outputs \ No newline at end of file + ls -la ../../coverage-outputs + + benchmarks: + runs-on: ubuntu-latest + steps: + - name: Determine folders + id: determine-folders + run: | + HEAD_FOLDER=$(pwd) + HEAD_BENCH_FOLDER=$HEAD_FOLDER/tmp/benchmarks/head + echo "head_folder=$HEAD_FOLDER" >> $GITHUB_OUTPUT + echo "head_benchmarks_folder=$HEAD_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 --framework net6.0 + - 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: Determine results file (HEAD) + id: determine-results-file + run: | + cd ${{ steps.determine-folders.outputs.head_benchmarks_folder }} + ls -lRA + 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 + # Tries to find a comment ID. + - 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 + # If a prior comment ID is not found, creates a new comment, otherwise updates the existent one. + - 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 }} + edit-mode: replace \ No newline at end of file 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/README.md b/README.md index cabdedfa..d76c65e4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Rules.Framework is a generic rules framework that allows defining and evaluating Why use rules? Most of us at some point, while developing software to support a business, have come across fast paced business logic changes. Sometimes, business needs change overnight, which requires a fast response to changes by engineering teams. By using rules, changing a calculus formula, a value mapping or simply a toggle configuration no longer requires code changes/endless CI/CD pipelines, QA validation, and so on... Business logic changes can be offloaded to configuration scenarios, instead of development scenarios. -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/8b48f4541fba4d4b8bad2e9a8563ede3)](https://app.codacy.com/gh/Farfetch/rules-framework?utm_source=github.com&utm_medium=referral&utm_content=Farfetch/rules-framework&utm_campaign=Badge_Grade_Settings) +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/8b48f4541fba4d4b8bad2e9a8563ede3)](https://app.codacy.com/gh/Farfetch/rules-framework?utm_source=github.com\&utm_medium=referral\&utm_content=Farfetch/rules-framework\&utm_campaign=Badge_Grade_Settings) [![.NET build](https://github.com/luispfgarces/rules-framework/actions/workflows/dotnet-build.yml/badge.svg)](https://github.com/luispfgarces/rules-framework/actions/workflows/dotnet-build.yml) ## Packages @@ -13,21 +13,22 @@ Why use rules? Most of us at some point, while developing software to support a |---------------------------------|----|---------| |Rules.Framework|[![Nuget Package](https://img.shields.io/nuget/v/Rules.Framework.svg?logo=nuget)](https://www.nuget.org/packages/Rules.Framework/)|[![Rules.Framework on fuget.org](https://www.fuget.org/packages/Rules.Framework/badge.svg)](https://www.fuget.org/packages/Rules.Framework)| |Rules.Framework.Providers.MongoDb|[![Nuget Package](https://img.shields.io/nuget/v/Rules.Framework.Providers.MongoDb?logo=nuget)](https://www.nuget.org/packages/Rules.Framework.Providers.MongoDb/)|[![Rules.Framework.Providers.MongoDb on fuget.org](https://www.fuget.org/packages/Rules.Framework.Providers.MongoDb/badge.svg)](https://www.fuget.org/packages/Rules.Framework.Providers.MongoDb)| -|Rules.Framework.Providers.InMemory|[![Nuget Package](https://img.shields.io/nuget/v/Rules.Framework.Providers.InMemory?logo=nuget)](https://www.nuget.org/packages/Rules.Framework.Providers.InMemory/)|[![Rules.Framework.Providers.InMemory on fuget.org](https://www.fuget.org/packages/Rules.Framework.Providers.InMemory/badge.svg)](https://www.fuget.org/packages/Rules.Framework.Providers.InMemory)| +|Rules.Framework.WebUI|[![Nuget Package](https://img.shields.io/nuget/v/Rules.Framework.WebUI?logo=nuget)](https://www.nuget.org/packages/Rules.Framework.WebUI/)|[![Rules.Framework.WebUI on fuget.org](https://www.fuget.org/packages/Rules.Framework.WebUI/badge.svg)](https://www.fuget.org/packages/Rules.Framework.WebUI)| ## Features The following listing presents features implemented and features to be implemented: -- [x] Rules evaluation (match one) -- [x] Rules evaluation (match many) -- [x] Rules search -- [x] Rules content serializarion -- [ ] Rules data source caching -- [x] Rules management (Create, Read, Update) -- [X] In-memory data source support -- [x] MongoDB data source support -- [ ] SQL Server data source support +* [x] Rules evaluation (match one) +* [x] Rules evaluation (match many) +* [x] Rules search +* [x] Rules content serializarion +* [ ] Rules data source caching +* [x] Rules management (Create, Read, Update) +* [x] In-memory data source support +* [x] MongoDB data source support +* [ ] SQL Server data source support +* [x] Rules evaluation modes (interpreted, compiled) ## How it works @@ -37,12 +38,12 @@ Starting with the basics, what are we considering a rule? For Rules.Framework, a valid rule accounts for the following conditions: -- Categorized by a **content type**, which groups rules by those that will be evaluated together. Rules from different content types won't be evaluated together. Content type is a user defined type, which can be a value type or a object, depending on the requirements of usage. -- Has a **name**, which must be unique by content type. -- Is constrained in time by a **date begin** and a **date end**. Date begin must be always set, and date end can be null (meaning that rule is applied from date begin to _ad eternum_). Please note that date begin threshold is inclusive and date end threshold is exclusive, so if you define a rule with date begin as "2020-01-01" and date end as "2021-01-01", if evaluation date is set to "2020-01-01", rule will match, but if evaluation date is set to "2021-01-01", rule will not match. -- Has a **priority** numeric value, which works as tiebreaker when many rules match on rules interval and given input conditions. Rules.Framework has the ability to configure if tiebreaker criteria is set to highest priority value or lowest priority value. This value must always be positive. -- Also has a set of **conditions** disposed in tree. Conditions can be set combined by AND/OR operators and by using comparison operators to compare values set on rule (integer, boolean, string or decimal) to input conditions. Conditions are categorized by a condition type, which must be one of the user-defined types (either value types or objects). -- And a **content** defined by user and totally up to the user to validate it (can virtually be anything the user wants, as long as the persistence mechanism used as data source supports it). +* Categorized by a **content type**, which groups rules by those that will be evaluated together. Rules from different content types won't be evaluated together. Content type is a user defined type, which can be a value type or a object, depending on the requirements of usage. +* Has a **name**, which must be unique by content type. +* Is constrained in time by a **date begin** and a **date end**. Date begin must be always set, and date end can be null (meaning that rule is applied from date begin to *ad eternum*). Please note that date begin threshold is inclusive and date end threshold is exclusive, so if you define a rule with date begin as "2020-01-01" and date end as "2021-01-01", if evaluation date is set to "2020-01-01", rule will match, but if evaluation date is set to "2021-01-01", rule will not match. +* Has a **priority** numeric value, which works as tiebreaker when many rules match on rules interval and given input conditions. Rules.Framework has the ability to configure if tiebreaker criteria is set to highest priority value or lowest priority value. This value must always be positive. +* Also has a set of **conditions** disposed in tree. Conditions can be set combined by AND/OR operators and by using comparison operators to compare values set on rule (integer, boolean, string or decimal) to input conditions. Conditions are categorized by a condition type, which must be one of the user-defined types (either value types or objects). +* And a **content** defined by user and totally up to the user to validate it (can virtually be anything the user wants, as long as the persistence mechanism used as data source supports it). Bellow you can see a simple sample for demonstration purposes: @@ -50,11 +51,11 @@ Bellow you can see a simple sample for demonstration purposes: The sample rule presented: -- Is described by it's name as "Body Mass default formula" - a simple human-readable description. -- Has a content type "Body Mass formula" that categorizes it. -- Begins at 1st January 2018 and never ends - which means that requesting on a date before 1st January 2018, rule is not matched, but after midnight at the same date, the rule will match. -- Priority is set to 1. This would be used as tiebreaker criteria if there were more rules defined, but since there's only one rule, there's no difference on evaluation. -- Rule has no conditions defined - which means, requesting on a date on rule dates range, it will always match. +* Is described by it's name as "Body Mass default formula" - a simple human-readable description. +* Has a content type "Body Mass formula" that categorizes it. +* Begins at 1st January 2018 and never ends - which means that requesting on a date before 1st January 2018, rule is not matched, but after midnight at the same date, the rule will match. +* Priority is set to 1. This would be used as tiebreaker criteria if there were more rules defined, but since there's only one rule, there's no difference on evaluation. +* Rule has no conditions defined - which means, requesting on a date on rule dates range, it will always match. Simple right? You got the basics covered, let's complicate this a bit by adding a new rule. The formula you saw on the first rule is used to calculate body mass when using kilograms and meters unit of measures, but what if we wanted to calculate using pounds and inches? Let's define a new rule for this: @@ -62,11 +63,11 @@ Simple right? You got the basics covered, let's complicate this a bit by adding Newly defined rule (Rule #2): -- Becomes the rule with priority 1. -- Defines a new formula. -- Defines a composed condition node specifying that a AND logical operator must be applied between child nodes conditions results. -- Defines a condition node with data type string, having a condition type of "Mass unit of measure", operator equal and operand "pounds". -- Defines a second condition node with data type string, having a condition type of "Height unit of measure", operator equal and operand "inches". +* Becomes the rule with priority 1. +* Defines a new formula. +* Defines a composed condition node specifying that a AND logical operator must be applied between child nodes conditions results. +* Defines a condition node with data type string, having a condition type of "Mass unit of measure", operator equal and operand "pounds". +* Defines a second condition node with data type string, having a condition type of "Height unit of measure", operator equal and operand "inches". If you request a rule for the content type "Body Mass formula" by specifying date 2019-01-01, "Mass unit of measure" as "pounds" and "Height unit of measure" as "inches", both rules will match (remember that Rule #1 has no conditions, so it matches anything). At this point is where priority is used to select the right one (by default, lowest priority values win to highest values, but this is configurable), so Rule #2 is chosen. @@ -91,4 +92,4 @@ Head over to [CONTRIBUTING](CONTRIBUTING.md) for further details. ## License -[MIT License](LICENSE.md) \ No newline at end of file +[MIT License](LICENSE.md) diff --git a/run-benchmarks.ps1 b/run-benchmarks.ps1 new file mode 100644 index 00000000..f3e03a0b --- /dev/null +++ b/run-benchmarks.ps1 @@ -0,0 +1,55 @@ +param ([switch] $KeepBenchmarksFiles) + +$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 artifacts/results -File -Filter *.md +if ($filteredResultsFiles) { + $resultsFile = $filteredResultsFiles.Name + + # Copy results file + Copy-Item -Path artifacts/results/$resultsFile -Destination . + + # 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 artifacts -Recurse > $null + } + if ($directoryFound = Get-ChildItem -Path $reportDir -Directory | Select-String -Pattern "bin") { + Remove-Item -Path bin -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/DataSource/ConditionNodeDataModel.cs b/tests/Rules.Framework.BenchmarkTests/DataSource/ConditionNodeDataModel.cs deleted file mode 100644 index d6c890f6..00000000 --- a/tests/Rules.Framework.BenchmarkTests/DataSource/ConditionNodeDataModel.cs +++ /dev/null @@ -1,19 +0,0 @@ -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 deleted file mode 100644 index 7592ccc2..00000000 --- a/tests/Rules.Framework.BenchmarkTests/DataSource/RuleDataModel.cs +++ /dev/null @@ -1,21 +0,0 @@ -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/Exporters/Common/BenchmarkReport.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Common/BenchmarkReport.cs new file mode 100644 index 00000000..84ebe067 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Common/BenchmarkReport.cs @@ -0,0 +1,17 @@ +namespace Rules.Framework.BenchmarkTests.Exporters.Common +{ + 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/Common/BenchmarkStatisticsComparisonItem.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Common/BenchmarkStatisticsComparisonItem.cs new file mode 100644 index 00000000..bbfe6f6d --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Common/BenchmarkStatisticsComparisonItem.cs @@ -0,0 +1,23 @@ +namespace Rules.Framework.BenchmarkTests.Exporters.Common +{ + internal class BenchmarkStatisticsComparisonItem + { + public BenchmarkStatisticsValue? AllocatedMemoryRate { get; set; } + + public BenchmarkStatisticsValue? BaselineAllocatedMemory { get; set; } + + public BenchmarkStatisticsValue? BaselineMeanTimeTaken { get; set; } + + public string? BaselineParameters { get; set; } + + public BenchmarkStatisticsValue? CompareAllocatedMemory { get; set; } + + public BenchmarkStatisticsValue? CompareMeanTimeTaken { get; set; } + + public string? CompareParameters { get; set; } + + public string? Key { get; set; } + + public BenchmarkStatisticsValue? MeanTimeTakenCompareRate { get; set; } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Exporters/Common/BenchmarkStatisticsItem.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Common/BenchmarkStatisticsItem.cs new file mode 100644 index 00000000..9dca5ffa --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Common/BenchmarkStatisticsItem.cs @@ -0,0 +1,65 @@ +namespace Rules.Framework.BenchmarkTests.Exporters.Common +{ + internal class BenchmarkStatisticsItem + { + public BenchmarkStatisticsItem( + string key, + string parameters, + string baseline, + BenchmarkStatisticsValue meanTimeTaken, + BenchmarkStatisticsValue standardError, + BenchmarkStatisticsValue branchInstructionsPerOp, + BenchmarkStatisticsValue branchMispredictionsPerOp, + BenchmarkStatisticsValue gen0Collects, + BenchmarkStatisticsValue allocatedMemory) + { + this.Key = key; + this.Parameters = parameters; + this.Baseline = baseline; + this.MeanTimeTaken = meanTimeTaken; + this.StandardError = standardError; + this.BranchInstructionsPerOp = branchInstructionsPerOp; + this.BranchMispredictionsPerOp = branchMispredictionsPerOp; + this.Gen0Collects = gen0Collects; + this.AllocatedMemory = allocatedMemory; + } + + public BenchmarkStatisticsValue AllocatedMemory { get; set; } + + public string Baseline { get; set; } + + public BenchmarkStatisticsValue BranchInstructionsPerOp { get; set; } + + public BenchmarkStatisticsValue BranchMispredictionsPerOp { get; set; } + + public BenchmarkStatisticsValue Gen0Collects { get; set; } + + public string Key { get; set; } + + public BenchmarkStatisticsValue MeanTimeTaken { get; set; } + + public string Parameters { get; set; } + + public BenchmarkStatisticsValue StandardError { get; set; } + + public static BenchmarkStatisticsItem New( + string key, + string parameters, + string baseline, + BenchmarkStatisticsValue meanTimeTaken, + BenchmarkStatisticsValue standardError, + BenchmarkStatisticsValue branchInstructionsPerOp, + BenchmarkStatisticsValue branchMispredictionsPerOp, + BenchmarkStatisticsValue gen0Collects, + BenchmarkStatisticsValue allocatedMemory) + => new BenchmarkStatisticsItem(key, + parameters, + baseline, + meanTimeTaken, + standardError, + branchInstructionsPerOp, + branchMispredictionsPerOp, + gen0Collects, + allocatedMemory); + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Exporters/Common/BenchmarkStatisticsValue.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Common/BenchmarkStatisticsValue.cs new file mode 100644 index 00000000..484f2458 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Common/BenchmarkStatisticsValue.cs @@ -0,0 +1,21 @@ +namespace Rules.Framework.BenchmarkTests.Exporters.Common +{ + internal class BenchmarkStatisticsValue + { + private BenchmarkStatisticsValue(string format, string unit, decimal value) + { + this.Format = format; + this.Unit = unit; + this.Value = value; + } + + public string Format { get; set; } + + public string Unit { get; set; } + + public decimal Value { get; set; } + + public static BenchmarkStatisticsValue New(string format, string unit, decimal value) + => new BenchmarkStatisticsValue(format, unit, value); + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Exporters/Common/Environment.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Common/Environment.cs new file mode 100644 index 00000000..a88e4299 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Common/Environment.cs @@ -0,0 +1,23 @@ +namespace Rules.Framework.BenchmarkTests.Exporters.Common +{ + 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/CustomExporterBase.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/CustomExporterBase.cs new file mode 100644 index 00000000..6db8c930 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/CustomExporterBase.cs @@ -0,0 +1,116 @@ +namespace Rules.Framework.BenchmarkTests.Exporters +{ + using System; + using System.Collections.Generic; + using System.Linq; + using BenchmarkDotNet.Exporters; + using BenchmarkDotNet.Reports; + using Rules.Framework.BenchmarkTests.Exporters.Common; + + internal abstract class CustomExporterBase : ExporterBase + { + protected static decimal CalculateRate(decimal baselineValue, decimal compareValue) + => ((baselineValue - compareValue) / baselineValue) * 100; + + protected virtual BenchmarkStatisticsComparisonItem CreateBenchmarkStatisticsComparisonItem(BenchmarkStatisticsItem baselineStatisticsItem, BenchmarkStatisticsItem nonBaselineStatisticsItem) => new BenchmarkStatisticsComparisonItem + { + AllocatedMemoryRate = BenchmarkStatisticsValue.New( + "0.##", "%", CalculateRate(baselineStatisticsItem.AllocatedMemory!.Value, nonBaselineStatisticsItem.AllocatedMemory!.Value)), + BaselineAllocatedMemory = baselineStatisticsItem.AllocatedMemory, + BaselineMeanTimeTaken = baselineStatisticsItem.MeanTimeTaken, + BaselineParameters = baselineStatisticsItem.Parameters, + CompareAllocatedMemory = nonBaselineStatisticsItem.AllocatedMemory, + CompareMeanTimeTaken = nonBaselineStatisticsItem.MeanTimeTaken, + CompareParameters = nonBaselineStatisticsItem.Parameters, + Key = baselineStatisticsItem.Key, + MeanTimeTakenCompareRate = BenchmarkStatisticsValue.New( + "0.##", "%", CalculateRate(baselineStatisticsItem.MeanTimeTaken!.Value, nonBaselineStatisticsItem.MeanTimeTaken!.Value)) + }; + + protected virtual 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 = BenchmarkStatisticsItem.New( + $"{benchmarkReport.BenchmarkCase.Descriptor.Type.Name}.{benchmarkReport.BenchmarkCase.Descriptor.WorkloadMethod.Name}", + benchmarkReport.BenchmarkCase.Parameters.DisplayInfo, + baselineClassifierColumn.Content[current++], + BenchmarkStatisticsValue.New("N0", "ns", Convert.ToDecimal(benchmarkReport.ResultStatistics?.Mean ?? 0)), + BenchmarkStatisticsValue.New("N0", "ns", Convert.ToDecimal(benchmarkReport.ResultStatistics?.StandardError ?? 0)), + CreateBenchmarkStatisticsValue(branchInstructionsMetric.Value), + CreateBenchmarkStatisticsValue(branchMispredictionsMetric.Value), + CreateBenchmarkStatisticsValue(gen0CollectsMetric.Value), + CreateBenchmarkStatisticsValue(allocatedMemoryMetric.Value) + ); + + return statisticsItem; + } + + protected virtual BenchmarkStatisticsValue CreateBenchmarkStatisticsValue(Metric? allocatedMemoryMetric) + { + if (allocatedMemoryMetric is null) + { + return BenchmarkStatisticsValue.New(string.Empty, string.Empty, decimal.Zero); + } + + return BenchmarkStatisticsValue.New( + allocatedMemoryMetric.Descriptor.NumberFormat, + allocatedMemoryMetric.Descriptor.Unit, + Convert.ToDecimal(allocatedMemoryMetric.Value)); + } + + protected virtual Common.Environment CreateEnvironment(Summary summary) => new Common.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, + }; + + protected virtual Common.BenchmarkReport CreateReport(Summary summary) + { + var report = new Common.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/CustomJsonExporter.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Json/CustomJsonExporter.cs new file mode 100644 index 00000000..ccee5125 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Json/CustomJsonExporter.cs @@ -0,0 +1,44 @@ +namespace Rules.Framework.BenchmarkTests.Exporters.Json +{ + using BenchmarkDotNet.Exporters; + using BenchmarkDotNet.Loggers; + using BenchmarkDotNet.Reports; + using Newtonsoft.Json; + + internal class CustomJsonExporter : CustomExporterBase + { + 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 = this.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); + } + } +} \ 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..3b8513a4 --- /dev/null +++ b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/CustomMarkdownExporter.cs @@ -0,0 +1,104 @@ +namespace Rules.Framework.BenchmarkTests.Exporters.Markdown +{ + using System.Linq; + using System.Text; + using BenchmarkDotNet.Exporters; + using BenchmarkDotNet.Loggers; + using BenchmarkDotNet.Reports; + + internal class CustomMarkdownExporter : CustomExporterBase + { + public static IExporter Default => new CustomMarkdownExporter(); + + protected override string FileExtension => "md"; + + public override void ExportToLog(Summary summary, ILogger logger) + { + var report = this.CreateReport(summary); + + string markdownReport = CustomMarkdownExporter.ParseReportAsMarkdown(report); + + logger.WriteLine(markdownReport); + } + + private static string ParseReportAsMarkdown(Common.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={hostEnvInfo.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 benchmarkStatisticsItems = benchmarkReport.Statistics!; + var hasBranchInstructionsPerOp = benchmarkStatisticsItems.FirstOrDefault()?.BranchInstructionsPerOp is not null; + var hasBranchMispredictionsPerOp = benchmarkStatisticsItems.FirstOrDefault()?.BranchMispredictionsPerOp is not null; + var hasGen0Collects = benchmarkStatisticsItems.FirstOrDefault()?.Gen0Collects is not null; + var hasAllocatedMemory = benchmarkStatisticsItems.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 benchmarkStatisticsItems!) + { + 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/StringBuilderExtensions.cs b/tests/Rules.Framework.BenchmarkTests/Exporters/Markdown/StringBuilderExtensions.cs new file mode 100644 index 00000000..c09ccd80 --- /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, Func textFunc, Func conditionFunc) + { + if (conditionFunc.Invoke()) + { + return builder.Append(textFunc.Invoke()); + } + + return builder; + } + } +} \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/InMemoryRulesDataSource.cs b/tests/Rules.Framework.BenchmarkTests/InMemoryRulesDataSource.cs deleted file mode 100644 index f22c7cbd..00000000 --- a/tests/Rules.Framework.BenchmarkTests/InMemoryRulesDataSource.cs +++ /dev/null @@ -1,62 +0,0 @@ -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 index c1a11ec0..879d8585 100644 --- a/tests/Rules.Framework.BenchmarkTests/Program.cs +++ b/tests/Rules.Framework.BenchmarkTests/Program.cs @@ -1,27 +1,59 @@ +using System.Runtime.InteropServices; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Exporters; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Running; +using McMaster.Extensions.CommandLineUtils; +using Rules.Framework.BenchmarkTests; +using Rules.Framework.BenchmarkTests.Exporters.Markdown; [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); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + manualConfig.AddHardwareCounters(HardwareCounter.BranchInstructions, HardwareCounter.BranchMispredictions); + } + + manualConfig.AddExporter(HtmlExporter.Default); + manualConfig.AddExporter(CustomMarkdownExporter.Default); + 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/Rules.Framework.BenchmarkTests.csproj b/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj index 22612f9f..6893c146 100644 --- a/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj +++ b/tests/Rules.Framework.BenchmarkTests/Rules.Framework.BenchmarkTests.csproj @@ -1,20 +1,20 @@ - - Exe - net6.0 - enable - enable - 10.0 - + + Exe + net6.0 + enable + enable + 10.0 + - - - - + + + + + - - - - - + + + + \ No newline at end of file diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1.cs index 02ca4a44..6743cf35 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark1/Benchmark1.cs @@ -1,15 +1,14 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark1 { - using System.Linq; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; - using Rules.Framework.Core; + using Rules.Framework.Providers.InMemory; [SkewnessColumn, KurtosisColumn] public class Benchmark1 : IBenchmark { private readonly Benchmark1Data benchmarkData = new Benchmark1Data(); - private RulesEngine rulesEngine; + private RulesEngine? rulesEngine; [ParamsAllValues] public bool EnableCompilation { get; set; } @@ -17,7 +16,7 @@ public class Benchmark1 : IBenchmark [Benchmark] public async Task RunAsync() { - await this.rulesEngine.MatchOneAsync(ContentTypes.ContentType1, this.benchmarkData.MatchDate, this.benchmarkData.Conditions).ConfigureAwait(false); + await this.rulesEngine!.MatchOneAsync(ContentTypes.ContentType1, this.benchmarkData.MatchDate, this.benchmarkData.Conditions).ConfigureAwait(false); } [GlobalSetup] @@ -26,7 +25,7 @@ public async Task SetUpAsync() this.rulesEngine = RulesEngineBuilder.CreateRulesEngine() .WithContentType() .WithConditionType() - .SetDataSource(new InMemoryRulesDataSource(Enumerable.Empty>())) + .SetInMemoryDataSource() .Configure(options => { options.EnableCompilation = this.EnableCompilation; diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2.cs index 470da6cf..f76ec701 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark2/Benchmark2.cs @@ -1,15 +1,14 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark2 { - using System.Linq; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; - using Rules.Framework.Core; + using Rules.Framework.Providers.InMemory; [SkewnessColumn, KurtosisColumn] public class Benchmark2 : IBenchmark { private readonly Benchmark2Data benchmarkData = new Benchmark2Data(); - private RulesEngine rulesEngine; + private RulesEngine? rulesEngine; [ParamsAllValues] public bool EnableCompilation { get; set; } @@ -17,7 +16,7 @@ public class Benchmark2 : IBenchmark [Benchmark] public async Task RunAsync() { - await this.rulesEngine.MatchOneAsync(ContentTypes.Songs, this.benchmarkData.MatchDate, this.benchmarkData.Conditions).ConfigureAwait(false); + await this.rulesEngine!.MatchOneAsync(ContentTypes.Songs, this.benchmarkData.MatchDate, this.benchmarkData.Conditions).ConfigureAwait(false); } [GlobalSetup] @@ -26,7 +25,7 @@ public async Task SetUpAsync() this.rulesEngine = RulesEngineBuilder.CreateRulesEngine() .WithContentType() .WithConditionType() - .SetDataSource(new InMemoryRulesDataSource(Enumerable.Empty>())) + .SetInMemoryDataSource() .Configure(options => { options.EnableCompilation = this.EnableCompilation; diff --git a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3.cs b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3.cs index bc714300..47f8fd35 100644 --- a/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3.cs +++ b/tests/Rules.Framework.BenchmarkTests/Tests/Benchmark3/Benchmark3.cs @@ -1,15 +1,14 @@ namespace Rules.Framework.BenchmarkTests.Tests.Benchmark3 { - using System.Linq; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; - using Rules.Framework.Core; + using Rules.Framework.Providers.InMemory; [SkewnessColumn, KurtosisColumn] public class Benchmark3 : IBenchmark { private readonly Benchmark3Data benchmarkData = new Benchmark3Data(); - private RulesEngine rulesEngine; + private RulesEngine? rulesEngine; [ParamsAllValues] public bool EnableCompilation { get; set; } @@ -17,7 +16,7 @@ public class Benchmark3 : IBenchmark [Benchmark] public async Task RunAsync() { - await this.rulesEngine.MatchOneAsync(ContentTypes.TexasHoldemPokerSingleCombinations, this.benchmarkData.MatchDate, this.benchmarkData.Conditions).ConfigureAwait(false); + await this.rulesEngine!.MatchOneAsync(ContentTypes.TexasHoldemPokerSingleCombinations, this.benchmarkData.MatchDate, this.benchmarkData.Conditions).ConfigureAwait(false); } [GlobalSetup] @@ -26,7 +25,7 @@ public async Task SetUpAsync() this.rulesEngine = RulesEngineBuilder.CreateRulesEngine() .WithContentType() .WithConditionType() - .SetDataSource(new InMemoryRulesDataSource(Enumerable.Empty>())) + .SetInMemoryDataSource() .Configure(options => { options.EnableCompilation = this.EnableCompilation;