Skip to content

Commit c2b6dd7

Browse files
User0332timcassell
authored andcommitted
Allow benchmark methods to use names that appear in source generation templates
1 parent 1f8e8db commit c2b6dd7

File tree

11 files changed

+150
-111
lines changed

11 files changed

+150
-111
lines changed

src/BenchmarkDotNet/Characteristics/CharacteristicPresenter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public override string ToPresentation(object? characteristicValue, Characteristi
8383
{
8484
// TODO: DO NOT hardcode Characteristic suffix
8585
string id = characteristic.Id;
86-
string type = characteristic.DeclaringType.FullName!;
86+
string type = characteristic.DeclaringType.GetCorrectCSharpTypeName();
8787
string value = SourceCodeHelper.ToSourceCode(characteristicValue);
8888
return $"{type}.{id}Characteristic[job] = {value}";
8989
}

src/BenchmarkDotNet/Code/DeclarationsProvider.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ internal abstract class DeclarationsProvider
2626

2727
public string IterationCleanupMethodName => Descriptor.IterationCleanupMethod?.Name ?? EmptyAction;
2828

29-
public virtual string GetWorkloadMethodCall(string passArguments) => $"{Descriptor.WorkloadMethod.Name}({passArguments})";
29+
public abstract string GetWorkloadMethodCall(string passArguments);
30+
31+
protected string WorkloadMethodPrefix => Descriptor.WorkloadMethod.IsStatic ? Descriptor.WorkloadMethod.DeclaringType.GetCorrectCSharpTypeName() : "base";
3032

3133
private string GetMethodName(MethodInfo method)
3234
{
@@ -48,15 +50,15 @@ private string GetMethodName(MethodInfo method)
4850
}
4951
}
5052

51-
internal class SyncDeclarationsProvider : DeclarationsProvider
53+
internal class SyncDeclarationsProvider(Descriptor descriptor) : DeclarationsProvider(descriptor)
5254
{
53-
public SyncDeclarationsProvider(Descriptor descriptor) : base(descriptor) { }
55+
public override string GetWorkloadMethodCall(string passArguments)
56+
=> $"{WorkloadMethodPrefix}.{Descriptor.WorkloadMethod.Name}({passArguments})";
5457
}
5558

56-
internal class AsyncDeclarationsProvider : DeclarationsProvider
59+
internal class AsyncDeclarationsProvider(Descriptor descriptor) : DeclarationsProvider(descriptor)
5760
{
58-
public AsyncDeclarationsProvider(Descriptor descriptor) : base(descriptor) { }
59-
60-
public override string GetWorkloadMethodCall(string passArguments) => $"BenchmarkDotNet.Helpers.AwaitHelper.GetResult({Descriptor.WorkloadMethod.Name}({passArguments}))";
61+
public override string GetWorkloadMethodCall(string passArguments)
62+
=> $"BenchmarkDotNet.Helpers.AwaitHelper.GetResult({WorkloadMethodPrefix}.{Descriptor.WorkloadMethod.Name}({passArguments}))";
6163
}
6264
}

src/BenchmarkDotNet/Extensions/ReflectionExtensions.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Reflection;
5+
using System.Security.Cryptography.X509Certificates;
56
using BenchmarkDotNet.Attributes;
67

