From 9f1f8bef31bb7bdf3ec2d54be953a3461169872c Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 16:22:02 +0100 Subject: [PATCH 01/22] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20simplify?= =?UTF-8?q?=20file=20handling=20in=20benchmark=20workspace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BenchmarkWorkspace.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspace.cs b/src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspace.cs index d4d0263..2dfc6b3 100644 --- a/src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspace.cs +++ b/src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspace.cs @@ -188,11 +188,10 @@ private static void CleanupResults(string reportsResultsPath, string reportsTuni Directory.CreateDirectory(reportsTuningPath); - foreach (var file in Directory.GetFiles(reportsResultsPath).Where(s => !s.EndsWith(".lock"))) + foreach (var file in Directory.GetFiles(reportsResultsPath)) { var targetFile = Path.Combine(reportsTuningPath, Path.GetFileName(file)); - File.Delete(targetFile); - File.Move(file, targetFile); + File.Move(file, targetFile, true); } Directory.Delete(reportsResultsPath, recursive: true); From b0f881d3974852478a410db21e6c148383b74288 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 16:22:27 +0100 Subject: [PATCH 02/22] =?UTF-8?q?=F0=9F=A7=B9=20refactor:=20remove=20danis?= =?UTF-8?q?h=20culture=20from=20benchmark=20options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BenchmarkWorkspaceOptions.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspaceOptions.cs b/src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspaceOptions.cs index 3321d0a..16db916 100644 --- a/src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspaceOptions.cs +++ b/src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspaceOptions.cs @@ -87,7 +87,6 @@ public class BenchmarkWorkspaceOptions : IValidatableParameterObject, IPostConfi private static readonly string DefaultRepositoryPath = GetDefaultRepositoryPath(); private static readonly string DefaultTargetFrameworkMoniker = ResolveCurrentTfm(); - private static readonly CultureInfo DanishCulture = CultureInfo.GetCultureInfo("da-DK"); /// /// Initializes a new instance of the class with sensible defaults. @@ -221,7 +220,7 @@ private static ManualConfig GetDefaultConfiguration() .AddJob(Slim.AsDefault()) // tell BDN that this are our default settings .AddDiagnoser(MemoryDiagnoser.Default) // MemoryDiagnoser is enabled by default .AddColumn(StatisticColumn.Median, StatisticColumn.Min, StatisticColumn.Max) - .WithSummaryStyle(SummaryStyle.Default.WithMaxParameterColumnWidth(36).WithCultureInfo(DanishCulture)); // the default is 20 and trims too aggressively some benchmark results + .WithSummaryStyle(SummaryStyle.Default.WithMaxParameterColumnWidth(36)); // the default is 20 and trims too aggressively some benchmark results config.Options = ConfigOptions.DisableLogFile; return config; } From d5809b9dc34793dbc9cc729ac40bc56eb70bdc2d Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 16:28:05 +0100 Subject: [PATCH 03/22] =?UTF-8?q?=E2=9C=A8=20enhance=20benchmark=20run=20m?= =?UTF-8?q?ethod=20with=20service=20configurator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BenchmarkProgram.cs | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkProgram.cs b/src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkProgram.cs index dc35e07..2289711 100644 --- a/src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkProgram.cs +++ b/src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkProgram.cs @@ -43,7 +43,21 @@ static BenchmarkProgram() /// public static void Run(string[] args, Action setup = null) { - Run(args, setup); + Run(args, null, setup); + } + + /// + /// Runs benchmarks using the default implementation. + /// + /// The command-line arguments passed to the application. + /// The delegate that will be invoked to configure additional services in the . + /// The which may be configured. + /// + /// This method configures the host builder with the necessary services, builds the host, and runs it to execute benchmarks. + /// + public static void Run(string[] args, Action serviceConfigurator = null, Action setup = null) + { + Run(args, serviceConfigurator, setup); } /// @@ -56,12 +70,28 @@ public static void Run(string[] args, Action setup = /// This method configures the host builder with the necessary services, builds the host, and runs it to execute benchmarks. /// public static void Run(string[] args, Action setup = null) where TWorkspace : class, IBenchmarkWorkspace + { + Run(args, null, setup); + } + + /// + /// Runs benchmarks using a custom implementation of . + /// + /// The type of the workspace that implements . + /// The command-line arguments passed to the application. + /// The delegate that will be invoked to configure additional services in the . + /// The which may be configured. + /// + /// This method configures the host builder with the necessary services, builds the host, and runs it to execute benchmarks. + /// + public static void Run(string[] args, Action serviceConfigurator = null, Action setup = null) where TWorkspace : class, IBenchmarkWorkspace { var hostBuilder = CreateHostBuilder(args); hostBuilder.ConfigureServices(services => { services.AddSingleton(new BenchmarkContext(args)); services.AddBenchmarkWorkspace(setup); + serviceConfigurator?.Invoke(services); }); var host = hostBuilder.Build(); host.Run(); From 9168aea24f4fbe560f8365441c470841ed7498cc Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 16:28:45 +0100 Subject: [PATCH 04/22] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20conditionally=20supp?= =?UTF-8?q?ress=20console=20status=20messages=20in=20services?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BenchmarkWorker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkWorker.cs b/src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkWorker.cs index e0e0f23..d9d4288 100644 --- a/src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkWorker.cs +++ b/src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkWorker.cs @@ -33,7 +33,7 @@ public BenchmarkWorker(IConfiguration configuration, IHostEnvironment environmen /// public override void ConfigureServices(IServiceCollection services) { - services.Configure(o => o.SuppressStatusMessages = true); + services.Configure(o => o.SuppressStatusMessages = !BenchmarkProgram.IsDebugBuild); } /// From 84a2fcbaa3944de496937747420d5b9fe922f246 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 16:29:37 +0100 Subject: [PATCH 05/22] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20skip=20entry=20assem?= =?UTF-8?q?bly=20depending=20tests=20when=20running=20under=20resharper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BenchmarkProgramTest.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/Codebelt.Extensions.BenchmarkDotNet.Console.Tests/BenchmarkProgramTest.cs b/test/Codebelt.Extensions.BenchmarkDotNet.Console.Tests/BenchmarkProgramTest.cs index 0e52f8d..d89dca7 100644 --- a/test/Codebelt.Extensions.BenchmarkDotNet.Console.Tests/BenchmarkProgramTest.cs +++ b/test/Codebelt.Extensions.BenchmarkDotNet.Console.Tests/BenchmarkProgramTest.cs @@ -57,6 +57,13 @@ public void BuildConfiguration_ShouldBeDebug_WhenCompiledInDebugMode() return; } + if (entryAssembly.FullName.Contains("ReSharper", StringComparison.OrdinalIgnoreCase)) + { + // Skip test if running under ReSharper + TestOutput.WriteLine("Running under ReSharper, skipping test"); + return; + } + // Act var buildConfiguration = BenchmarkProgram.BuildConfiguration; @@ -82,6 +89,13 @@ public void IsDebugBuild_ShouldBeTrue_WhenCompiledInDebugMode() return; } + if (entryAssembly.FullName.Contains("ReSharper", StringComparison.OrdinalIgnoreCase)) + { + // Skip test if running under ReSharper + TestOutput.WriteLine("Running under ReSharper, skipping test"); + return; + } + // Act var isDebugBuild = BenchmarkProgram.IsDebugBuild; From 1e0ff384e56133cbb5f07d7efeb8941c9b3c0842 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 16:29:51 +0100 Subject: [PATCH 06/22] =?UTF-8?q?=F0=9F=A7=B9=20remove=20danish=20culture?= =?UTF-8?q?=20info=20test=20from=20benchmark=20options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BenchmarkWorkspaceOptionsTest.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/test/Codebelt.Extensions.BenchmarkDotNet.Tests/BenchmarkWorkspaceOptionsTest.cs b/test/Codebelt.Extensions.BenchmarkDotNet.Tests/BenchmarkWorkspaceOptionsTest.cs index afa9657..dee5b35 100644 --- a/test/Codebelt.Extensions.BenchmarkDotNet.Tests/BenchmarkWorkspaceOptionsTest.cs +++ b/test/Codebelt.Extensions.BenchmarkDotNet.Tests/BenchmarkWorkspaceOptionsTest.cs @@ -583,20 +583,6 @@ public void Configuration_ShouldHaveCustomSummaryStyle() TestOutput.WriteLine($"MaxParameterColumnWidth: {options.Configuration.SummaryStyle.MaxParameterColumnWidth}"); } - [Fact] - public void Configuration_ShouldHaveDanishCultureInfo() - { - // Arrange & Act - var options = new BenchmarkWorkspaceOptions(); - - // Assert - Assert.NotNull(options.Configuration.SummaryStyle); - Assert.NotNull(options.Configuration.SummaryStyle.CultureInfo); - Assert.Equal("da-DK", options.Configuration.SummaryStyle.CultureInfo.Name); - - TestOutput.WriteLine($"CultureInfo: {options.Configuration.SummaryStyle.CultureInfo.Name}"); - } - [Fact] public void Configuration_ShouldHaveDisabledLogFile() { From 6fea611f8cbb5b127b77d36c01681fa47a4f47de Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 18:08:01 +0100 Subject: [PATCH 07/22] =?UTF-8?q?=E2=9C=A8=20add=20methods=20to=20get=20be?= =?UTF-8?q?nchmark=20reports=20and=20tuning=20paths?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BenchmarkWorkspace.cs | 32 +++- .../BenchmarkWorkspaceTest.cs | 174 ++++++++++++++++++ 2 files changed, 203 insertions(+), 3 deletions(-) diff --git a/src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspace.cs b/src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspace.cs index 2dfc6b3..a93339a 100644 --- a/src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspace.cs +++ b/src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspace.cs @@ -21,6 +21,34 @@ public sealed class BenchmarkWorkspace : IBenchmarkWorkspace private static readonly Lock AssemblyResolverLock = new(); private static Dictionary _assemblyLookup = new(StringComparer.OrdinalIgnoreCase); + /// + /// Gets the path to the BenchmarkDotNet results directory + /// + /// The which configures repository paths, build modes and BenchmarkDotNet configuration. + /// A string containing the full path to the 'results' directory within the artifacts path. + /// + /// This path is constructed from the configured and the "results" subdirectory. + /// + public static string GetReportsResultsPath(BenchmarkWorkspaceOptions options) + { + Validator.ThrowIfInvalidOptions(options); + return Path.Combine(options.Configuration.ArtifactsPath, "results"); + } + + /// + /// Gets the path to the tuning folder where final benchmark reports are stored. + /// + /// The which configures repository paths, build modes and BenchmarkDotNet configuration. + /// A string representing the full path to the tuning reports directory. + /// + /// This path is constructed from the configured and the . + /// + public static string GetReportsTuningPath(BenchmarkWorkspaceOptions options) + { + Validator.ThrowIfInvalidOptions(options); + return Path.Combine(options.Configuration.ArtifactsPath, options.RepositoryTuningFolder); + } + /// /// Initializes a new instance of the class with the specified options. /// @@ -74,9 +102,7 @@ public Assembly[] LoadBenchmarkAssemblies() /// public void PostProcessArtifacts() { - var reportsResultsPath = Path.Combine(_options.Configuration.ArtifactsPath, "results"); - var reportsTuningPath = Path.Combine(_options.Configuration.ArtifactsPath, _options.RepositoryTuningFolder); - CleanupResults(reportsResultsPath, reportsTuningPath); + CleanupResults(GetReportsResultsPath(_options), GetReportsTuningPath(_options)); } private static IEnumerable LoadAssemblies(string repositoryPath, string targetFrameworkMoniker, string benchmarkProjectSuffix, string repositoryTuningFolder, bool useDebugBuild) diff --git a/test/Codebelt.Extensions.BenchmarkDotNet.Tests/BenchmarkWorkspaceTest.cs b/test/Codebelt.Extensions.BenchmarkDotNet.Tests/BenchmarkWorkspaceTest.cs index 9f07e74..ab420a5 100644 --- a/test/Codebelt.Extensions.BenchmarkDotNet.Tests/BenchmarkWorkspaceTest.cs +++ b/test/Codebelt.Extensions.BenchmarkDotNet.Tests/BenchmarkWorkspaceTest.cs @@ -627,4 +627,178 @@ public void LoadBenchmarkAssemblies_ShouldHandleAssemblyLoadFailuresGracefully() } } } + + [Fact] + public void GetReportsResultsPath_ShouldReturnCorrectPath_WhenOptionsAreValid() + { + // Arrange + var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + try + { + Directory.CreateDirectory(tempPath); + var artifactsPath = Path.Combine(tempPath, "artifacts"); + var config = ManualConfig.CreateEmpty().WithArtifactsPath(artifactsPath); + var options = new BenchmarkWorkspaceOptions + { + RepositoryPath = tempPath, + Configuration = config + }; + + // Act + var resultsPath = BenchmarkWorkspace.GetReportsResultsPath(options); + + // Assert + var expectedPath = Path.Combine(artifactsPath, "results"); + Assert.Equal(expectedPath, resultsPath); + + TestOutput.WriteLine($"Results path: {resultsPath}"); + } + finally + { + if (Directory.Exists(tempPath)) + { + Directory.Delete(tempPath, true); + } + } + } + + [Fact] + public void GetReportsResultsPath_ShouldThrowArgumentException_WhenOptionsAreInvalid() + { + // Arrange + var options = new BenchmarkWorkspaceOptions + { + RepositoryPath = null + }; + + // Act & Assert + Assert.Throws(() => BenchmarkWorkspace.GetReportsResultsPath(options)); + } + + [Fact] + public void GetReportsResultsPath_ShouldThrowArgumentNullException_WhenOptionsIsNull() + { + // Arrange + BenchmarkWorkspaceOptions options = null; + + // Act & Assert + Assert.Throws(() => BenchmarkWorkspace.GetReportsResultsPath(options)); + } + + [Fact] + public void GetReportsTuningPath_ShouldReturnCorrectPath_WhenOptionsAreValid() + { + // Arrange + var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + try + { + Directory.CreateDirectory(tempPath); + var artifactsPath = Path.Combine(tempPath, "artifacts"); + var config = ManualConfig.CreateEmpty().WithArtifactsPath(artifactsPath); + var options = new BenchmarkWorkspaceOptions + { + RepositoryPath = tempPath, + Configuration = config, + RepositoryTuningFolder = "tuning" + }; + + // Act + var tuningPath = BenchmarkWorkspace.GetReportsTuningPath(options); + + // Assert + var expectedPath = Path.Combine(artifactsPath, "tuning"); + Assert.Equal(expectedPath, tuningPath); + + TestOutput.WriteLine($"Tuning path: {tuningPath}"); + } + finally + { + if (Directory.Exists(tempPath)) + { + Directory.Delete(tempPath, true); + } + } + } + + [Fact] + public void GetReportsTuningPath_ShouldThrowArgumentException_WhenOptionsAreInvalid() + { + // Arrange + var options = new BenchmarkWorkspaceOptions + { + RepositoryPath = null + }; + + // Act & Assert + Assert.Throws(() => BenchmarkWorkspace.GetReportsTuningPath(options)); + } + + [Fact] + public void GetReportsTuningPath_ShouldThrowArgumentNullException_WhenOptionsIsNull() + { + // Arrange + BenchmarkWorkspaceOptions options = null; + + // Act & Assert + Assert.Throws(() => BenchmarkWorkspace.GetReportsTuningPath(options)); + } + + [Fact] + public void GetReportsTuningPath_ShouldUseCustomTuningFolder_WhenSpecified() + { + // Arrange + var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + try + { + Directory.CreateDirectory(tempPath); + var artifactsPath = Path.Combine(tempPath, "artifacts"); + var customTuningFolder = "custom-tuning"; + var config = ManualConfig.CreateEmpty().WithArtifactsPath(artifactsPath); + var options = new BenchmarkWorkspaceOptions + { + RepositoryPath = tempPath, + Configuration = config, + RepositoryTuningFolder = customTuningFolder + }; + + // Act + var tuningPath = BenchmarkWorkspace.GetReportsTuningPath(options); + + // Assert + var expectedPath = Path.Combine(artifactsPath, customTuningFolder); + Assert.Equal(expectedPath, tuningPath); + Assert.Contains(customTuningFolder, tuningPath); + + TestOutput.WriteLine($"Custom tuning path: {tuningPath}"); + } + finally + { + if (Directory.Exists(tempPath)) + { + Directory.Delete(tempPath, true); + } + } + } + + [Fact] + public void GetReportsResultsPath_AndGetReportsTuningPath_ShouldReturnDifferentPaths() + { + // Arrange + var options = new BenchmarkWorkspaceOptions + { + AllowDebugBuild = IsDebugBuild + }; + + // Act + var resultsPath = BenchmarkWorkspace.GetReportsResultsPath(options); + var tuningPath = BenchmarkWorkspace.GetReportsTuningPath(options); + + // Assert + Assert.NotEqual(resultsPath, tuningPath); + Assert.Contains("results", resultsPath); + Assert.Contains("tuning", tuningPath); + + TestOutput.WriteLine($"Results path: {resultsPath}"); + TestOutput.WriteLine($"Tuning path: {tuningPath}"); + } } From 7a9dfe6033ddf7bfadde8dc2a09ec1096e53717d Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 18:08:19 +0100 Subject: [PATCH 08/22] =?UTF-8?q?=E2=9C=A8=20add=20skip=20option=20for=20b?= =?UTF-8?q?enchmarks=20with=20existing=20reports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BenchmarkWorkspaceOptions.cs | 12 ++++++++++ .../BenchmarkWorkspaceOptionsTest.cs | 23 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspaceOptions.cs b/src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspaceOptions.cs index 16db916..e4f3201 100644 --- a/src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspaceOptions.cs +++ b/src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspaceOptions.cs @@ -55,6 +55,10 @@ namespace Codebelt.Extensions.BenchmarkDotNet; /// /// false /// +/// +/// +/// false +/// /// /// public class BenchmarkWorkspaceOptions : IValidatableParameterObject, IPostConfigurableParameterObject @@ -146,6 +150,14 @@ public BenchmarkWorkspaceOptions() /// true to allow Debug builds for benchmarks; otherwise, false. Default is false. /// public bool AllowDebugBuild { get; set; } + + /// + /// Gets or sets a value indicating whether benchmarks that already have generated reports should be skipped during execution. + /// + /// + /// true to skip benchmarks with existing reports; otherwise, false. Default is false. + /// + public bool SkipBenchmarksWithReports { get; set; } /// /// Finalizes the configured options before use. diff --git a/test/Codebelt.Extensions.BenchmarkDotNet.Tests/BenchmarkWorkspaceOptionsTest.cs b/test/Codebelt.Extensions.BenchmarkDotNet.Tests/BenchmarkWorkspaceOptionsTest.cs index dee5b35..5022c6f 100644 --- a/test/Codebelt.Extensions.BenchmarkDotNet.Tests/BenchmarkWorkspaceOptionsTest.cs +++ b/test/Codebelt.Extensions.BenchmarkDotNet.Tests/BenchmarkWorkspaceOptionsTest.cs @@ -632,4 +632,27 @@ public void Configuration_ShouldHaveSlimJobAsDefault() TestOutput.WriteLine($"Default Job - WarmupCount: {jobs[0].Run.WarmupCount}"); } + + [Fact] + public void SkipBenchmarksWithReports_ShouldDefaultToFalse() + { + // Arrange & Act + var options = new BenchmarkWorkspaceOptions(); + + // Assert + Assert.False(options.SkipBenchmarksWithReports); + } + + [Fact] + public void SkipBenchmarksWithReports_ShouldBeSettable() + { + // Arrange + var options = new BenchmarkWorkspaceOptions(); + + // Act + options.SkipBenchmarksWithReports = true; + + // Assert + Assert.True(options.SkipBenchmarksWithReports); + } } From 408f8084609101f6cfa327955b371e326d539028 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 18:09:13 +0100 Subject: [PATCH 09/22] =?UTF-8?q?=E2=9C=A8=20add=20option=20to=20skip=20be?= =?UTF-8?q?nchmarks=20with=20existing=20reports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BenchmarkWorker.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkWorker.cs b/src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkWorker.cs index d9d4288..6965c18 100644 --- a/src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkWorker.cs +++ b/src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkWorker.cs @@ -4,8 +4,12 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; +using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Filters; namespace Codebelt.Extensions.BenchmarkDotNet.Console { @@ -53,6 +57,32 @@ public override Task RunAsync(IServiceProvider serviceProvider, CancellationToke var assemblies = workspace.LoadBenchmarkAssemblies(); var context = serviceProvider.GetRequiredService(); + if (options.SkipBenchmarksWithReports) + { + var benchmarkTypes = assemblies.SelectMany(a => a.GetTypes().Where(t => t.Name.EndsWith("Benchmark"))).ToList(); + options.ConfigureBenchmarkDotNet(c => + { + var reports = Directory.EnumerateFiles(BenchmarkWorkspace.GetReportsTuningPath(options)); + foreach (var report in reports) + { + var filename = Path.GetFileNameWithoutExtension(report); + var potentialTypeFullName = filename.Split('-').FirstOrDefault(); + if (string.IsNullOrWhiteSpace(potentialTypeFullName)) { continue; } + + var potentialTypeName = potentialTypeFullName.Split('.').LastOrDefault(); + if (string.IsNullOrWhiteSpace(potentialTypeName)) { continue; } + + var matchingType = benchmarkTypes.SingleOrDefault(t => t.Name.Equals(potentialTypeName, StringComparison.OrdinalIgnoreCase)); + + if (matchingType != null) + { + c = c.AddFilter(new SimpleFilter(bc => bc.Descriptor.Type != matchingType)); + } + } + return c; + }); + } + try { if (context.Args.Length == 0) From 0e0d6bec3a455d904fd049d769ce7d2e1b69b2e6 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 18:11:29 +0100 Subject: [PATCH 10/22] :arrow_up: bump dependencies --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index e454e68..3a92f84 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,7 +7,7 @@ - + From d5a7839c00e4c40a18b6286fa733a45eaf3cc497 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 18:41:55 +0100 Subject: [PATCH 11/22] :package: updated NuGet package definition --- .../PackageReleaseNotes.txt | 13 ++++++++++++- .../PackageReleaseNotes.txt | 15 ++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/.nuget/Codebelt.Extensions.BenchmarkDotNet.Console/PackageReleaseNotes.txt b/.nuget/Codebelt.Extensions.BenchmarkDotNet.Console/PackageReleaseNotes.txt index 59e6169..d100530 100644 --- a/.nuget/Codebelt.Extensions.BenchmarkDotNet.Console/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.BenchmarkDotNet.Console/PackageReleaseNotes.txt @@ -1,6 +1,17 @@ +Version: 1.1.0 +Availability: .NET 10 and .NET 9 +  +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) +  +# Improvements +- EXTENDED BenchmarkProgram class in the Codebelt.Extensions.BenchmarkDotNet.Console namespace to support an optional service configurator delegate for customizing the IServiceCollection during host building +- EXTENDED BenchmarkWorker class in the Codebelt.Extensions.BenchmarkDotNet.Console namespace to support skipping benchmarks that already have generated reports based on the BenchmarkWorkspaceOptions.SkipBenchmarksWithReports property +- CHANGED BenchmarkWorker class in the Codebelt.Extensions.BenchmarkDotNet.Console namespace to conditionally suppress console status messages in services based on wether you are in a debugging session or not +  Version: 1.0.0 Availability: .NET 10 and .NET 9 - +  # New Features - ADDED BenchmarkContext class in the Codebelt.Extensions.BenchmarkDotNet.Console namespace that represents the command-line context for a benchmark run - ADDED BenchmarkProgram class in the Codebelt.Extensions.BenchmarkDotNet.Console namespace that provides the main entry point for hosting and running benchmarks using BenchmarkDotNet diff --git a/.nuget/Codebelt.Extensions.BenchmarkDotNet/PackageReleaseNotes.txt b/.nuget/Codebelt.Extensions.BenchmarkDotNet/PackageReleaseNotes.txt index 42108fa..ed4e30e 100644 --- a/.nuget/Codebelt.Extensions.BenchmarkDotNet/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.BenchmarkDotNet/PackageReleaseNotes.txt @@ -1,6 +1,19 @@ +Version: 1.1.0 +Availability: .NET 10 and .NET 9 +  +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) +  +# Bug Fixes +- FIXED BenchmarkWorkspaceOptions class in the Codebelt.Extensions.BenchmarkDotNet namespace so it no longer relies on Danish culture +  +# Improvements +- EXTENDED BenchmarkWorkspace class in the Codebelt.Extensions.BenchmarkDotNet namespace with two new static methods; GetReportsResultsPath and GetReportsTuningPath for retrieving the paths to the reports results and tuning directories respectively +- EXTENDED BenchmarkWorkspaceOptions class in the Codebelt.Extensions.BenchmarkDotNet namespace to include one new property; SkipBenchmarksWithReports that indicates whether benchmarks that already have generated reports should be skipped during execution +  Version: 1.0.0 Availability: .NET 10 and .NET 9 - +  # New Features - ADDED BenchmarkWorkspace class in the Codebelt.Extensions.BenchmarkDotNet namespace that provides a default implementation of IBenchmarkWorkspace for discovering and handling assemblies and their generated artifacts in BenchmarkDotNet - ADDED BenchmarkWorkspaceOptions class in the Codebelt.Extensions.BenchmarkDotNet namespace that specifies configuration options that is related to the BenchmarkWorkspace class From d846b10b2242a8af3fdc6f831f280e4e4e9d924b Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 18:59:19 +0100 Subject: [PATCH 12/22] :art: update icon for benchmarkdotnet console application --- .../icon.png | Bin 5486 -> 4280 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.nuget/Codebelt.Extensions.BenchmarkDotNet.Console/icon.png b/.nuget/Codebelt.Extensions.BenchmarkDotNet.Console/icon.png index 0b304b4da142478b36afec0c21a0502950bbb2e1..d3af8d6d8e266cf0679688395da8e6cc168025b8 100644 GIT binary patch delta 4197 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 P00000NkvXXu0mjf(P&nw From 47dd6f77f16c7deaf3d468845596c4131b61d1bb Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 18:59:37 +0100 Subject: [PATCH 13/22] :speech_balloon: updated community health pages --- .github/CONTRIBUTING.md | 4 ++-- CHANGELOG.md | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 12f8f3c..414681e 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing to `Classlibrary1` +# Contributing to `BenchmarkDotNet Extensions by Codebelt` When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change. @@ -48,5 +48,5 @@ That is, in pursuit of the items on the left we have found the items on the righ [Manifesto for Software Craftsmanship](https://manifesto.softwarecraftsmanship.org/) is the originator of this text. ## License -By contributing to `ClassLibrary1`, you agree that your contributions will be licensed +By contributing to `BenchmarkDotNet Extensions by Codebelt`, you agree that your contributions will be licensed under the MIT license. diff --git a/CHANGELOG.md b/CHANGELOG.md index f41a179..ef5203d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), For more details, please refer to `PackageReleaseNotes.txt` on a per assembly basis in the `.nuget` folder. +## [1.1.0] - 2025-12-14 + +### Changed + +- `BenchmarkWorkspace` class in the Codebelt.Extensions.BenchmarkDotNet namespace was extended with two new static methods; `GetReportsResultsPath` and `GetReportsTuningPath` for retrieving the paths to the reports results and tuning directories respectively, +- `BenchmarkWorkspaceOptions` class in the Codebelt.Extensions.BenchmarkDotNet namespace was extended with an additional new property; `SkipBenchmarksWithReports` that indicates whether benchmarks that already have generated reports should be skipped during execution, +- `BenchmarkProgram` class in the Codebelt.Extensions.BenchmarkDotNet.Console namespace was extended to support an optional service configurator delegate for customizing the `IServiceCollection` during host building, +- `BenchmarkWorker` class in the Codebelt.Extensions.BenchmarkDotNet.Console namespace was extended to support skipping benchmarks that already have generated reports based on the `BenchmarkWorkspaceOptions.SkipBenchmarksWithReports` property, +- `BenchmarkWorker` class in the Codebelt.Extensions.BenchmarkDotNet.Console namespace was changed to conditionally suppress console status messages in services based on wether you are in a debugging session or not. + +### Fixed + +- `BenchmarkWorkspaceOptions` class in the Codebelt.Extensions.BenchmarkDotNet namespace was fixed so it no longer relies on Danish culture. + ## [1.0.0] - 2025-12-12 This is the initial stable release of the `Codebelt.Extensions.BenchmarkDotNet` and `Codebelt.Extensions.BenchmarkDotNet.Console` packages. From c4d8af556b6dd5aa17638d3fac0ebd6f585e777f Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 18:59:57 +0100 Subject: [PATCH 14/22] =?UTF-8?q?=E2=9C=A8=20update=20main=20author=20emai?= =?UTF-8?q?l=20in=20Directory.Build.props?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 1516ab1..9f59c55 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ $(MSBuildProjectDirectory.ToLower().StartsWith('$(MSBuildThisFileDirectory.ToLower())tooling')) $([MSBuild]::IsOSPlatform('Linux')) $([MSBuild]::IsOSPlatform('Windows')) - true + true false ..\..\.nuget\$(MSBuildProjectName)\PackageReleaseNotes.txt latest From e4dd07cdb2a094749d9ecfbbee25a6cc18a40020 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 19:11:06 +0100 Subject: [PATCH 15/22] :white_check_mark: make SuppressStatusMessages test build-config aware --- .../BenchmarkWorkerTest.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/Codebelt.Extensions.BenchmarkDotNet.Console.Tests/BenchmarkWorkerTest.cs b/test/Codebelt.Extensions.BenchmarkDotNet.Console.Tests/BenchmarkWorkerTest.cs index 9a4f9f4..ed4ec63 100644 --- a/test/Codebelt.Extensions.BenchmarkDotNet.Console.Tests/BenchmarkWorkerTest.cs +++ b/test/Codebelt.Extensions.BenchmarkDotNet.Console.Tests/BenchmarkWorkerTest.cs @@ -161,7 +161,16 @@ public void ConfigureServices_ShouldSuppressStatusMessages() // Assert Assert.NotNull(options); - Assert.True(options.SuppressStatusMessages); + + if (BenchmarkProgram.IsDebugBuild) + { + Assert.False(options.SuppressStatusMessages); + } + else + { + Assert.True(options.SuppressStatusMessages); + } + TestOutput.WriteLine($"ConsoleLifetimeOptions.SuppressStatusMessages = {options.SuppressStatusMessages}"); } From e106a282c2336f8f2f8073f5c79c399a0092229e Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 19:33:14 +0100 Subject: [PATCH 16/22] Update .nuget/Codebelt.Extensions.BenchmarkDotNet.Console/PackageReleaseNotes.txt Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../PackageReleaseNotes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nuget/Codebelt.Extensions.BenchmarkDotNet.Console/PackageReleaseNotes.txt b/.nuget/Codebelt.Extensions.BenchmarkDotNet.Console/PackageReleaseNotes.txt index d100530..66033c2 100644 --- a/.nuget/Codebelt.Extensions.BenchmarkDotNet.Console/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.BenchmarkDotNet.Console/PackageReleaseNotes.txt @@ -7,7 +7,7 @@ Availability: .NET 10 and .NET 9 # Improvements - EXTENDED BenchmarkProgram class in the Codebelt.Extensions.BenchmarkDotNet.Console namespace to support an optional service configurator delegate for customizing the IServiceCollection during host building - EXTENDED BenchmarkWorker class in the Codebelt.Extensions.BenchmarkDotNet.Console namespace to support skipping benchmarks that already have generated reports based on the BenchmarkWorkspaceOptions.SkipBenchmarksWithReports property -- CHANGED BenchmarkWorker class in the Codebelt.Extensions.BenchmarkDotNet.Console namespace to conditionally suppress console status messages in services based on wether you are in a debugging session or not +- CHANGED BenchmarkWorker class in the Codebelt.Extensions.BenchmarkDotNet.Console namespace to conditionally suppress console status messages in services based on whether you are in a debugging session or not   Version: 1.0.0 Availability: .NET 10 and .NET 9 From 1f97e1a46d0d2695837a60eb146f0f59efac51c0 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 19:33:32 +0100 Subject: [PATCH 17/22] Update CHANGELOG.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef5203d..732f847 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ For more details, please refer to `PackageReleaseNotes.txt` on a per assembly ba - `BenchmarkWorkspaceOptions` class in the Codebelt.Extensions.BenchmarkDotNet namespace was extended with an additional new property; `SkipBenchmarksWithReports` that indicates whether benchmarks that already have generated reports should be skipped during execution, - `BenchmarkProgram` class in the Codebelt.Extensions.BenchmarkDotNet.Console namespace was extended to support an optional service configurator delegate for customizing the `IServiceCollection` during host building, - `BenchmarkWorker` class in the Codebelt.Extensions.BenchmarkDotNet.Console namespace was extended to support skipping benchmarks that already have generated reports based on the `BenchmarkWorkspaceOptions.SkipBenchmarksWithReports` property, -- `BenchmarkWorker` class in the Codebelt.Extensions.BenchmarkDotNet.Console namespace was changed to conditionally suppress console status messages in services based on wether you are in a debugging session or not. +- `BenchmarkWorker` class in the Codebelt.Extensions.BenchmarkDotNet.Console namespace was changed to conditionally suppress console status messages in services based on whether you are in a debugging session or not. ### Fixed From 45ea81de0f6b429cc0945f1adec0ec4b7be58941 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 19:52:47 +0100 Subject: [PATCH 18/22] :white_check_mark: enable skipping benchmarks with reports --- tooling/Codebelt.Extensions.BenchmarkDotNet.Runner/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tooling/Codebelt.Extensions.BenchmarkDotNet.Runner/Program.cs b/tooling/Codebelt.Extensions.BenchmarkDotNet.Runner/Program.cs index ef47eb4..d639e65 100644 --- a/tooling/Codebelt.Extensions.BenchmarkDotNet.Runner/Program.cs +++ b/tooling/Codebelt.Extensions.BenchmarkDotNet.Runner/Program.cs @@ -12,6 +12,7 @@ public static void Main(string[] args) BenchmarkProgram.Run(args, o => { o.AllowDebugBuild = BenchmarkProgram.IsDebugBuild; + o.SkipBenchmarksWithReports = true; o.ConfigureBenchmarkDotNet(c => { var slimJob = BenchmarkWorkspaceOptions.Slim; From f54af1e1093f56a8085e4a5c1c47ec05f4c0382e Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 19:53:07 +0100 Subject: [PATCH 19/22] =?UTF-8?q?=E2=9C=A8=20add=20guidelines=20to=20avoid?= =?UTF-8?q?=20internalsvisibleto=20in=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/copilot-instructions.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 9ef3035..27107cc 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -149,6 +149,37 @@ namespace Codebelt.Extensions.BenchmarkDotNet - Before overriding methods, verify that the method is virtual or abstract; this rule also applies to mocks. - Never mock IMarshaller; always use a new instance of JsonMarshaller. +## 9. Avoid `InternalsVisibleTo` in Tests + +- **Do not** use `InternalsVisibleTo` to access internal types or members from test projects. +- Prefer **indirect testing via public APIs** that depend on the internal implementation (public facades, public extension methods, or other public entry points). + +### Preferred Pattern + +**Pattern name:** Public Facade Testing (also referred to as *Public API Proxy Testing*) + +**Description:** +Internal classes and methods must be validated by exercising the public API that consumes them. Tests should assert observable behavior exposed by the public surface rather than targeting internal implementation details directly. + +### Example Mapping + +- **Internal helper:** `DelimitedString` (internal static class) +- **Public API:** `TestOutputHelperExtensions.WriteLines()` (public extension method) +- **Test strategy:** Write tests for `WriteLines()` and verify its public behavior. The internal call to `DelimitedString.Create()` is exercised implicitly. + +### Benefits + +- Avoids exposing internal types to test assemblies. +- Ensures tests reflect real-world usage patterns. +- Maintains strong encapsulation and a clean public API. +- Tests remain resilient to internal refactoring as long as public behavior is preserved. + +### When to Apply + +- Internal logic is fully exercised through existing public APIs. +- Public entry points provide sufficient coverage of internal code paths. +- The internal implementation exists solely as a helper or utility for public-facing functionality. + --- description: 'Writing Performance Tests in Codebelt.Extensions.BenchmarkDotNet' applyTo: "tuning/**, **/*Benchmark*.cs" From e7975bcd047c909c3528d5630d7d8a976ad839be Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 19:54:52 +0100 Subject: [PATCH 20/22] :bug: filter out benchmark run files from tuning reports directory --- src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspace.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspace.cs b/src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspace.cs index a93339a..58a2240 100644 --- a/src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspace.cs +++ b/src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspace.cs @@ -214,7 +214,7 @@ private static void CleanupResults(string reportsResultsPath, string reportsTuni Directory.CreateDirectory(reportsTuningPath); - foreach (var file in Directory.GetFiles(reportsResultsPath)) + foreach (var file in Directory.GetFiles(reportsResultsPath).Where(f => !Path.GetFileNameWithoutExtension(f).StartsWith("BenchmarkRun", StringComparison.Ordinal))) { var targetFile = Path.Combine(reportsTuningPath, Path.GetFileName(file)); File.Move(file, targetFile, true); From 852937f11a3582f03f2025245b61fd51a8392688 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 19:55:14 +0100 Subject: [PATCH 21/22] =?UTF-8?q?=F0=9F=94=A7=20use=20'using'=20declaratio?= =?UTF-8?q?n=20for=20host=20in=20benchmark=20run=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BenchmarkProgram.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkProgram.cs b/src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkProgram.cs index 2289711..e71b672 100644 --- a/src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkProgram.cs +++ b/src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkProgram.cs @@ -93,7 +93,7 @@ public static void Run(string[] args, Action ser services.AddBenchmarkWorkspace(setup); serviceConfigurator?.Invoke(services); }); - var host = hostBuilder.Build(); + using var host = hostBuilder.Build(); host.Run(); } } From 5f3e63e91b0dbe151d2eb18e3e50eb5f58270b1d Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 14 Dec 2025 19:55:39 +0100 Subject: [PATCH 22/22] =?UTF-8?q?=E2=9C=A8=20improve=20benchmark=20skippin?= =?UTF-8?q?g=20logic=20and=20add=20directory=20existence=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BenchmarkWorker.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkWorker.cs b/src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkWorker.cs index 6965c18..fb2ca5b 100644 --- a/src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkWorker.cs +++ b/src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkWorker.cs @@ -59,10 +59,12 @@ public override Task RunAsync(IServiceProvider serviceProvider, CancellationToke if (options.SkipBenchmarksWithReports) { - var benchmarkTypes = assemblies.SelectMany(a => a.GetTypes().Where(t => t.Name.EndsWith("Benchmark"))).ToList(); + var benchmarkTypes = assemblies.SelectMany(a => a.GetTypes().Where(t => t.Name.EndsWith("Benchmark", StringComparison.Ordinal))).ToList(); options.ConfigureBenchmarkDotNet(c => { - var reports = Directory.EnumerateFiles(BenchmarkWorkspace.GetReportsTuningPath(options)); + var tuningPath = BenchmarkWorkspace.GetReportsTuningPath(options); + if (!Directory.Exists(tuningPath)) { return c; } + var reports = Directory.EnumerateFiles(tuningPath); foreach (var report in reports) { var filename = Path.GetFileNameWithoutExtension(report); @@ -72,7 +74,7 @@ public override Task RunAsync(IServiceProvider serviceProvider, CancellationToke var potentialTypeName = potentialTypeFullName.Split('.').LastOrDefault(); if (string.IsNullOrWhiteSpace(potentialTypeName)) { continue; } - var matchingType = benchmarkTypes.SingleOrDefault(t => t.Name.Equals(potentialTypeName, StringComparison.OrdinalIgnoreCase)); + var matchingType = benchmarkTypes.FirstOrDefault(t => t.Name.Equals(potentialTypeName, StringComparison.OrdinalIgnoreCase)); if (matchingType != null) {