Skip to content

Commit 4f4bef7

Browse files
authored
Allow filters to filter out every benchmark from a type (Fixes #2860) (#2879)
* Allow filters to filter out every benchmark from a type * Add test for assembly with filtered benchmarks case * Enable assembly with filtered benchmarks test on .NET Framework 4
1 parent 9c1aa83 commit 4f4bef7

File tree

4 files changed

+55
-13
lines changed

4 files changed

+55
-13
lines changed

src/BenchmarkDotNet/Running/BenchmarkConverter.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ private static BenchmarkRunInfo MethodsToBenchmarksWithFullConfig(Type type, Met
5959

6060
var benchmarks = new List<BenchmarkCase>();
6161

62+
bool containsBenchmarkDeclarations = false;
63+
6264
foreach (var target in targets)
6365
{
6466
var argumentsDefinitions = GetArgumentsDefinitions(target.WorkloadMethod, target.Type, configPerType.SummaryStyle).ToArray();
@@ -75,12 +77,14 @@ from job in configPerMethod.GetJobs()
7577
from parameterInstance in parameterInstances
7678
select BenchmarkCase.Create(target, job, parameterInstance, configPerMethod);
7779

80+
if (benchmarksForTarget.Any() && !containsBenchmarkDeclarations) containsBenchmarkDeclarations = true;
81+
7882
benchmarks.AddRange(GetFilteredBenchmarks(benchmarksForTarget, configPerMethod.GetFilters()));
7983
}
8084

8185
var orderedBenchmarks = configPerType.Orderer.GetExecutionOrder(benchmarks.ToImmutableArray()).ToArray();
8286

83-
return new BenchmarkRunInfo(orderedBenchmarks, type, configPerType);
87+
return new BenchmarkRunInfo(orderedBenchmarks, type, configPerType, containsBenchmarkDeclarations);
8488
}
8589

8690
private static ImmutableConfig GetFullTypeConfig(Type type, IConfig? config)

src/BenchmarkDotNet/Running/BenchmarkRunInfo.cs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,9 @@
33

44
namespace BenchmarkDotNet.Running
55
{
6-
public class BenchmarkRunInfo : IDisposable
6+
public class BenchmarkRunInfo(BenchmarkCase[] benchmarksCase, Type type, ImmutableConfig config, bool containsBenchmarkDeclarations) : IDisposable
77
{
8-
public BenchmarkRunInfo(BenchmarkCase[] benchmarksCase, Type type, ImmutableConfig config)
9-
{
10-
BenchmarksCases = benchmarksCase;
11-
Type = type;
12-
Config = config;
13-
}
8+
public BenchmarkRunInfo(BenchmarkCase[] benchmarksCase, Type type, ImmutableConfig config) : this(benchmarksCase, type, config, benchmarksCase.Length > 0) { }
149

1510
public void Dispose()
1611
{
@@ -20,8 +15,9 @@ public void Dispose()
2015
}
2116
}
2217

23-
public BenchmarkCase[] BenchmarksCases { get; }
24-
public Type Type { get; }
25-
public ImmutableConfig Config { get; }
18+
public BenchmarkCase[] BenchmarksCases { get; } = benchmarksCase;
19+
public Type Type { get; } = type;
20+
public ImmutableConfig Config { get; } = config;
21+
public bool ContainsBenchmarkDeclarations { get; } = containsBenchmarkDeclarations;
2622
}
2723
}

src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,7 @@ private static (BenchmarkRunInfo[], List<ValidationError>) GetSupportedBenchmark
633633

