Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 5 additions & 2 deletions src/Commands/ComparerCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

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(
Expand All @@ -24,7 +24,7 @@
private readonly IServiceProvider _provider = provider;


public void Execute(
public int Execute(

Check warning on line 27 in src/Commands/ComparerCommand.cs

View workflow job for this annotation

GitHub Actions / Sonar Scanner and Mutation Tests

Refactor this method to reduce its Cognitive Complexity from 38 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)
string? baseline,
string? target,
string? meanThreshold,
Expand Down Expand Up @@ -166,5 +166,8 @@
.GetRequiredKeyedService<IExporter>(format.ToLower())
.Generate(comparerReport, output);
}


return 0; // Success exit code
}
}
12 changes: 12 additions & 0 deletions src/Options/AllocationThresholdOption.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.CommandLine;

namespace PowerUtils.BenchmarkDotnet.Reporter.Options;

public sealed class AllocationThresholdOption : Option<string>
{
public AllocationThresholdOption()
: base("--threshold-allocation", "-ta")
{
Description = "Throw an error when the allocation threshold is met. Examples: 5%, 10b, 10kb, 100mb, 1gb.";
}
}
13 changes: 13 additions & 0 deletions src/Options/BaselineOption.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.CommandLine;

namespace PowerUtils.BenchmarkDotnet.Reporter.Options;

public sealed class BaselineOption : Option<string>
{
public BaselineOption()
: base("--baseline", "-b")
{
Description = "Path to the folder or file with Baseline report.";
Required = true;
}
}
31 changes: 31 additions & 0 deletions src/Options/FormatsOption.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.Linq;

namespace PowerUtils.BenchmarkDotnet.Reporter.Options;

public sealed class FormatsOption : Option<string[]>
{
private static readonly HashSet<string> _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);
foreach(var value in values)
{
if(!_allowedValues.Contains(value))
{
result.AddError($"Invalid format '{value}'. Allowed values: {string.Join(", ", _allowedValues)}");
}
}
});
}
}
12 changes: 12 additions & 0 deletions src/Options/MeanThresholdOption.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.CommandLine;

namespace PowerUtils.BenchmarkDotnet.Reporter.Options;

public sealed class MeanThresholdOption : Option<string>
{
public MeanThresholdOption()
: base("--threshold-mean", "-tm")
{
Description = "Throw an error when the mean threshold is met. Examples: 5%, 10ms, 10μs, 100ns, 1s.";
}
}
13 changes: 13 additions & 0 deletions src/Options/OutputOption.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.CommandLine;

namespace PowerUtils.BenchmarkDotnet.Reporter.Options;

public sealed class OutputOption : Option<string>
{
public OutputOption()
: base("--output", "-o")
{
Description = "Output directory to export the diff report. Default is current directory.";
DefaultValueFactory = _ => "./BenchmarkReporter";
}
}
13 changes: 13 additions & 0 deletions src/Options/TargetOption.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.CommandLine;

namespace PowerUtils.BenchmarkDotnet.Reporter.Options;

public sealed class TargetOption : Option<string>
{
public TargetOption()
: base("--target", "-t")
{
Description = "Path to the folder or file with target reports.";
Required = true;
}
}
2 changes: 1 addition & 1 deletion src/PowerUtils.BenchmarkDotnet.Reporter.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
<ItemGroup>
<PackageReference Include="MarkdownLog" Version="0.9.64" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta5.25306.1" />
</ItemGroup>


Expand Down
8 changes: 5 additions & 3 deletions src/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.CommandLine;
using System.Globalization;
using System.Threading;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -15,6 +14,7 @@

var serviceCollection = new ServiceCollection();
serviceCollection
.AddTransient<ToolCommands>()
.AddTransient<IOHelpers.Printer>(sp =>
(message) => IOHelpers.Print(message))
.AddTransient<IOHelpers.FileWriter>(sp =>
Expand All @@ -28,5 +28,7 @@
.AddKeyedTransient<IExporter, ConsoleExporter>("console")
.AddTransient<IComparerCommand, ComparerCommand>();

var tool = new ToolCommands(serviceCollection.BuildServiceProvider());
tool.Invoke(args);
return serviceCollection.BuildServiceProvider()
.GetRequiredService<ToolCommands>()
.Parse(args)
.Invoke();
79 changes: 18 additions & 61 deletions src/ToolCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@
using System.CommandLine;
using Microsoft.Extensions.DependencyInjection;
using PowerUtils.BenchmarkDotnet.Reporter.Commands;
using PowerUtils.BenchmarkDotnet.Reporter.Options;

namespace PowerUtils.BenchmarkDotnet.Reporter;

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",
Expand All @@ -30,60 +31,16 @@ public ToolCommands(IServiceProvider provider)
output
};

compareCommand
.SetHandler(
provider.GetRequiredService<IComparerCommand>().Execute,
baseline,
target,
meanThreshold,
allocationThreshold,
formats,
output);

Add(compareCommand);
compareCommand.SetAction(parser => provider
.GetRequiredService<IComparerCommand>()
.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<string> _createBaselineOption()
=> new(
["-b", "--baseline"],
"Path to the folder or file with Baseline report.")
{
IsRequired = true
};

private static Option<string> _createTargetOption()
=> new(
["-t", "--target"],
"Path to the folder or file with target reports.")
{
IsRequired = true
};

private static Option<string> _createMeanThresholdOption()
=> new(
["-tm", "--threshold-mean"],
"Throw an error when the mean threshold is met. Examples: 5%, 10ms, 10μs, 100ns, 1s.");

private static Option<string> _createAllocationThresholdOption()
=> new(
["-ta", "--threshold-allocation"],
"Throw an error when the allocation threshold is met. Examples: 5%, 10b, 10kb, 100mb, 1gb.");

private static Option<string[]> _createFormatsOption()
=> new Option<string[]>(
["-f", "--format"],
() => ["console"],
"Output format for the report.")
.FromAmong(
"console",
"markdown",
"json",
"hit-txt");

private static Option<string> _createOutputOption()
=> new(
["-o", "--output"],
() => "./BenchmarkReporter",
"Output directory to export the diff report. Default is current directory.");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
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<IServiceProvider>();
var command = Substitute.For<IComparerCommand>();

_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.");
(formatsOption.GetDefaultValue() as string[]).ShouldBe(["console"]);
}

[Theory]
[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";
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(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");
}

[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'.");
}
}
Loading
Loading