diff --git a/samples/BenchmarkDotNet.Samples/IntroMemoryRandomization.cs b/samples/BenchmarkDotNet.Samples/IntroMemoryRandomization.cs
index 03d55574ec..5b9e417398 100644
--- a/samples/BenchmarkDotNet.Samples/IntroMemoryRandomization.cs
+++ b/samples/BenchmarkDotNet.Samples/IntroMemoryRandomization.cs
@@ -1,9 +1,5 @@
using BenchmarkDotNet.Attributes;
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace BenchmarkDotNet.Samples
{
@@ -23,6 +19,12 @@ public void Setup()
}
[Benchmark]
- public void Array() => System.Array.Copy(_array, _destination, Size);
+ [MemoryRandomization(false)]
+ public void Array_RandomizationDisabled() => Array.Copy(_array, _destination, Size);
+
+ [Benchmark]
+ [MemoryRandomization(true)]
+ [MaxIterationCount(40)] // the benchmark becomes multimodal and need a lower limit of max iterations than the default
+ public void Array_RandomizationEnabled() => Array.Copy(_array, _destination, Size);
}
}
diff --git a/src/BenchmarkDotNet/Attributes/Filters/FilterConfigBaseAttribute.cs b/src/BenchmarkDotNet/Attributes/Filters/FilterConfigBaseAttribute.cs
index ac5584a012..2a1c2f142e 100644
--- a/src/BenchmarkDotNet/Attributes/Filters/FilterConfigBaseAttribute.cs
+++ b/src/BenchmarkDotNet/Attributes/Filters/FilterConfigBaseAttribute.cs
@@ -4,7 +4,7 @@
namespace BenchmarkDotNet.Attributes
{
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly)]
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)]
public abstract class FilterConfigBaseAttribute : Attribute, IConfigSource
{
// CLS-Compliant Code requires a constructor without an array in the argument list
diff --git a/src/BenchmarkDotNet/Attributes/Filters/OperatingSystemsArchitectureFilterAttribute.cs b/src/BenchmarkDotNet/Attributes/Filters/OperatingSystemsArchitectureFilterAttribute.cs
new file mode 100644
index 0000000000..5b7419a227
--- /dev/null
+++ b/src/BenchmarkDotNet/Attributes/Filters/OperatingSystemsArchitectureFilterAttribute.cs
@@ -0,0 +1,25 @@
+using System.Linq;
+using BenchmarkDotNet.Filters;
+using JetBrains.Annotations;
+using System.Runtime.InteropServices;
+
+namespace BenchmarkDotNet.Attributes
+{
+ [PublicAPI]
+ public class OperatingSystemsArchitectureFilterAttribute : FilterConfigBaseAttribute
+ {
+ // CLS-Compliant Code requires a constructor without an array in the argument list
+ public OperatingSystemsArchitectureFilterAttribute() { }
+
+ /// if set to true, the architectures are enabled, if set to false, disabled
+ public OperatingSystemsArchitectureFilterAttribute(bool allowed, params Architecture[] architectures)
+ : base(new SimpleFilter(_ =>
+ {
+ return allowed
+ ? architectures.Any(architecture => RuntimeInformation.OSArchitecture == architecture)
+ : architectures.All(architecture => RuntimeInformation.OSArchitecture != architecture);
+ }))
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Attributes/Filters/OperatingSystemsFilterAttribute.cs b/src/BenchmarkDotNet/Attributes/Filters/OperatingSystemsFilterAttribute.cs
new file mode 100644
index 0000000000..203d25094e
--- /dev/null
+++ b/src/BenchmarkDotNet/Attributes/Filters/OperatingSystemsFilterAttribute.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Linq;
+using BenchmarkDotNet.Filters;
+using JetBrains.Annotations;
+using System.Runtime.InteropServices;
+
+namespace BenchmarkDotNet.Attributes
+{
+ public enum OS : byte
+ {
+ Windows,
+ Linux,
+ macOS,
+ ///
+ /// WebAssembly
+ ///
+ Browser
+ }
+
+ [PublicAPI]
+ public class OperatingSystemsFilterAttribute : FilterConfigBaseAttribute
+ {
+ private static readonly OSPlatform browser = OSPlatform.Create("BROWSER");
+
+ // CLS-Compliant Code requires a constructor without an array in the argument list
+ public OperatingSystemsFilterAttribute() { }
+
+ /// if set to true, the OSes beloning to platforms are enabled, if set to false, disabled
+ public OperatingSystemsFilterAttribute(bool allowed, params OS[] platforms)
+ : base(new SimpleFilter(_ =>
+ {
+ return allowed
+ ? platforms.Any(platform => RuntimeInformation.IsOSPlatform(Map(platform)))
+ : platforms.All(platform => !RuntimeInformation.IsOSPlatform(Map(platform)));
+ }))
+ {
+ }
+
+ // OSPlatform is a struct so it can not be used as attribute argument and this is why we use PlatformID enum
+ private static OSPlatform Map(OS platform)
+ {
+ switch (platform)
+ {
+ case OS.Windows:
+ return OSPlatform.Windows;
+ case OS.Linux:
+ return OSPlatform.Linux;
+ case OS.macOS:
+ return OSPlatform.OSX;
+ case OS.Browser:
+ return browser;
+ default:
+ throw new NotSupportedException($"Platform {platform} is not supported");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Attributes/Mutators/JobMutatorConfigBaseAttribute.cs b/src/BenchmarkDotNet/Attributes/Mutators/JobMutatorConfigBaseAttribute.cs
index 518ae6e7b5..a4a4102f49 100644
--- a/src/BenchmarkDotNet/Attributes/Mutators/JobMutatorConfigBaseAttribute.cs
+++ b/src/BenchmarkDotNet/Attributes/Mutators/JobMutatorConfigBaseAttribute.cs
@@ -5,7 +5,7 @@
namespace BenchmarkDotNet.Attributes
{
- [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] // users must not be able to define given mutator attribute more than once per type
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] // users must not be able to define given mutator attribute more than once per type
public class JobMutatorConfigBaseAttribute : Attribute, IConfigSource
{
// CLS-Compliant Code requires a constructor which use only CLS-compliant types
diff --git a/src/BenchmarkDotNet/Running/BenchmarkConverter.cs b/src/BenchmarkDotNet/Running/BenchmarkConverter.cs
index 6ee7467d81..fdca381fc8 100644
--- a/src/BenchmarkDotNet/Running/BenchmarkConverter.cs
+++ b/src/BenchmarkDotNet/Running/BenchmarkConverter.cs
@@ -23,78 +23,78 @@ public static BenchmarkRunInfo TypeToBenchmarks(Type type, IConfig config = null
// We should check all methods including private to notify users about private methods with the [Benchmark] attribute
var bindingFlags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
+ var benchmarkMethods = type.GetMethods(bindingFlags).Where(method => method.HasAttribute()).ToArray();
- var fullConfig = GetFullConfig(type, config);
- var allMethods = type.GetMethods(bindingFlags);
- return MethodsToBenchmarksWithFullConfig(type, allMethods, fullConfig);
+ return MethodsToBenchmarksWithFullConfig(type, benchmarkMethods, config);
}
public static BenchmarkRunInfo MethodsToBenchmarks(Type containingType, MethodInfo[] benchmarkMethods, IConfig config = null)
- {
- var fullConfig = GetFullConfig(containingType, config);
-
- return MethodsToBenchmarksWithFullConfig(containingType, benchmarkMethods, fullConfig);
- }
+ => MethodsToBenchmarksWithFullConfig(containingType, benchmarkMethods, config);
- private static BenchmarkRunInfo MethodsToBenchmarksWithFullConfig(Type containingType, MethodInfo[] benchmarkMethods, ImmutableConfig immutableConfig)
+ private static BenchmarkRunInfo MethodsToBenchmarksWithFullConfig(Type type, MethodInfo[] benchmarkMethods, IConfig config)
{
- if (immutableConfig == null)
- throw new ArgumentNullException(nameof(immutableConfig));
-
- var helperMethods = containingType.GetMethods(); // benchmarkMethods can be filtered, without Setups, look #564
-
- var globalSetupMethods = GetAttributedMethods(helperMethods, "GlobalSetup");
- var globalCleanupMethods = GetAttributedMethods(helperMethods, "GlobalCleanup");
- var iterationSetupMethods = GetAttributedMethods(helperMethods, "IterationSetup");
- var iterationCleanupMethods = GetAttributedMethods(helperMethods, "IterationCleanup");
-
- var targetMethods = benchmarkMethods.Where(method => method.HasAttribute()).ToArray();
+ var allPublicMethods = type.GetMethods(); // benchmarkMethods can be filtered, without Setups, look #564
+ var configPerType = GetFullTypeConfig(type, config);
- var parameterDefinitions = GetParameterDefinitions(containingType);
- var parameterInstancesList = parameterDefinitions.Expand(immutableConfig.SummaryStyle);
+ var globalSetupMethods = GetAttributedMethods(allPublicMethods, "GlobalSetup");
+ var globalCleanupMethods = GetAttributedMethods(allPublicMethods, "GlobalCleanup");
+ var iterationSetupMethods = GetAttributedMethods(allPublicMethods, "IterationSetup");
+ var iterationCleanupMethods = GetAttributedMethods(allPublicMethods, "IterationCleanup");
- var jobs = immutableConfig.GetJobs();
+ var targets = GetTargets(benchmarkMethods, type, globalSetupMethods, globalCleanupMethods, iterationSetupMethods, iterationCleanupMethods).ToArray();
- var targets = GetTargets(targetMethods, containingType, globalSetupMethods, globalCleanupMethods, iterationSetupMethods, iterationCleanupMethods).ToArray();
+ var parameterDefinitions = GetParameterDefinitions(type);
+ var parameterInstancesList = parameterDefinitions.Expand(configPerType.SummaryStyle);
var benchmarks = new List();
+
foreach (var target in targets)
{
- var argumentsDefinitions = GetArgumentsDefinitions(target.WorkloadMethod, target.Type, immutableConfig.SummaryStyle).ToArray();
+ var argumentsDefinitions = GetArgumentsDefinitions(target.WorkloadMethod, target.Type, configPerType.SummaryStyle).ToArray();
var parameterInstances =
(from parameterInstance in parameterInstancesList
from argumentDefinition in argumentsDefinitions
select new ParameterInstances(parameterInstance.Items.Concat(argumentDefinition.Items).ToArray())).ToArray();
- benchmarks.AddRange(
- from job in jobs
+ var configPerMethod = GetFullMethodConfig(target.WorkloadMethod, configPerType);
+
+ var benchmarksForTarget =
+ from job in configPerMethod.GetJobs()
from parameterInstance in parameterInstances
- select BenchmarkCase.Create(target, job, parameterInstance, immutableConfig)
- );
+ select BenchmarkCase.Create(target, job, parameterInstance, configPerMethod);
+
+ benchmarks.AddRange(GetFilteredBenchmarks(benchmarksForTarget, configPerMethod.GetFilters()));
}
- var filters = immutableConfig.GetFilters().ToArray();
- var filteredBenchmarks = GetFilteredBenchmarks(benchmarks, filters);
- var orderedBenchmarks = immutableConfig.Orderer.GetExecutionOrder(filteredBenchmarks).ToArray();
+ var orderedBenchmarks = configPerType.Orderer.GetExecutionOrder(benchmarks.ToImmutableArray()).ToArray();
- return new BenchmarkRunInfo(orderedBenchmarks, containingType, immutableConfig);
+ return new BenchmarkRunInfo(orderedBenchmarks, type, configPerType);
}
- public static ImmutableConfig GetFullConfig(Type type, IConfig config)
+ private static ImmutableConfig GetFullTypeConfig(Type type, IConfig config)
{
config = config ?? DefaultConfig.Instance;
- if (type != null)
- {
- var typeAttributes = type.GetTypeInfo().GetCustomAttributes(true).OfType();
- var assemblyAttributes = type.GetTypeInfo().Assembly.GetCustomAttributes().OfType();
- var allAttributes = typeAttributes.Concat(assemblyAttributes);
- var configs = allAttributes.Select(attribute => attribute.Config)
- .OrderBy(c => c.GetJobs().Count(job => job.Meta.IsMutator)); // configs with mutators must be the ones applied at the end
-
- foreach (var configFromAttribute in configs)
- config = ManualConfig.Union(config, configFromAttribute);
- }
+
+ var typeAttributes = type.GetCustomAttributes(true).OfType();
+ var assemblyAttributes = type.Assembly.GetCustomAttributes().OfType();
+
+ foreach (var configFromAttribute in typeAttributes.Concat(assemblyAttributes))
+ config = ManualConfig.Union(config, configFromAttribute.Config);
+
+ return ImmutableConfigBuilder.Create(config);
+ }
+
+ private static ImmutableConfig GetFullMethodConfig(MethodInfo method, ImmutableConfig typeConfig)
+ {
+ var methodAttributes = method.GetCustomAttributes(true).OfType();
+
+ if (!methodAttributes.Any()) // the most common case
+ return typeConfig;
+
+ var config = ManualConfig.Create(typeConfig);
+ foreach (var configFromAttribute in methodAttributes)
+ config = ManualConfig.Union(config, configFromAttribute.Config);
return ImmutableConfigBuilder.Create(config);
}
@@ -251,7 +251,7 @@ private static string[] GetCategories(MethodInfo method)
return attributes.SelectMany(attr => attr.Categories).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
}
- private static ImmutableArray GetFilteredBenchmarks(IList benchmarks, IList filters)
+ private static ImmutableArray GetFilteredBenchmarks(IEnumerable benchmarks, IEnumerable filters)
=> benchmarks.Where(benchmark => filters.All(filter => filter.Predicate(benchmark))).ToImmutableArray();
private static void AssertMethodHasCorrectSignature(string methodType, MethodInfo methodInfo)
diff --git a/src/BenchmarkDotNet/Running/BenchmarkPartitioner.cs b/src/BenchmarkDotNet/Running/BenchmarkPartitioner.cs
index 1a60178e9e..373d956b77 100644
--- a/src/BenchmarkDotNet/Running/BenchmarkPartitioner.cs
+++ b/src/BenchmarkDotNet/Running/BenchmarkPartitioner.cs
@@ -13,7 +13,7 @@ public static class BenchmarkPartitioner
{
public static BuildPartition[] CreateForBuild(BenchmarkRunInfo[] supportedBenchmarks, IResolver resolver)
=> supportedBenchmarks
- .SelectMany(info => info.BenchmarksCases.Select(benchmark => (benchmark, info.Config)))
+ .SelectMany(info => info.BenchmarksCases.Select(benchmark => (benchmark, benchmark.Config)))
.GroupBy(tuple => tuple.benchmark, BenchmarkRuntimePropertiesComparer.Instance)
.Select(group => new BuildPartition(group.Select((item, index) => new BenchmarkBuildInfo(item.benchmark, item.Config, index)).ToArray(), resolver))
.ToArray();
diff --git a/tests/BenchmarkDotNet.IntegrationTests/BenchmarkTestExecutor.cs b/tests/BenchmarkDotNet.IntegrationTests/BenchmarkTestExecutor.cs
index f8f827462a..d7b2311a48 100644
--- a/tests/BenchmarkDotNet.IntegrationTests/BenchmarkTestExecutor.cs
+++ b/tests/BenchmarkDotNet.IntegrationTests/BenchmarkTestExecutor.cs
@@ -60,7 +60,7 @@ protected Reports.Summary CanExecute(Type type, IConfig config = null, bool full
config = config.AddColumnProvider(DefaultColumnProviders.Instance);
// Make sure we ALWAYS combine the Config (default or passed in) with any Config applied to the Type/Class
- var summary = BenchmarkRunner.Run(type, BenchmarkConverter.GetFullConfig(type, config));
+ var summary = BenchmarkRunner.Run(type, config);
if (fullValidation)
{
diff --git a/tests/BenchmarkDotNet.Tests/Configs/ConfigPerMethodTests.cs b/tests/BenchmarkDotNet.Tests/Configs/ConfigPerMethodTests.cs
new file mode 100644
index 0000000000..f11fbb7e42
--- /dev/null
+++ b/tests/BenchmarkDotNet.Tests/Configs/ConfigPerMethodTests.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Linq;
+using System.Runtime.InteropServices;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Filters;
+using BenchmarkDotNet.Running;
+using Xunit;
+
+namespace BenchmarkDotNet.Tests.Configs
+{
+ public class ConfigPerMethodTests
+ {
+ [Fact]
+ public void PerMethodConfigsAreRespected()
+ {
+ var never = BenchmarkConverter.TypeToBenchmarks(typeof(WithBenchmarkThatShouldNeverRun));
+
+ Assert.Empty(never.BenchmarksCases);
+
+ var always = BenchmarkConverter.TypeToBenchmarks(typeof(WithBenchmarkThatShouldAlwaysRun));
+
+ Assert.NotEmpty(always.BenchmarksCases);
+ }
+
+ public class ConditionalRun : FilterConfigBaseAttribute
+ {
+ public ConditionalRun(bool value) : base(new SimpleFilter(_ => value)) { }
+ }
+
+ public class WithBenchmarkThatShouldNeverRun
+ {
+ [Benchmark]
+ [ConditionalRun(false)]
+ public void Method() { }
+ }
+
+ public class WithBenchmarkThatShouldAlwaysRun
+ {
+ [Benchmark]
+ [ConditionalRun(true)]
+ public void Method() { }
+ }
+
+ [Fact]
+ public void CanEnableOrDisableTheBenchmarkPerOperatingSystem()
+ {
+ var allowedForWindows = BenchmarkConverter.TypeToBenchmarks(typeof(WithBenchmarkAllowedForWindows));
+ var notAllowedForWindows = BenchmarkConverter.TypeToBenchmarks(typeof(WithBenchmarkNotAllowedForWindows));
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ Assert.NotEmpty(allowedForWindows.BenchmarksCases);
+ Assert.Empty(notAllowedForWindows.BenchmarksCases);
+ }
+ else
+ {
+ Assert.Empty(allowedForWindows.BenchmarksCases);
+ Assert.NotEmpty(notAllowedForWindows.BenchmarksCases);
+ }
+ }
+
+ public class WithBenchmarkAllowedForWindows
+ {
+ [Benchmark]
+ [OperatingSystemsFilter(allowed: true, OS.Windows)]
+ public void Method() { }
+ }
+
+ public class WithBenchmarkNotAllowedForWindows
+ {
+ [Benchmark]
+ [OperatingSystemsFilter(allowed: false, OS.Windows)]
+ public void Method() { }
+ }
+
+ [Fact]
+ public void CanEnableOrDisableTheBenchmarkPerOperatingSystemArchitecture()
+ {
+ var allowed = BenchmarkConverter.TypeToBenchmarks(typeof(WithBenchmarkAllowedForX64));
+ var notallowed = BenchmarkConverter.TypeToBenchmarks(typeof(WithBenchmarkNotAllowedForX64));
+
+ if (RuntimeInformation.OSArchitecture == Architecture.X64)
+ {
+ Assert.NotEmpty(allowed.BenchmarksCases);
+ Assert.Empty(notallowed.BenchmarksCases);
+ }
+ else
+ {
+ Assert.Empty(allowed.BenchmarksCases);
+ Assert.NotEmpty(notallowed.BenchmarksCases);
+ }
+ }
+
+ public class WithBenchmarkAllowedForX64
+ {
+ [Benchmark]
+ [OperatingSystemsArchitectureFilter(allowed: true, Architecture.X64)]
+ public void Method() { }
+ }
+
+ public class WithBenchmarkNotAllowedForX64
+ {
+ [Benchmark]
+ [OperatingSystemsArchitectureFilter(allowed: false, Architecture.X64)]
+ public void Method() { }
+ }
+
+ [Fact]
+ public void CanEnableOrDisableMemoryRandomizationPerMethod()
+ {
+ var benchmarks = BenchmarkConverter.TypeToBenchmarks(typeof(WithMemoryRandomization)).BenchmarksCases;
+
+ Assert.Equal(2, benchmarks.Length);
+ var disabled = benchmarks.Single(benchmark => benchmark.Descriptor.WorkloadMethod.Name == nameof(WithMemoryRandomization.DisabledByDefault));
+ Assert.False(disabled.Job.Run.MemoryRandomization);
+ var enabled = benchmarks.Single(benchmark => benchmark.Descriptor.WorkloadMethod.Name == nameof(WithMemoryRandomization.EnabledWithAttributeOnMethod));
+ Assert.True(enabled.Job.Run.MemoryRandomization);
+ }
+
+ public class WithMemoryRandomization
+ {
+ [Benchmark]
+ public void DisabledByDefault() { }
+
+ [Benchmark]
+ [MemoryRandomization(true)]
+ public void EnabledWithAttributeOnMethod() { }
+ }
+ }
+}
\ No newline at end of file