634634
foreach (var benchmarkRunInfo in benchmarkRunInfos)
635635
{
636-
if (benchmarkRunInfo.BenchmarksCases.Length == 0)
636+
if (!benchmarkRunInfo.ContainsBenchmarkDeclarations)
637637
{
638638
validationErrors.Add(new ValidationError(true, $"No [Benchmark] attribute found on '{benchmarkRunInfo.Type.Name}' benchmark case."));
639639
continue;

tests/BenchmarkDotNet.Tests/Running/RunningEmptyBenchmarkTests.cs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using BenchmarkDotNet.Loggers;
1010
using BenchmarkDotNet.Reports;
1111
using System.Runtime.InteropServices;
12+
using BenchmarkDotNet.Filters;
1213

1314
namespace BenchmarkDotNet.Tests.Running
1415
{
@@ -232,8 +233,8 @@ public void MixedTypes_ThrowsValidationError_WhenNoBenchmarkAttribute(string[]?
232233
#region Assembly Tests
233234
// In this tests there is no config and logger because the logger is initiated at CreateCompositeLogger when the BenchmarkRunInfo[] is empty
234235
// those cannot be inserted using config
235-
[Theory]
236236

237+
[Theory]
237238
[InlineData(null)]
238239
[InlineData(new object[] { new string[] { " " } })]
239240
public void AssemblyWithoutBenchmarks_ThrowsValidationError_WhenNoBenchmarksFound(string[]? args)
@@ -308,6 +309,42 @@ public void AssemblyWithBenchmarks_RunsSuccessfully_WhenBenchmarkAttributePresen
308309
Assert.DoesNotContain(summary.ValidationErrors, validationError => validationError.Message == GetGeneralValidationError());
309310
}
310311
}
312+
313+
[Fact]
314+
public void AssemblyWithBenchmarks_RunsNothing_WhenAllBenchmarksFilteredOutFromOneTypeWithBenchmarkAttributePresent()
315+
{
316+
// Create a mock assembly with benchmark types
317+
var assemblyName = new AssemblyName("MockAssemblyWithBenchmarks");
318+
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
319+
var moduleBuilder = assemblyBuilder.DefineDynamicModule("MockModule");
320+
321+
// Create a benchmark type
322+
var benchmarkTypeBuilder = moduleBuilder.DefineType("MockBenchmark", TypeAttributes.Public);
323+
var benchmarkMethod = benchmarkTypeBuilder.DefineMethod("Benchmark", MethodAttributes.Public, typeof(void), Type.EmptyTypes);
324+
325+
// Generate method body
326+
var ilGenerator = benchmarkMethod.GetILGenerator();
327+
ilGenerator.Emit(OpCodes.Ret); // Just return from the method
328+
329+
var benchmarkAttributeCtor = typeof(BenchmarkAttribute).GetConstructor(new[] { typeof(int), typeof(string) });
330+
if (benchmarkAttributeCtor == null)
331+
throw new InvalidOperationException("Could not find BenchmarkAttribute constructor");
332+
benchmarkMethod.SetCustomAttribute(new CustomAttributeBuilder(
333+
benchmarkAttributeCtor,
334+
new object[] { 0, "" }));
335+
benchmarkTypeBuilder.CreateType();
336+
337+
Summary[] summaries = null;
338+
339+
GetConfigWithLogger(out var logger, out var config);
340+
341+
config.AddFilter(new NameFilter(name => name != "Benchmark")); // Filter out only benchmark method on MockBenchmark
342+
343+
summaries = BenchmarkRunner.Run(assemblyBuilder, config);
344+
Assert.DoesNotContain(GetValidationErrorForType(benchmarkTypeBuilder), logger.GetLog());
345+
Assert.Contains(GetExporterNoBenchmarksError(), logger.GetLog());
346+
}
347+
311348
#endregion
312349
#region Helper Methods
313350
private string GetValidationErrorForType(Type type)
@@ -330,6 +367,11 @@ private string GetGeneralValidationError()
330367
return $"No benchmarks were found.";
331368
}
332369

370+
private string GetExporterNoBenchmarksError()
371+
{
372+
return "There are no benchmarks found";
373+
}
374+
333375
private void GetConfigWithLogger(out AccumulationLogger logger, out ManualConfig manualConfig)
334376
{
335377
logger = new AccumulationLogger();

0 commit comments

Comments
 (0)