78
namespace BenchmarkDotNet.Extensions
@@ -36,7 +37,7 @@ public static bool IsInitOnly(this PropertyInfo propertyInfo)
3637
/// <summary>
3738
/// returns type name which can be used in generated C# code
3839
/// </summary>
39-
internal static string GetCorrectCSharpTypeName(this Type type, bool includeNamespace = true, bool includeGenericArgumentsNamespace = true)
40+
internal static string GetCorrectCSharpTypeName(this Type type, bool includeNamespace = true, bool includeGenericArgumentsNamespace = true, bool prefixWithGlobal = true)
4041
{
4142
while (!(type.IsPublic || type.IsNestedPublic) && type.BaseType != null)
4243
type = type.BaseType;
@@ -49,16 +50,23 @@ internal static string GetCorrectCSharpTypeName(this Type type, bool includeName
4950
return "void";
5051
if (type == typeof(void*))
5152
return "void*";
53+
5254
string prefix = "";
55+
5356
if (!string.IsNullOrEmpty(type.Namespace) && includeNamespace)
57+
{
5458
prefix += type.Namespace + ".";
5559

60+
if (prefixWithGlobal)
61+
prefix = $"global::{prefix}";
62+
}
63+
5664
if (type.GetTypeInfo().IsGenericParameter)
5765
return type.Name;
5866

5967
if (type.IsArray)
6068
{
61-
var typeName = GetCorrectCSharpTypeName(type.GetElementType());
69+
var typeName = GetCorrectCSharpTypeName(type.GetElementType(), includeNamespace, includeGenericArgumentsNamespace, prefixWithGlobal);
6270
var parts = typeName.Split(['['], count: 2);
6371

6472
string repr = parts[0] + '[' + new string(',', type.GetArrayRank() - 1) + ']';
@@ -68,11 +76,11 @@ internal static string GetCorrectCSharpTypeName(this Type type, bool includeName
6876
return repr;
6977
}
7078

71-
return prefix + string.Join(".", GetNestedTypeNames(type, includeGenericArgumentsNamespace).Reverse());
79+
return prefix + string.Join(".", GetNestedTypeNames(type, includeGenericArgumentsNamespace, prefixWithGlobal).Reverse());
7280
}
7381

7482
// from most nested to least
75-
private static IEnumerable<string> GetNestedTypeNames(Type type, bool includeGenericArgumentsNamespace)
83+
private static IEnumerable<string> GetNestedTypeNames(Type type, bool includeGenericArgumentsNamespace, bool prefixWithGlobal)
7684
{
7785
var allTypeParameters = new Stack<Type>(type.GetGenericArguments());
7886

@@ -92,7 +100,7 @@ private static IEnumerable<string> GetNestedTypeNames(Type type, bool includeGen
92100
.Select(_ => allTypeParameters.Pop())
93101
.Reverse();
94102

95-
var args = string.Join(", ", typeParameters.Select(T => GetCorrectCSharpTypeName(T, includeGenericArgumentsNamespace, includeGenericArgumentsNamespace)));
103+
var args = string.Join(", ", typeParameters.Select(T => GetCorrectCSharpTypeName(T, includeGenericArgumentsNamespace, includeGenericArgumentsNamespace, prefixWithGlobal)));
96104
name = $"{mainName}<{args}>";
97105
}
98106

src/BenchmarkDotNet/Helpers/FolderNameHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public static string ToFolderName(object? value)
4444
// we can't simply use type.FullName, because for generics it's too long
4545
// example: typeof(List<int>).FullName => "System.Collections.Generic.List`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"
4646
public static string ToFolderName(Type type, bool includeNamespace = true, bool includeGenericArgumentsNamespace = false)
47-
=> Escape(new StringBuilder(type.GetCorrectCSharpTypeName(includeNamespace, includeGenericArgumentsNamespace)));
47+
=> Escape(new StringBuilder(type.GetCorrectCSharpTypeName(includeNamespace, includeGenericArgumentsNamespace, prefixWithGlobal: false)));
4848

4949
private static string Escape(StringBuilder builder)
5050
{

src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ internal static Summary[] Run(BenchmarkRunInfo[] benchmarkRunInfos)
137137
var benchmarkWithHighestIdForGivenType = benchmarkRunInfo.BenchmarksCases.Last();
138138
if (benchmarkToBuildResult[benchmarkWithHighestIdForGivenType].Id.Value <= idToResume)
139139
{
140-
compositeLogger.WriteLineInfo($"Skipping {benchmarkRunInfo.BenchmarksCases.Length} benchmark(s) defined by {benchmarkRunInfo.Type.GetCorrectCSharpTypeName()}.");
140+
compositeLogger.WriteLineInfo($"Skipping {benchmarkRunInfo.BenchmarksCases.Length} benchmark(s) defined by {benchmarkRunInfo.Type.GetCorrectCSharpTypeName(prefixWithGlobal: false)}.");
141141
continue;
142142
}
143143
}

src/BenchmarkDotNet/Running/Descriptor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public Descriptor(
7171

7272
public bool HasCategory(string category) => Categories.Any(c => c.EqualsWithIgnoreCase(category));
7373

74-
public string GetFilterName() => $"{Type.GetCorrectCSharpTypeName(includeGenericArgumentsNamespace: false)}.{WorkloadMethod.Name}";
74+
public string GetFilterName() => $"{Type.GetCorrectCSharpTypeName(includeGenericArgumentsNamespace: false, prefixWithGlobal: false)}.{WorkloadMethod.Name}";
7575

7676
public bool Equals(Descriptor? other) => GetFilterName().Equals(other?.GetFilterName());
7777

src/BenchmarkDotNet/Templates/BenchmarkProgram.txt

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace BenchmarkDotNet.Autogenerated
1111
public class UniqueProgramName // we need different name than typical "Program" to avoid problems with referencing "Program" types from benchmarked code, #691
1212
{
1313
$ExtraAttribute$
14-
public static System.Int32 Main(System.String[] args)
14+
public static global::System.Int32 Main(global::System.String[] args)
1515
{
1616
// this method MUST NOT have any dependencies to BenchmarkDotNet and any other external dlls! (CoreRT is exception from this rule)
1717
// otherwise if LINQPad's shadow copy is enabled, we will not register for AssemblyLoading event
@@ -22,17 +22,17 @@ namespace BenchmarkDotNet.Autogenerated
2222
return AfterAssemblyLoadingAttached(args);
2323
}
2424

25-
private static System.Int32 AfterAssemblyLoadingAttached(System.String[] args)
25+
private static global::System.Int32 AfterAssemblyLoadingAttached(global::System.String[] args)
2626
{
27-
BenchmarkDotNet.Engines.IHost host; // this variable name is used by CodeGenerator.GetCoreRtSwitch, do NOT change it
28-
if (BenchmarkDotNet.Engines.AnonymousPipesHost.TryGetFileHandles(args, out System.String writeHandle, out System.String readHandle))
29-
host = new BenchmarkDotNet.Engines.AnonymousPipesHost(writeHandle, readHandle);
27+
global::BenchmarkDotNet.Engines.IHost host; // this variable name is used by CodeGenerator.GetCoreRtSwitch, do NOT change it
28+
if (global::BenchmarkDotNet.Engines.AnonymousPipesHost.TryGetFileHandles(args, out global::System.String writeHandle, out global::System.String readHandle))
29+
host = new global::BenchmarkDotNet.Engines.AnonymousPipesHost(writeHandle, readHandle);
3030
else
31-
host = new BenchmarkDotNet.Engines.NoAcknowledgementConsoleHost();
31+
host = new global::BenchmarkDotNet.Engines.NoAcknowledgementConsoleHost();
3232

3333
// the first thing to do is to let diagnosers hook in before anything happens
3434
// so all jit-related diagnosers can catch first jit compilation!
35-
BenchmarkDotNet.Engines.HostExtensions.BeforeAnythingElse(host);
35+
global::BenchmarkDotNet.Engines.HostExtensions.BeforeAnythingElse(host);
3636

3737
try
3838
{
@@ -41,10 +41,10 @@ namespace BenchmarkDotNet.Autogenerated
4141
// we have some jitting diagnosers and we want them to catch all the informations!!
4242

4343
// this variable name is used by CodeGenerator.GetCoreRtSwitch, do NOT change it
44-
System.String benchmarkName = System.Linq.Enumerable.FirstOrDefault(System.Linq.Enumerable.Skip(System.Linq.Enumerable.SkipWhile(args, arg => arg != "--benchmarkName"), 1)) ?? "not provided";
45-
BenchmarkDotNet.Diagnosers.RunMode diagnoserRunMode = (BenchmarkDotNet.Diagnosers.RunMode) System.Int32.Parse(System.Linq.Enumerable.FirstOrDefault(System.Linq.Enumerable.Skip(System.Linq.Enumerable.SkipWhile(args, arg => arg != "--diagnoserRunMode"), 1)) ?? "0");
46-
System.Int32 id = args.Length > 0
47-
? System.Int32.Parse(args[args.Length - 1]) // this variable name is used by CodeGenerator.GetCoreRtSwitch, do NOT change it
44+
global::System.String benchmarkName = global::System.Linq.Enumerable.FirstOrDefault(global::System.Linq.Enumerable.Skip(global::System.Linq.Enumerable.SkipWhile(args, arg => arg != "--benchmarkName"), 1)) ?? "not provided";
45+
global::BenchmarkDotNet.Diagnosers.RunMode diagnoserRunMode = (global::BenchmarkDotNet.Diagnosers.RunMode) global::System.Int32.Parse(global::System.Linq.Enumerable.FirstOrDefault(global::System.Linq.Enumerable.Skip(global::System.Linq.Enumerable.SkipWhile(args, arg => arg != "--diagnoserRunMode"), 1)) ?? "0");
46+
global::System.Int32 id = args.Length > 0
47+
? global::System.Int32.Parse(args[args.Length - 1]) // this variable name is used by CodeGenerator.GetCoreRtSwitch, do NOT change it
4848
: 0; // used when re-using generated exe without BDN (typically to repro a bug)
4949

5050
if (args.Length == 0)
@@ -54,12 +54,12 @@ namespace BenchmarkDotNet.Autogenerated
5454
#if NATIVEAOT
5555
$NativeAotSwitch$
5656
#else
57-
System.Type type = typeof(BenchmarkDotNet.Autogenerated.UniqueProgramName).Assembly.GetType($"BenchmarkDotNet.Autogenerated.Runnable_{id}");
58-
type.GetMethod("Run", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static).Invoke(null, new System.Object[] { host, benchmarkName, diagnoserRunMode });
57+
global::System.Type type = typeof(global::BenchmarkDotNet.Autogenerated.UniqueProgramName).Assembly.GetType($"BenchmarkDotNet.Autogenerated.Runnable_{id}");
58+
type.GetMethod("Run", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.Static).Invoke(null, new global::System.Object[] { host, benchmarkName, diagnoserRunMode });
5959
#endif
6060
return 0;
6161
}
62-
catch (System.Exception oom) when (oom is System.OutOfMemoryException || oom is System.Reflection.TargetInvocationException reflection && reflection.InnerException is System.OutOfMemoryException)
62+
catch (global::System.Exception oom) when (oom is global::System.OutOfMemoryException || oom is global::System.Reflection.TargetInvocationException reflection && reflection.InnerException is global::System.OutOfMemoryException)
6363
{
6464
host.WriteLine();
6565
host.WriteLine("OutOfMemoryException!");
@@ -71,60 +71,60 @@ namespace BenchmarkDotNet.Autogenerated
7171

7272
return -1;
7373
}
74-
catch(System.Exception ex)
74+
catch(global::System.Exception ex)
7575
{
7676
host.WriteLine();
7777
host.WriteLine(ex.ToString());
7878
return -1;
7979
}
8080
finally
8181
{
82-
BenchmarkDotNet.Engines.HostExtensions.AfterAll(host);
82+
global::BenchmarkDotNet.Engines.HostExtensions.AfterAll(host);
8383

8484
host.Dispose();
8585
}
8686
}
8787
}
8888

8989
#if NETFRAMEWORK
90-
internal class DirtyAssemblyResolveHelper : System.IDisposable
90+
internal class DirtyAssemblyResolveHelper : global::System.IDisposable
9191
{
92-
internal DirtyAssemblyResolveHelper() => System.AppDomain.CurrentDomain.AssemblyResolve += HelpTheFrameworkToResolveTheAssembly;
92+
internal DirtyAssemblyResolveHelper() => global::System.AppDomain.CurrentDomain.AssemblyResolve += HelpTheFrameworkToResolveTheAssembly;
9393

94-
public void Dispose() => System.AppDomain.CurrentDomain.AssemblyResolve -= HelpTheFrameworkToResolveTheAssembly;
94+
public void Dispose() => global::System.AppDomain.CurrentDomain.AssemblyResolve -= HelpTheFrameworkToResolveTheAssembly;
9595

9696
/// <summary>
9797
/// according to https://msdn.microsoft.com/en-us/library/ff527268(v=vs.110).aspx
9898
/// "the handler is invoked whenever the runtime fails to bind to an assembly by name."
9999
/// </summary>
100100
/// <returns>not null when we find it manually, null when can't help</returns>
101-
private System.Reflection.Assembly HelpTheFrameworkToResolveTheAssembly(System.Object sender, System.ResolveEventArgs args)
101+
private global::System.Reflection.Assembly HelpTheFrameworkToResolveTheAssembly(global::System.Object sender, global::System.ResolveEventArgs args)
102102
{
103103
#if SHADOWCOPY // used for LINQPad
104-
const System.String shadowCopyFolderPath = @"$ShadowCopyFolderPath$";
104+
const global::System.String shadowCopyFolderPath = @"$ShadowCopyFolderPath$";
105105

106-
System.String guessedPath = System.IO.Path.Combine(shadowCopyFolderPath, $"{new System.Reflection.AssemblyName(args.Name).Name}.dll");
106+
global::System.String guessedPath = global::System.IO.Path.Combine(shadowCopyFolderPath, $"{new global::System.Reflection.AssemblyName(args.Name).Name}.dll");
107107

108-
return System.IO.File.Exists(guessedPath) ? System.Reflection.Assembly.LoadFrom(guessedPath) : null;
108+
return global::System.IO.File.Exists(guessedPath) ? global::System.Reflection.Assembly.LoadFrom(guessedPath) : null;
109109
#else
110-
System.Reflection.AssemblyName fullName = new System.Reflection.AssemblyName(args.Name);
111-
System.String simpleName = fullName.Name;
110+
global::System.Reflection.AssemblyName fullName = new global::System.Reflection.AssemblyName(args.Name);
111+
global::System.String simpleName = fullName.Name;
112112

113-
System.String guessedPath = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, $"{simpleName}.dll");
113+
global::System.String guessedPath = global::System.IO.Path.Combine(global::System.AppDomain.CurrentDomain.BaseDirectory, $"{simpleName}.dll");
114114

115-
if (!System.IO.File.Exists(guessedPath))
115+
if (!global::System.IO.File.Exists(guessedPath))
116116
{
117-
System.Console.WriteLine($"// Wrong assembly binding redirects for {args.Name}.");
117+
global::System.Console.WriteLine($"// Wrong assembly binding redirects for {args.Name}.");
118118
return null; // we can't help, and we also don't call Assembly.Load which if fails comes back here, creates endless loop and causes StackOverflow
119119
}
120120

121121
// the file is right there, but has most probably different version and there is no assembly binding redirect or there is a wrong one...
122122
// so we just load it and ignore the version mismatch
123123

124124
// we warn the user about that, in case some Super User want to be aware of that
125-
System.Console.WriteLine($"// Wrong assembly binding redirects for {simpleName}, loading it from disk anyway.");
125+
global::System.Console.WriteLine($"// Wrong assembly binding redirects for {simpleName}, loading it from disk anyway.");
126126

127-
return System.Reflection.Assembly.LoadFrom(guessedPath);
127+
return global::System.Reflection.Assembly.LoadFrom(guessedPath);
128128
#endif // SHADOWCOPY
129129
}
130130
}

0 commit comments

Comments
 (0)