Skip to content

Commit 3ea2129

Browse files
authored
ensure the default order of benchmarks is the same as declared in source code (#1907)
1 parent 6d27395 commit 3ea2129

File tree

5 files changed

+78
-8
lines changed

5 files changed

+78
-8
lines changed

src/BenchmarkDotNet.Annotations/Attributes/BenchmarkAttribute.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Runtime.CompilerServices;
23
using JetBrains.Annotations;
34

45
namespace BenchmarkDotNet.Attributes
@@ -7,10 +8,20 @@ namespace BenchmarkDotNet.Attributes
78
[MeansImplicitUse]
89
public class BenchmarkAttribute : Attribute
910
{
11+
public BenchmarkAttribute([CallerLineNumber] int sourceCodeLineNumber = 0, [CallerFilePath] string sourceCodeFile = "")
12+
{
13+
SourceCodeLineNumber = sourceCodeLineNumber;
14+
SourceCodeFile = sourceCodeFile;
15+
}
16+
1017
public string Description { get; set; }
1118

1219
public bool Baseline { get; set; }
1320

1421
public int OperationsPerInvoke { get; set; } = 1;
22+
23+
public int SourceCodeLineNumber { get; }
24+
25+
public string SourceCodeFile { get; }
1526
}
1627
}

src/BenchmarkDotNet/Running/BenchmarkConverter.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,22 @@ public static BenchmarkRunInfo TypeToBenchmarks(Type type, IConfig config = null
2323

2424
// We should check all methods including private to notify users about private methods with the [Benchmark] attribute
2525
var bindingFlags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
26-
var benchmarkMethods = type.GetMethods(bindingFlags).Where(method => method.HasAttribute<BenchmarkAttribute>()).ToArray();
26+
var benchmarkMethods = GetOrderedBenchmarkMethods(type.GetMethods(bindingFlags));
2727

2828
return MethodsToBenchmarksWithFullConfig(type, benchmarkMethods, config);
2929
}
3030

3131
public static BenchmarkRunInfo MethodsToBenchmarks(Type containingType, MethodInfo[] benchmarkMethods, IConfig config = null)
32-
=> MethodsToBenchmarksWithFullConfig(containingType, benchmarkMethods, config);
32+
=> MethodsToBenchmarksWithFullConfig(containingType, GetOrderedBenchmarkMethods(benchmarkMethods), config);
33+
34+
private static MethodInfo[] GetOrderedBenchmarkMethods(MethodInfo[] methods)
35+
=> methods
36+
.Select(method => (method, attribute: method.ResolveAttribute<BenchmarkAttribute>()))
37+
.Where(pair => pair.attribute is not null)
38+
.OrderBy(pair => pair.attribute.SourceCodeFile)
39+
.ThenBy(pair => pair.attribute.SourceCodeLineNumber)
40+
.Select(pair => pair.method)
41+
.ToArray();
3342

3443
private static BenchmarkRunInfo MethodsToBenchmarksWithFullConfig(Type type, MethodInfo[] benchmarkMethods, IConfig config)
3544
{
@@ -108,7 +117,6 @@ private static IEnumerable<Descriptor> GetTargets(
108117
Tuple<MethodInfo, TargetedAttribute>[] iterationCleanupMethods)
109118
{
110119
return targetMethods
111-
.Where(m => m.HasAttribute<BenchmarkAttribute>())
112120
.Select(methodInfo => CreateDescriptor(type,
113121
GetTargetedMatchingMethod(methodInfo, globalSetupMethods),
114122
methodInfo,

src/BenchmarkDotNet/Running/TypeFilter.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99

1010
namespace BenchmarkDotNet.Running
1111
{
12-
internal static class TypeFilter
12+
public static class TypeFilter
1313
{
14-
internal static (bool allTypesValid, IReadOnlyList<Type> runnable) GetTypesWithRunnableBenchmarks(IEnumerable<Type> types, IEnumerable<Assembly> assemblies, ILogger logger)
14+
public static (bool allTypesValid, IReadOnlyList<Type> runnable) GetTypesWithRunnableBenchmarks(IEnumerable<Type> types, IEnumerable<Assembly> assemblies, ILogger logger)
1515
{
1616
var validRunnableTypes = new List<Type>();
1717

@@ -37,7 +37,7 @@ internal static (bool allTypesValid, IReadOnlyList<Type> runnable) GetTypesWithR
3737
return (true, validRunnableTypes);
3838
}
3939

40-
internal static BenchmarkRunInfo[] Filter(IConfig effectiveConfig, IEnumerable<Type> types)
40+
public static BenchmarkRunInfo[] Filter(IConfig effectiveConfig, IEnumerable<Type> types)
4141
=> types
4242
.Select(type => BenchmarkConverter.TypeToBenchmarks(type, effectiveConfig))
4343
.Where(info => info.BenchmarksCases.Any())
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using BenchmarkDotNet.Attributes;
2+
3+
namespace BenchmarkDotNet.Tests.Running
4+
{
5+
public partial class BenchmarkConverterTests
6+
{
7+
public partial class BAC_Partial_DifferentFiles
8+
{
9+
[Benchmark] public void B() { }
10+
}
11+
}
12+
}

tests/BenchmarkDotNet.Tests/Running/BenchmarkConverterTests.cs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Linq;
1+
using System;
2+
using System.Linq;
23
using BenchmarkDotNet.Attributes;
34
using BenchmarkDotNet.Configs;
45
using BenchmarkDotNet.Environments;
@@ -9,7 +10,7 @@
910

1011
namespace BenchmarkDotNet.Tests.Running
1112
{
12-
public class BenchmarkConverterTests
13+
public partial class BenchmarkConverterTests
1314
{
1415
/// <summary>
1516
/// https://github.com/dotnet/BenchmarkDotNet/issues/495
@@ -198,5 +199,43 @@ public class WithFewMutators
198199
{
199200
[Benchmark] public void Method() { }
200201
}
202+
203+
[Fact]
204+
public void MethodDeclarationOrderIsPreserved()
205+
{
206+
foreach (Type type in new[] { typeof(BAC), typeof(BAC_Partial), typeof(BAC_Partial_DifferentFiles) })
207+
{
208+
var info = BenchmarkConverter.TypeToBenchmarks(type);
209+
210+
Assert.Equal(nameof(BAC.B), info.BenchmarksCases[0].Descriptor.WorkloadMethod.Name);
211+
Assert.Equal(nameof(BAC.A), info.BenchmarksCases[1].Descriptor.WorkloadMethod.Name);
212+
Assert.Equal(nameof(BAC.C), info.BenchmarksCases[2].Descriptor.WorkloadMethod.Name);
213+
}
214+
}
215+
216+
public class BAC
217+
{
218+
// BAC is not sorted in either desceding or ascending way
219+
[Benchmark] public void B() { }
220+
[Benchmark] public void A() { }
221+
[Benchmark] public void C() { }
222+
}
223+
224+
public partial class BAC_Partial
225+
{
226+
[Benchmark] public void B() { }
227+
[Benchmark] public void A() { }
228+
}
229+
230+
public partial class BAC_Partial
231+
{
232+
[Benchmark] public void C() { }
233+
}
234+
235+
public partial class BAC_Partial_DifferentFiles
236+
{
237+
[Benchmark] public void A() { }
238+
[Benchmark] public void C() { }
239+
}
201240
}
202241
}

0 commit comments

Comments
 (0)