From bf14f7b8710b77c40bd747b85032fe00d89cf302 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Jun 2025 06:59:34 +0000 Subject: [PATCH 1/6] build: Bump System.CommandLine from 2.0.0-beta4.22272.1 to 2.0.0-beta5.25306.1 --- updated-dependencies: - dependency-name: System.CommandLine dependency-version: 2.0.0-beta5.25306.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- src/PowerUtils.BenchmarkDotnet.Reporter.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerUtils.BenchmarkDotnet.Reporter.csproj b/src/PowerUtils.BenchmarkDotnet.Reporter.csproj index 9d1a77a..aa64d3a 100644 --- a/src/PowerUtils.BenchmarkDotnet.Reporter.csproj +++ b/src/PowerUtils.BenchmarkDotnet.Reporter.csproj @@ -95,7 +95,7 @@ - + From 3aff795fec60290a6cd0f46d52ab7cb1a50f8ad6 Mon Sep 17 00:00:00 2001 From: Nelson Nobre Date: Sat, 21 Jun 2025 11:30:28 +0100 Subject: [PATCH 2/6] refactor(commands): adapt the commands to the new structure --- src/Commands/ComparerCommand.cs | 7 +- src/Program.cs | 3 +- src/ToolCommands.cs | 94 +++++++++++-------- .../ToolCommandsTest.cs | 28 +++--- 4 files changed, 76 insertions(+), 56 deletions(-) diff --git a/src/Commands/ComparerCommand.cs b/src/Commands/ComparerCommand.cs index 650f34e..02fd318 100644 --- a/src/Commands/ComparerCommand.cs +++ b/src/Commands/ComparerCommand.cs @@ -11,7 +11,7 @@ namespace PowerUtils.BenchmarkDotnet.Reporter.Commands; public interface IComparerCommand { - void Execute(string? baseline, string? target, string? meanThreshold, string? allocationThreshold, string[] formats, string output); + int Execute(string? baseline, string? target, string? meanThreshold, string? allocationThreshold, string[] formats, string output); } public sealed class ComparerCommand( @@ -24,7 +24,7 @@ public sealed class ComparerCommand( private readonly IServiceProvider _provider = provider; - public void Execute( + public int Execute( string? baseline, string? target, string? meanThreshold, @@ -166,5 +166,8 @@ public void Execute( .GetRequiredKeyedService(format.ToLower()) .Generate(comparerReport, output); } + + + return 0; // Success exit code } } diff --git a/src/Program.cs b/src/Program.cs index 2a455c2..81f54db 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,5 +1,4 @@ using System; -using System.CommandLine; using System.Globalization; using System.Threading; using Microsoft.Extensions.DependencyInjection; @@ -29,4 +28,4 @@ .AddTransient(); var tool = new ToolCommands(serviceCollection.BuildServiceProvider()); -tool.Invoke(args); +return tool.Parse(args).Invoke(); diff --git a/src/ToolCommands.cs b/src/ToolCommands.cs index 3893fd5..970284e 100644 --- a/src/ToolCommands.cs +++ b/src/ToolCommands.cs @@ -1,5 +1,6 @@ using System; using System.CommandLine; +using System.Linq; using Microsoft.Extensions.DependencyInjection; using PowerUtils.BenchmarkDotnet.Reporter.Commands; @@ -30,60 +31,77 @@ public ToolCommands(IServiceProvider provider) output }; - compareCommand - .SetHandler( - provider.GetRequiredService().Execute, - baseline, - target, - meanThreshold, - allocationThreshold, - formats, - output); - - Add(compareCommand); + compareCommand.SetAction(parser => provider + .GetRequiredService() + .Execute( + parser.GetValue(baseline)!, + parser.GetValue(target)!, + parser.GetValue(meanThreshold), + parser.GetValue(allocationThreshold), + parser.GetValue(formats)!, + parser.GetValue(output)!)); + + Subcommands.Add(compareCommand); } private static Option _createBaselineOption() - => new( - ["-b", "--baseline"], - "Path to the folder or file with Baseline report.") + => new("--baseline", "-b") { - IsRequired = true + Description = "Path to the folder or file with Baseline report.", + Required = true }; private static Option _createTargetOption() - => new( - ["-t", "--target"], - "Path to the folder or file with target reports.") + => new("--target", "-t") { - IsRequired = true + Description = "Path to the folder or file with target reports.", + Required = true }; private static Option _createMeanThresholdOption() - => new( - ["-tm", "--threshold-mean"], - "Throw an error when the mean threshold is met. Examples: 5%, 10ms, 10μs, 100ns, 1s."); + => new("--threshold-mean", "-tm") + { + Description = "Throw an error when the mean threshold is met. Examples: 5%, 10ms, 10μs, 100ns, 1s." + }; private static Option _createAllocationThresholdOption() - => new( - ["-ta", "--threshold-allocation"], - "Throw an error when the allocation threshold is met. Examples: 5%, 10b, 10kb, 100mb, 1gb."); + => new("--threshold-allocation", "-ta") + { + Description = "Throw an error when the allocation threshold is met. Examples: 5%, 10b, 10kb, 100mb, 1gb." + }; private static Option _createFormatsOption() - => new Option( - ["-f", "--format"], - () => ["console"], - "Output format for the report.") - .FromAmong( - "console", - "markdown", - "json", - "hit-txt"); + { + var option = new Option("--format", "-f") + { + Description = "Output format for the report.", + DefaultValueFactory = _ => ["console"] + }; + + option.Validators.Add(result => + { + var allowedValues = new[] { "console", "markdown", "json", "hit-txt" }; + var values = result.GetValue(option); + if(values != null) + { + foreach(var value in values) + { + if(!allowedValues.Contains(value)) + { + result.AddError($"Invalid format '{value}'. Allowed values: {string.Join(", ", allowedValues)}"); + } + } + } + }); + + return option; + } private static Option _createOutputOption() - => new( - ["-o", "--output"], - () => "./BenchmarkReporter", - "Output directory to export the diff report. Default is current directory."); + => new("--output", "-o") + { + Description = "Output directory to export the diff report. Default is current directory.", + DefaultValueFactory = _ => "./BenchmarkReporter" + }; } diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/ToolCommandsTest.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/ToolCommandsTest.cs index 0d50c7c..adc8715 100644 --- a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/ToolCommandsTest.cs +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/ToolCommandsTest.cs @@ -66,12 +66,12 @@ public void CompareCommand_ShouldHave_BaselineOption() // Assert var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); - var baselineOption = compareCommand.Options.Single(o => o.Name == "baseline"); + var baselineOption = compareCommand.Options.Single(o => o.Name == "--baseline"); baselineOption.ValueType.ShouldBe(typeof(string)); + baselineOption.Aliases.Count.ShouldBe(1); baselineOption.Aliases.ShouldContain("-b"); - baselineOption.Aliases.ShouldContain("--baseline"); - baselineOption.IsRequired.ShouldBeTrue(); + baselineOption.Required.ShouldBeTrue(); baselineOption.Description.ShouldBe("Path to the folder or file with Baseline report."); } @@ -84,12 +84,12 @@ public void CompareCommand_ShouldHave_TargetOption() // Assert var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); - var targetOption = compareCommand.Options.Single(o => o.Name == "target"); + var targetOption = compareCommand.Options.Single(o => o.Name == "--target"); targetOption.ValueType.ShouldBe(typeof(string)); + targetOption.Aliases.Count.ShouldBe(1); targetOption.Aliases.ShouldContain("-t"); - targetOption.Aliases.ShouldContain("--target"); - targetOption.IsRequired.ShouldBeTrue(); + targetOption.Required.ShouldBeTrue(); targetOption.Description.ShouldBe("Path to the folder or file with target reports."); } @@ -102,11 +102,11 @@ public void CompareCommand_ShouldHave_ThresholdMeanOption() // Assert var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); - var meanThresholdOption = compareCommand.Options.Single(o => o.Name == "threshold-mean"); + var meanThresholdOption = compareCommand.Options.Single(o => o.Name == "--threshold-mean"); meanThresholdOption.ValueType.ShouldBe(typeof(string)); + meanThresholdOption.Aliases.Count.ShouldBe(1); meanThresholdOption.Aliases.ShouldContain("-tm"); - meanThresholdOption.Aliases.ShouldContain("--threshold-mean"); meanThresholdOption.Description.ShouldBe("Throw an error when the mean threshold is met. Examples: 5%, 10ms, 10μs, 100ns, 1s."); } @@ -119,11 +119,11 @@ public void CompareCommand_ShouldHave_ThresholdAllocationOption() // Assert var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); - var allocationThresholdOption = compareCommand.Options.Single(o => o.Name == "threshold-allocation"); + var allocationThresholdOption = compareCommand.Options.Single(o => o.Name == "--threshold-allocation"); allocationThresholdOption.ValueType.ShouldBe(typeof(string)); + allocationThresholdOption.Aliases.Count.ShouldBe(1); allocationThresholdOption.Aliases.ShouldContain("-ta"); - allocationThresholdOption.Aliases.ShouldContain("--threshold-allocation"); allocationThresholdOption.Description.ShouldBe("Throw an error when the allocation threshold is met. Examples: 5%, 10b, 10kb, 100mb, 1gb."); } @@ -136,11 +136,11 @@ public void CompareCommand_ShouldHave_FormatsOption() // Assert var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); - var formatsOption = compareCommand.Options.Single(o => o.Name == "format"); + var formatsOption = compareCommand.Options.Single(o => o.Name == "--format"); formatsOption.ValueType.ShouldBe(typeof(string[])); + formatsOption.Aliases.Count.ShouldBe(1); formatsOption.Aliases.ShouldContain("-f"); - formatsOption.Aliases.ShouldContain("--format"); formatsOption.Description.ShouldBe("Output format for the report."); } @@ -153,11 +153,11 @@ public void CompareCommand_ShouldHave_OutputOption() // Assert var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); - var outputOption = compareCommand.Options.Single(o => o.Name == "output"); + var outputOption = compareCommand.Options.Single(o => o.Name == "--output"); outputOption.ValueType.ShouldBe(typeof(string)); + outputOption.Aliases.Count.ShouldBe(1); outputOption.Aliases.ShouldContain("-o"); - outputOption.Aliases.ShouldContain("--output"); outputOption.Description.ShouldBe("Output directory to export the diff report. Default is current directory."); } } From d633699e6391e0a84bd961738665d04aadd2b969 Mon Sep 17 00:00:00 2001 From: Nelson Nobre Date: Sat, 21 Jun 2025 14:49:35 +0100 Subject: [PATCH 3/6] refactor: improve readability and maintainability organization options --- .vscode/launch.json | 2 +- src/Options/AllocationThresholdOption.cs | 12 ++ src/Options/BaselineOption.cs | 13 +++ src/Options/FormatsOption.cs | 36 ++++++ src/Options/MeanThresholdOption.cs | 12 ++ src/Options/OutputOption.cs | 13 +++ src/Options/TargetOption.cs | 13 +++ src/ToolCommands.cs | 75 ++---------- .../Options/FormatOptionTests.cs | 63 +++++++++++ .../Options/OptionsTests.cs | 107 ++++++++++++++++++ .../ToolCommandsTest.cs | 106 +---------------- 11 files changed, 278 insertions(+), 174 deletions(-) create mode 100644 src/Options/AllocationThresholdOption.cs create mode 100644 src/Options/BaselineOption.cs create mode 100644 src/Options/FormatsOption.cs create mode 100644 src/Options/MeanThresholdOption.cs create mode 100644 src/Options/OutputOption.cs create mode 100644 src/Options/TargetOption.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Options/FormatOptionTests.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Options/OptionsTests.cs diff --git a/.vscode/launch.json b/.vscode/launch.json index ba1171e..ea9820e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -13,7 +13,7 @@ //"compare", "-f", "console", "-b", "../tests/test-data/report-02/Benchmark-report-full.json", "-t", "../tests/test-data/report-01/Benchmark-report-full.json", "-tm", "4%", "-ta", "5%" //"compare", "-f", "console", "-b", "../tests/test-data/report-01/Benchmark-report-full.json", "-t", "../tests/test-data/report-04/Benchmark-report-full.json" //"compare", "-f", "markdown", "-b", "../tests/test-data/report-01/Benchmark-report-full.json", "-t", "../tests/test-data/report-02/Benchmark-report-full.json", "-tm", "12ns", "-ta", "5%" - "compare", "-f", "console", "-f", "markdown", "-b", "../tests/test-data/report-12", "-t", "../tests/test-data/report-11" + "compare", "-f", "CONSOLE", "-f", "markdown", "-b", "../tests/test-data/report-12", "-t", "../tests/test-data/report-11" ], "cwd": "${workspaceFolder}/src", "console": "internalConsole", diff --git a/src/Options/AllocationThresholdOption.cs b/src/Options/AllocationThresholdOption.cs new file mode 100644 index 0000000..ae753aa --- /dev/null +++ b/src/Options/AllocationThresholdOption.cs @@ -0,0 +1,12 @@ +using System.CommandLine; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Options; + +public sealed class AllocationThresholdOption : Option +{ + public AllocationThresholdOption() + : base("--threshold-allocation", "-ta") + { + Description = "Throw an error when the allocation threshold is met. Examples: 5%, 10b, 10kb, 100mb, 1gb."; + } +} diff --git a/src/Options/BaselineOption.cs b/src/Options/BaselineOption.cs new file mode 100644 index 0000000..a4e721f --- /dev/null +++ b/src/Options/BaselineOption.cs @@ -0,0 +1,13 @@ +using System.CommandLine; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Options; + +public sealed class BaselineOption : Option +{ + public BaselineOption() + : base("--baseline", "-b") + { + Description = "Path to the folder or file with Baseline report."; + Required = true; + } +} diff --git a/src/Options/FormatsOption.cs b/src/Options/FormatsOption.cs new file mode 100644 index 0000000..34a4129 --- /dev/null +++ b/src/Options/FormatsOption.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.Linq; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Options; + +public sealed class FormatsOption : Option +{ + private static readonly HashSet _allowedValues = new(StringComparer.OrdinalIgnoreCase) { "console", "markdown", "json", "hit-txt" }; + + public FormatsOption() + : base("--format", "-f") + { + Description = "Output format for the report."; + DefaultValueFactory = _ => ["console"]; + + Validators.Add(static result => + { + var values = result.Tokens + .Select(token => token.Value) + .Where(value => !string.IsNullOrWhiteSpace(value)) + .ToArray(); + if(values != null) + { + foreach(var value in values) + { + if(!_allowedValues.Contains(value)) + { + result.AddError($"Invalid format '{value}'. Allowed values: {string.Join(", ", _allowedValues)}"); + } + } + } + }); + } +} diff --git a/src/Options/MeanThresholdOption.cs b/src/Options/MeanThresholdOption.cs new file mode 100644 index 0000000..f7a6216 --- /dev/null +++ b/src/Options/MeanThresholdOption.cs @@ -0,0 +1,12 @@ +using System.CommandLine; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Options; + +public sealed class MeanThresholdOption : Option +{ + public MeanThresholdOption() + : base("--threshold-mean", "-tm") + { + Description = "Throw an error when the mean threshold is met. Examples: 5%, 10ms, 10μs, 100ns, 1s."; + } +} diff --git a/src/Options/OutputOption.cs b/src/Options/OutputOption.cs new file mode 100644 index 0000000..3f0ab39 --- /dev/null +++ b/src/Options/OutputOption.cs @@ -0,0 +1,13 @@ +using System.CommandLine; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Options; + +public sealed class OutputOption : Option +{ + public OutputOption() + : base("--output", "-o") + { + Description = "Output directory to export the diff report. Default is current directory."; + DefaultValueFactory = _ => "./BenchmarkReporter"; + } +} diff --git a/src/Options/TargetOption.cs b/src/Options/TargetOption.cs new file mode 100644 index 0000000..3c24ff9 --- /dev/null +++ b/src/Options/TargetOption.cs @@ -0,0 +1,13 @@ +using System.CommandLine; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Options; + +public sealed class TargetOption : Option +{ + public TargetOption() + : base("--target", "-t") + { + Description = "Path to the folder or file with target reports."; + Required = true; + } +} diff --git a/src/ToolCommands.cs b/src/ToolCommands.cs index 970284e..94dd4aa 100644 --- a/src/ToolCommands.cs +++ b/src/ToolCommands.cs @@ -1,8 +1,8 @@ using System; using System.CommandLine; -using System.Linq; using Microsoft.Extensions.DependencyInjection; using PowerUtils.BenchmarkDotnet.Reporter.Commands; +using PowerUtils.BenchmarkDotnet.Reporter.Options; namespace PowerUtils.BenchmarkDotnet.Reporter; @@ -10,14 +10,14 @@ public sealed class ToolCommands : RootCommand { public ToolCommands(IServiceProvider provider) { - var baseline = _createBaselineOption(); - var target = _createTargetOption(); + var baseline = new BaselineOption(); + var target = new TargetOption(); - var meanThreshold = _createMeanThresholdOption(); - var allocationThreshold = _createAllocationThresholdOption(); + var meanThreshold = new MeanThresholdOption(); + var allocationThreshold = new AllocationThresholdOption(); - var formats = _createFormatsOption(); - var output = _createOutputOption(); + var formats = new FormatsOption(); + var output = new OutputOption(); var compareCommand = new Command( "compare", @@ -43,65 +43,4 @@ public ToolCommands(IServiceProvider provider) Subcommands.Add(compareCommand); } - - - private static Option _createBaselineOption() - => new("--baseline", "-b") - { - Description = "Path to the folder or file with Baseline report.", - Required = true - }; - - private static Option _createTargetOption() - => new("--target", "-t") - { - Description = "Path to the folder or file with target reports.", - Required = true - }; - - private static Option _createMeanThresholdOption() - => new("--threshold-mean", "-tm") - { - Description = "Throw an error when the mean threshold is met. Examples: 5%, 10ms, 10μs, 100ns, 1s." - }; - - private static Option _createAllocationThresholdOption() - => new("--threshold-allocation", "-ta") - { - Description = "Throw an error when the allocation threshold is met. Examples: 5%, 10b, 10kb, 100mb, 1gb." - }; - - private static Option _createFormatsOption() - { - var option = new Option("--format", "-f") - { - Description = "Output format for the report.", - DefaultValueFactory = _ => ["console"] - }; - - option.Validators.Add(result => - { - var allowedValues = new[] { "console", "markdown", "json", "hit-txt" }; - var values = result.GetValue(option); - if(values != null) - { - foreach(var value in values) - { - if(!allowedValues.Contains(value)) - { - result.AddError($"Invalid format '{value}'. Allowed values: {string.Join(", ", allowedValues)}"); - } - } - } - }); - - return option; - } - - private static Option _createOutputOption() - => new("--output", "-o") - { - Description = "Output directory to export the diff report. Default is current directory.", - DefaultValueFactory = _ => "./BenchmarkReporter" - }; } diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Options/FormatOptionTests.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Options/FormatOptionTests.cs new file mode 100644 index 0000000..63adaec --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Options/FormatOptionTests.cs @@ -0,0 +1,63 @@ +using System; +using System.Linq; +using PowerUtils.BenchmarkDotnet.Reporter.Commands; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Options; + +public sealed class FormatOptionTests +{ + private readonly IServiceProvider _provider; + + public FormatOptionTests() + { + _provider = Substitute.For(); + var command = Substitute.For(); + + _provider + .GetService(typeof(IComparerCommand)) + .Returns(command); + } + + [Fact] + public void CompareCommand_ShouldHave_FormatsOption() + { + // Arrange & Act + var toolCommands = new ToolCommands(_provider); + + + // Assert + var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); + var formatsOption = compareCommand.Options.Single(o => o.Name == "--format"); + + formatsOption.ValueType.ShouldBe(typeof(string[])); + formatsOption.Aliases.Count.ShouldBe(1); + formatsOption.Aliases.ShouldContain("-f"); + formatsOption.Description.ShouldBe("Output format for the report."); + } + + [Theory] + [InlineData("markdown", true)] + [InlineData("jSOn", true)] + [InlineData("HIT-TXT", true)] + [InlineData("console", true)] + [InlineData("invalid-format", false)] + public void Test_Validation_Format_Option(string format, bool isValid) + { + // Arrange + var command = "compare"; + var option = "--format"; + + var toolCommands = new ToolCommands(_provider); + var compareCommand = toolCommands.Subcommands.Single(c => c.Name == command); + var formatsOption = compareCommand.Options.Single(o => o.Name == option); + var validation = formatsOption.Validators.Single(); + + + // Act + var parseResult = toolCommands.Parse($"{command} {option} {format}"); + var firstOptionResult = parseResult.GetResult(formatsOption); + + // Assert + firstOptionResult?.Errors.Count().ShouldBe(isValid ? 0 : 1); + } +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Options/OptionsTests.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Options/OptionsTests.cs new file mode 100644 index 0000000..0abd565 --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Options/OptionsTests.cs @@ -0,0 +1,107 @@ +using System; +using System.Linq; +using PowerUtils.BenchmarkDotnet.Reporter.Commands; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Options; + +public sealed class OptionsTests +{ + private readonly IServiceProvider _provider; + + public OptionsTests() + { + _provider = Substitute.For(); + var command = Substitute.For(); + + _provider + .GetService(typeof(IComparerCommand)) + .Returns(command); + } + + [Fact] + public void CompareCommand_ShouldHave_BaselineOption() + { + // Arrange & Act + var toolCommands = new ToolCommands(_provider); + + + // Assert + var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); + var baselineOption = compareCommand.Options.Single(o => o.Name == "--baseline"); + + baselineOption.ValueType.ShouldBe(typeof(string)); + baselineOption.Aliases.Count.ShouldBe(1); + baselineOption.Aliases.ShouldContain("-b"); + baselineOption.Required.ShouldBeTrue(); + baselineOption.Description.ShouldBe("Path to the folder or file with Baseline report."); + } + + [Fact] + public void CompareCommand_ShouldHave_TargetOption() + { + // Arrange & Act + var toolCommands = new ToolCommands(_provider); + + + // Assert + var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); + var targetOption = compareCommand.Options.Single(o => o.Name == "--target"); + + targetOption.ValueType.ShouldBe(typeof(string)); + targetOption.Aliases.Count.ShouldBe(1); + targetOption.Aliases.ShouldContain("-t"); + targetOption.Required.ShouldBeTrue(); + targetOption.Description.ShouldBe("Path to the folder or file with target reports."); + } + + [Fact] + public void CompareCommand_ShouldHave_ThresholdMeanOption() + { + // Arrange & Act + var toolCommands = new ToolCommands(_provider); + + + // Assert + var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); + var meanThresholdOption = compareCommand.Options.Single(o => o.Name == "--threshold-mean"); + + meanThresholdOption.ValueType.ShouldBe(typeof(string)); + meanThresholdOption.Aliases.Count.ShouldBe(1); + meanThresholdOption.Aliases.ShouldContain("-tm"); + meanThresholdOption.Description.ShouldBe("Throw an error when the mean threshold is met. Examples: 5%, 10ms, 10μs, 100ns, 1s."); + } + + [Fact] + public void CompareCommand_ShouldHave_ThresholdAllocationOption() + { + // Arrange & Act + var toolCommands = new ToolCommands(_provider); + + + // Assert + var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); + var allocationThresholdOption = compareCommand.Options.Single(o => o.Name == "--threshold-allocation"); + + allocationThresholdOption.ValueType.ShouldBe(typeof(string)); + allocationThresholdOption.Aliases.Count.ShouldBe(1); + allocationThresholdOption.Aliases.ShouldContain("-ta"); + allocationThresholdOption.Description.ShouldBe("Throw an error when the allocation threshold is met. Examples: 5%, 10b, 10kb, 100mb, 1gb."); + } + + [Fact] + public void CompareCommand_ShouldHave_OutputOption() + { + // Arrange & Act + var toolCommands = new ToolCommands(_provider); + + + // Assert + var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); + var outputOption = compareCommand.Options.Single(o => o.Name == "--output"); + + outputOption.ValueType.ShouldBe(typeof(string)); + outputOption.Aliases.Count.ShouldBe(1); + outputOption.Aliases.ShouldContain("-o"); + outputOption.Description.ShouldBe("Output directory to export the diff report. Default is current directory."); + } +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/ToolCommandsTest.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/ToolCommandsTest.cs index adc8715..c78710d 100644 --- a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/ToolCommandsTest.cs +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/ToolCommandsTest.cs @@ -45,7 +45,7 @@ public void RootCommands_ShouldContain_CompareCommand() } [Fact] - public void CompareCommand_ShouldHave_4Options() + public void CompareCommand_ShouldHave_6Options() { // Arrange & Act var toolCommands = new ToolCommands(_provider); @@ -56,108 +56,4 @@ public void CompareCommand_ShouldHave_4Options() compareCommand.Options.Count.ShouldBe(6); } - - [Fact] - public void CompareCommand_ShouldHave_BaselineOption() - { - // Arrange & Act - var toolCommands = new ToolCommands(_provider); - - - // Assert - var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); - var baselineOption = compareCommand.Options.Single(o => o.Name == "--baseline"); - - baselineOption.ValueType.ShouldBe(typeof(string)); - baselineOption.Aliases.Count.ShouldBe(1); - baselineOption.Aliases.ShouldContain("-b"); - baselineOption.Required.ShouldBeTrue(); - baselineOption.Description.ShouldBe("Path to the folder or file with Baseline report."); - } - - [Fact] - public void CompareCommand_ShouldHave_TargetOption() - { - // Arrange & Act - var toolCommands = new ToolCommands(_provider); - - - // Assert - var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); - var targetOption = compareCommand.Options.Single(o => o.Name == "--target"); - - targetOption.ValueType.ShouldBe(typeof(string)); - targetOption.Aliases.Count.ShouldBe(1); - targetOption.Aliases.ShouldContain("-t"); - targetOption.Required.ShouldBeTrue(); - targetOption.Description.ShouldBe("Path to the folder or file with target reports."); - } - - [Fact] - public void CompareCommand_ShouldHave_ThresholdMeanOption() - { - // Arrange & Act - var toolCommands = new ToolCommands(_provider); - - - // Assert - var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); - var meanThresholdOption = compareCommand.Options.Single(o => o.Name == "--threshold-mean"); - - meanThresholdOption.ValueType.ShouldBe(typeof(string)); - meanThresholdOption.Aliases.Count.ShouldBe(1); - meanThresholdOption.Aliases.ShouldContain("-tm"); - meanThresholdOption.Description.ShouldBe("Throw an error when the mean threshold is met. Examples: 5%, 10ms, 10μs, 100ns, 1s."); - } - - [Fact] - public void CompareCommand_ShouldHave_ThresholdAllocationOption() - { - // Arrange & Act - var toolCommands = new ToolCommands(_provider); - - - // Assert - var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); - var allocationThresholdOption = compareCommand.Options.Single(o => o.Name == "--threshold-allocation"); - - allocationThresholdOption.ValueType.ShouldBe(typeof(string)); - allocationThresholdOption.Aliases.Count.ShouldBe(1); - allocationThresholdOption.Aliases.ShouldContain("-ta"); - allocationThresholdOption.Description.ShouldBe("Throw an error when the allocation threshold is met. Examples: 5%, 10b, 10kb, 100mb, 1gb."); - } - - [Fact] - public void CompareCommand_ShouldHave_FormatsOption() - { - // Arrange & Act - var toolCommands = new ToolCommands(_provider); - - - // Assert - var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); - var formatsOption = compareCommand.Options.Single(o => o.Name == "--format"); - - formatsOption.ValueType.ShouldBe(typeof(string[])); - formatsOption.Aliases.Count.ShouldBe(1); - formatsOption.Aliases.ShouldContain("-f"); - formatsOption.Description.ShouldBe("Output format for the report."); - } - - [Fact] - public void CompareCommand_ShouldHave_OutputOption() - { - // Arrange & Act - var toolCommands = new ToolCommands(_provider); - - - // Assert - var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); - var outputOption = compareCommand.Options.Single(o => o.Name == "--output"); - - outputOption.ValueType.ShouldBe(typeof(string)); - outputOption.Aliases.Count.ShouldBe(1); - outputOption.Aliases.ShouldContain("-o"); - outputOption.Description.ShouldBe("Output directory to export the diff report. Default is current directory."); - } } From 211d9e1304578b55fb2a9ed32af28c1bd15f307e Mon Sep 17 00:00:00 2001 From: Nelson Nobre Date: Sat, 21 Jun 2025 14:53:29 +0100 Subject: [PATCH 4/6] refactor: restructure service registration for ToolCommands --- src/Program.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index 81f54db..9f35499 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -14,6 +14,7 @@ var serviceCollection = new ServiceCollection(); serviceCollection + .AddTransient() .AddTransient(sp => (message) => IOHelpers.Print(message)) .AddTransient(sp => @@ -27,5 +28,7 @@ .AddKeyedTransient("console") .AddTransient(); -var tool = new ToolCommands(serviceCollection.BuildServiceProvider()); -return tool.Parse(args).Invoke(); +return serviceCollection.BuildServiceProvider() + .GetRequiredService() + .Parse(args) + .Invoke(); From 24b867bf27d611fb5713a0151af301732e97f881 Mon Sep 17 00:00:00 2001 From: Nelson Nobre Date: Sat, 21 Jun 2025 15:10:09 +0100 Subject: [PATCH 5/6] refactor(options): simplify validation logic for format option --- src/Options/FormatsOption.cs | 9 ++--- .../Options/FormatOptionTests.cs | 39 +++++++++++++++---- .../Options/OptionsTests.cs | 1 + 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/Options/FormatsOption.cs b/src/Options/FormatsOption.cs index 34a4129..7eda3fb 100644 --- a/src/Options/FormatsOption.cs +++ b/src/Options/FormatsOption.cs @@ -21,14 +21,11 @@ public FormatsOption() .Select(token => token.Value) .Where(value => !string.IsNullOrWhiteSpace(value)) .ToArray(); - if(values != null) + foreach(var value in values) { - foreach(var value in values) + if(!_allowedValues.Contains(value)) { - if(!_allowedValues.Contains(value)) - { - result.AddError($"Invalid format '{value}'. Allowed values: {string.Join(", ", _allowedValues)}"); - } + result.AddError($"Invalid format '{value}'. Allowed values: {string.Join(", ", _allowedValues)}"); } } }); diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Options/FormatOptionTests.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Options/FormatOptionTests.cs index 63adaec..1d437bc 100644 --- a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Options/FormatOptionTests.cs +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Options/FormatOptionTests.cs @@ -33,15 +33,15 @@ public void CompareCommand_ShouldHave_FormatsOption() formatsOption.Aliases.Count.ShouldBe(1); formatsOption.Aliases.ShouldContain("-f"); formatsOption.Description.ShouldBe("Output format for the report."); + (formatsOption.GetDefaultValue() as string[]).ShouldBe(["console"]); } [Theory] - [InlineData("markdown", true)] - [InlineData("jSOn", true)] - [InlineData("HIT-TXT", true)] - [InlineData("console", true)] - [InlineData("invalid-format", false)] - public void Test_Validation_Format_Option(string format, bool isValid) + [InlineData("markdown")] + [InlineData("jSOn")] + [InlineData("HIT-TXT")] + [InlineData("console")] + public void When_Format_Is_Valid_Shouldnt_Have_Validation_Error(string format) { // Arrange var command = "compare"; @@ -58,6 +58,31 @@ public void Test_Validation_Format_Option(string format, bool isValid) var firstOptionResult = parseResult.GetResult(formatsOption); // Assert - firstOptionResult?.Errors.Count().ShouldBe(isValid ? 0 : 1); + firstOptionResult?.Errors.Count().ShouldBe(0); + } + + [Theory] + [InlineData("invalid-format")] + [InlineData("csv")] + [InlineData("html")] + public void When_Format_Is_Invalid_Should_Have_Validation_Error(string? format) + { + // Arrange + var command = "compare"; + var option = "--format"; + + var toolCommands = new ToolCommands(_provider); + var compareCommand = toolCommands.Subcommands.Single(c => c.Name == command); + var formatsOption = compareCommand.Options.Single(o => o.Name == option); + var validation = formatsOption.Validators.Single(); + + + // Act + var parseResult = toolCommands.Parse($"{command} {option} {format}"); + var firstOptionResult = parseResult.GetResult(formatsOption); + + // Assert + firstOptionResult?.Errors.Count().ShouldBe(1); + firstOptionResult?.Errors.ShouldContain(e => e.Message == $"Invalid format '{format}'. Allowed values: console, markdown, json, hit-txt"); } } diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Options/OptionsTests.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Options/OptionsTests.cs index 0abd565..2e689fb 100644 --- a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Options/OptionsTests.cs +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Options/OptionsTests.cs @@ -103,5 +103,6 @@ public void CompareCommand_ShouldHave_OutputOption() outputOption.Aliases.Count.ShouldBe(1); outputOption.Aliases.ShouldContain("-o"); outputOption.Description.ShouldBe("Output directory to export the diff report. Default is current directory."); + (outputOption.GetDefaultValue() as string).ShouldBe("./BenchmarkReporter"); } } From 740922733a03f002dd67a3dc5128f467fe0b6353 Mon Sep 17 00:00:00 2001 From: Nelson Nobre Date: Sat, 21 Jun 2025 15:17:17 +0100 Subject: [PATCH 6/6] refactor: Simplify readability of the code --- src/Options/FormatsOption.cs | 4 +-- .../Options/FormatOptionTests.cs | 27 ++++++++++++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/Options/FormatsOption.cs b/src/Options/FormatsOption.cs index 7eda3fb..3d09ddb 100644 --- a/src/Options/FormatsOption.cs +++ b/src/Options/FormatsOption.cs @@ -18,9 +18,7 @@ public FormatsOption() Validators.Add(static result => { var values = result.Tokens - .Select(token => token.Value) - .Where(value => !string.IsNullOrWhiteSpace(value)) - .ToArray(); + .Select(token => token.Value); foreach(var value in values) { if(!_allowedValues.Contains(value)) diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Options/FormatOptionTests.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Options/FormatOptionTests.cs index 1d437bc..6823546 100644 --- a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Options/FormatOptionTests.cs +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Options/FormatOptionTests.cs @@ -65,7 +65,7 @@ public void When_Format_Is_Valid_Shouldnt_Have_Validation_Error(string format) [InlineData("invalid-format")] [InlineData("csv")] [InlineData("html")] - public void When_Format_Is_Invalid_Should_Have_Validation_Error(string? format) + public void When_Format_Is_Invalid_Should_Have_Validation_Error(string format) { // Arrange var command = "compare"; @@ -85,4 +85,29 @@ public void When_Format_Is_Invalid_Should_Have_Validation_Error(string? format) firstOptionResult?.Errors.Count().ShouldBe(1); firstOptionResult?.Errors.ShouldContain(e => e.Message == $"Invalid format '{format}'. Allowed values: console, markdown, json, hit-txt"); } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + public void When_Format_Isnt_Defined_Should_Have_Validation_Error(string? format) + { + // Arrange + var command = "compare"; + var option = "--format"; + + var toolCommands = new ToolCommands(_provider); + var compareCommand = toolCommands.Subcommands.Single(c => c.Name == command); + var formatsOption = compareCommand.Options.Single(o => o.Name == option); + var validation = formatsOption.Validators.Single(); + + + // Act + var parseResult = toolCommands.Parse($"{command} {option} {format}"); + var firstOptionResult = parseResult.GetResult(formatsOption); + + // Assert + firstOptionResult?.Errors.Count().ShouldBe(1); + firstOptionResult?.Errors.ShouldContain(e => e.Message == "Required argument missing for option: '--format'."); + } }