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
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
Version: 1.2.0
Availability: .NET 10 and .NET 9

# ALM
- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)

# Breaking Changes
- REMOVED BenchmarkWorker class in the Codebelt.Extensions.BenchmarkDotNet.Console namespace and functionality has been merged into the BenchmarkProgram class to streamline the hosting and execution of benchmarks

Version: 1.1.0
Availability: .NET 10 and .NET 9

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
Version: 1.2.0
Availability: .NET 10 and .NET 9

# ALM
- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Duplicate ALM entry between versions 1.2.0 and 1.1.0.

Both Version 1.2.0 (line 5) and Version 1.1.0 (line 11) contain identical wording: "Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)". Clarify whether:

  • This is a copy-paste error in 1.1.0's notes
  • Both versions genuinely have the same dependency change (and if so, whether 1.2.0's notes should be more specific)
  • The 1.1.0 notes need historical correction

Also applies to: 11-11

🤖 Prompt for AI Agents
In .nuget/Codebelt.Extensions.BenchmarkDotNet/PackageReleaseNotes.txt around
lines 5 and 11, there is a duplicate ALM entry ("Dependencies have been upgraded
to the latest compatible versions for all supported target frameworks (TFMs)")
present in both Version 1.2.0 (line 5) and Version 1.1.0 (line 11); determine
which version actually introduced that dependency change and then either remove
or rewrite the duplicate: if 1.2.0 is the correct entry, remove or replace the
1.1.0 sentence with a concise historical correction (e.g., accurate change text
or "no dependency updates"), or if 1.1.0 was correct, make 1.2.0 more specific
about what changed (package names/versions or scope) so the notes are not
identical; commit the updated PackageReleaseNotes.txt reflecting the chosen
clarification.


Version: 1.1.0
Availability: .NET 10 and .NET 9

Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,18 @@ 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.2.0] - 2025-12-19

Technically, this is a major release due to the removal of BenchmarkWorker class in the Codebelt.Extensions.BenchmarkDotNet.Console namespace. However, since this library is new and the external API remains unchanged, we have decided to label this release as a minor update.

### Removed

- `BenchmarkWorker` class in the Codebelt.Extensions.BenchmarkDotNet.Console namespace has been removed. Its functionality has been merged into the `BenchmarkProgram` class to streamline the hosting and execution of benchmarks.

## [1.1.0] - 2025-12-14

This release introduces several enhancements and fixes to improve the functionality and usability of the `Codebelt.Extensions.BenchmarkDotNet` and `Codebelt.Extensions.BenchmarkDotNet.Console` packages.

### 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,
Expand Down
4 changes: 2 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
<ItemGroup>
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
<PackageVersion Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.15.8" />
<PackageVersion Include="Codebelt.Bootstrapper.Console" Version="5.0.1" />
<PackageVersion Include="Codebelt.Extensions.Xunit.App" Version="11.0.2" />
<PackageVersion Include="Codebelt.Bootstrapper.Console" Version="5.0.2" />
<PackageVersion Include="Codebelt.Extensions.Xunit.App" Version="11.0.3" />
<PackageVersion Include="Cuemon.Core" Version="10.1.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageVersion Include="MinVer" Version="6.0.0" />
Expand Down

This file was deleted.

116 changes: 105 additions & 11 deletions src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkProgram.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
using Codebelt.Bootstrapper.Console;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Filters;
using BenchmarkDotNet.Running;
using Codebelt.Bootstrapper.Console;
using Cuemon;
using Cuemon.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace Codebelt.Extensions.BenchmarkDotNet.Console
{
/// <summary>
/// Entry point helper for hosting and running benchmarks using BenchmarkDotNet.
/// </summary>
/// <seealso cref="ConsoleProgram{TStartup}"/>
public class BenchmarkProgram : ConsoleProgram<BenchmarkWorker>
/// <seealso cref="MinimalConsoleProgram{BenchmarkProgram}"/>
public class BenchmarkProgram : MinimalConsoleProgram<BenchmarkProgram>
{
static BenchmarkProgram()
{
Expand Down Expand Up @@ -86,15 +94,101 @@ public static void Run<TWorkspace>(string[] args, Action<BenchmarkWorkspaceOptio
/// </remarks>
public static void Run<TWorkspace>(string[] args, Action<IServiceCollection> serviceConfigurator = null, Action<BenchmarkWorkspaceOptions> setup = null) where TWorkspace : class, IBenchmarkWorkspace
{
var hostBuilder = CreateHostBuilder(args);
hostBuilder.ConfigureServices(services =>
{
services.AddSingleton(new BenchmarkContext(args));
services.AddBenchmarkWorkspace<TWorkspace>(setup);
serviceConfigurator?.Invoke(services);
});
using var host = hostBuilder.Build();
var builder = CreateHostBuilder(args);

builder.Services.Configure<ConsoleLifetimeOptions>(o => o.SuppressStatusMessages = !IsDebugBuild);
builder.Services.AddSingleton(new BenchmarkContext(args));
builder.Services.AddBenchmarkWorkspace<TWorkspace>(setup);
serviceConfigurator?.Invoke(builder.Services);

using var host = builder.Build();
host.Run();
}

/// <summary>
/// Runs the actual benchmarks as envisioned by BenchmarkDotNet.
/// </summary>
/// <param name="serviceProvider">The service provider.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A completed task when benchmark execution has finished.</returns>
/// <remarks>
/// When arguments are provided, they are forwarded to <see cref="BenchmarkSwitcher"/> for selective execution.
/// After execution completes, artifact post-processing is performed.
/// </remarks>
public override Task RunAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken)
{
var options = serviceProvider.GetRequiredService<BenchmarkWorkspaceOptions>();
var workspace = serviceProvider.GetRequiredService<IBenchmarkWorkspace>();
var assemblies = workspace.LoadBenchmarkAssemblies();
var context = serviceProvider.GetRequiredService<BenchmarkContext>();

if (options.SkipBenchmarksWithReports) { ConfigureBenchmarkDotNetFiltersForExistingReports(options, assemblies); }

try
{
ExecuteBenchmarks(assemblies, context, options);
}
finally
{
workspace.PostProcessArtifacts();
}

return Task.CompletedTask;
}

private static void ConfigureBenchmarkDotNetFiltersForExistingReports(BenchmarkWorkspaceOptions options, Assembly[] assemblies)
{
var benchmarkTypes = assemblies
.SelectMany(a => a.GetTypes().Where(t => t.Name.EndsWith("Benchmark", StringComparison.Ordinal)))
.ToList();

options.ConfigureBenchmarkDotNet(c => ApplyReportFilters(c, options, benchmarkTypes));
}

private static IConfig ApplyReportFilters(IConfig config, BenchmarkWorkspaceOptions options, List<Type> benchmarkTypes)
{
var tuningPath = BenchmarkWorkspace.GetReportsTuningPath(options);
if (!Directory.Exists(tuningPath)) { return config; }

foreach (var report in Directory.EnumerateFiles(tuningPath))
{
var matchingType = FindMatchingBenchmarkType(report, benchmarkTypes);
if (matchingType != null)
{
config = config.AddFilter(new SimpleFilter(bc => bc.Descriptor.Type != matchingType));
}
}

return config;
}

private static Type FindMatchingBenchmarkType(string reportPath, List<Type> benchmarkTypes)
{
var filename = Path.GetFileNameWithoutExtension(reportPath);
var potentialTypeFullName = filename.Split('-').FirstOrDefault();
if (string.IsNullOrWhiteSpace(potentialTypeFullName)) { return null; }

var potentialTypeName = potentialTypeFullName.Split('.').LastOrDefault();
if (string.IsNullOrWhiteSpace(potentialTypeName)) { return null; }

return benchmarkTypes.FirstOrDefault(t => t.Name.Equals(potentialTypeName, StringComparison.OrdinalIgnoreCase));
}

private static void ExecuteBenchmarks(Assembly[] assemblies, BenchmarkContext context, BenchmarkWorkspaceOptions options)
{
if (context.Args.Length == 0)
{
foreach (var assembly in assemblies)
{
BenchmarkRunner.Run(assembly, options.Configuration);
}
}
else
{
BenchmarkSwitcher
.FromAssemblies(assemblies)
.Run(context.Args, options.Configuration);
}
}
}
}
112 changes: 0 additions & 112 deletions src/Codebelt.Extensions.BenchmarkDotNet.Console/BenchmarkWorker.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@

var candidatePaths = Directory
.EnumerateFiles(tuningDir, $"*.{benchmarkProjectSuffix}.dll", SearchOption.AllDirectories)
.Where(path => path.IndexOf(buildSegment, StringComparison.OrdinalIgnoreCase) >= 0);
.Where(path => path.Contains(buildSegment, StringComparison.OrdinalIgnoreCase));

foreach (var path in candidatePaths)
{
Expand All @@ -142,7 +142,7 @@
continue;
}

assemblies.Add(Assembly.LoadFrom(path));

Check warning on line 145 in src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspace.cs

View workflow job for this annotation

GitHub Actions / call-sonarcloud / 🔬 Code Quality Analysis

Replace this call to 'Assembly.LoadFrom' with 'Assembly.Load'. (https://rules.sonarsource.com/csharp/RSPEC-3885)

Check warning on line 145 in src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspace.cs

View workflow job for this annotation

GitHub Actions / call-sonarcloud / 🔬 Code Quality Analysis

Replace this call to 'Assembly.LoadFrom' with 'Assembly.Load'. (https://rules.sonarsource.com/csharp/RSPEC-3885)

Check warning on line 145 in src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspace.cs

View workflow job for this annotation

GitHub Actions / call-sonarcloud / 🔬 Code Quality Analysis

Replace this call to 'Assembly.LoadFrom' with 'Assembly.Load'. (https://rules.sonarsource.com/csharp/RSPEC-3885)

Check warning on line 145 in src/Codebelt.Extensions.BenchmarkDotNet/BenchmarkWorkspace.cs

View workflow job for this annotation

GitHub Actions / call-sonarcloud / 🔬 Code Quality Analysis

Replace this call to 'Assembly.LoadFrom' with 'Assembly.Load'. (https://rules.sonarsource.com/csharp/RSPEC-3885)
}
catch
{
Expand Down
8 changes: 8 additions & 0 deletions src/Codebelt.Extensions.BenchmarkDotNet/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.

using System.Diagnostics.CodeAnalysis;

[assembly: SuppressMessage("Performance", "CA1859:Use concrete types when possible for improved performance", Justification = "Using abstractions in public APIs promotes encapsulation, testability, and long-term maintainability, as prescribed by the .NET Framework Design Guidelines.", Scope = "member", Target = "~M:Codebelt.Extensions.BenchmarkDotNet.BenchmarkWorkspace.LoadAssemblies(System.String,System.String,System.String,System.String,System.Boolean)~System.Collections.Generic.IEnumerable{System.Reflection.Assembly}")]
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ public void BenchmarkProgram_ShouldInheritFromConsoleProgram()
// Assert
Assert.NotNull(baseType);
Assert.True(baseType.IsGenericType);
Assert.Equal("ConsoleProgram`1", baseType.Name);
Assert.Equal("MinimalConsoleProgram`1", baseType.Name);

TestOutput.WriteLine($"BenchmarkProgram correctly inherits from: {baseType.FullName}");
}
Expand All @@ -263,7 +263,7 @@ public void BenchmarkProgram_ShouldUseCorrectGenericTypeParameter()
// Assert
Assert.NotNull(genericArguments);
Assert.Single(genericArguments);
Assert.Equal(typeof(BenchmarkWorker), genericArguments[0]);
Assert.Equal(typeof(BenchmarkProgram), genericArguments[0]);

TestOutput.WriteLine($"BenchmarkProgram uses correct generic type parameter: {genericArguments[0].Name}");
}
Expand Down
Loading