Skip to content

Commit 55538ba

Browse files
committed
When determining whether a class has any benchmark methods, iterate through its ancestors
1 parent 3178a18 commit 55538ba

File tree

4 files changed

+91
-12
lines changed

4 files changed

+91
-12
lines changed

src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,16 +118,16 @@
118118
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119119
</resheader>
120120
<data name="BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods_Description" xml:space="preserve">
121-
<value>The referenced benchmark class must have at least one method annotated with the [Benchmark] attribute</value>
121+
<value>The referenced benchmark class (or any of its inherited classes) must have at least one method annotated with the [Benchmark] attribute</value>
122122
</data>
123123
<data name="BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods_MessageFormat" xml:space="preserve">
124-
<value>Intended benchmark class '{0}' has no method(s) annotated with the [Benchmark] attribute</value>
124+
<value>Intended benchmark class '{0}' (or any of its ancestors) has no method(s) annotated with the [Benchmark] attribute</value>
125125
</data>
126126
<data name="BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract_MessageFormat" xml:space="preserve">
127127
<value>Referenced benchmark class '{0}' cannot be abstract</value>
128128
</data>
129129
<data name="BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods_Title" xml:space="preserve">
130-
<value>Benchmark class has no annotated method(s)</value>
130+
<value>Benchmark class (or any of its ancestors) has no annotated method(s)</value>
131131
</data>
132132
<data name="BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract_Title" xml:space="preserve">
133133
<value>Benchmark classes must be non-abstract</value>

src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,21 +116,28 @@ private static void Analyze(SyntaxNodeAnalysisContext context)
116116

117117
bool HasBenchmarkAttribute()
118118
{
119-
foreach (var member in benchmarkClassTypeSymbol.GetMembers())
119+
var baseType = benchmarkClassTypeSymbol;
120+
121+
while (baseType != null && baseType.SpecialType != SpecialType.System_Object)
120122
{
121-
if (member is IMethodSymbol)
123+
foreach (var member in baseType.GetMembers())
122124
{
123-
foreach (var attributeData in member.GetAttributes())
125+
if (member is IMethodSymbol { MethodKind: MethodKind.Ordinary })
124126
{
125-
if (attributeData.AttributeClass != null)
127+
foreach (var attributeData in member.GetAttributes())
126128
{
127-
if (attributeData.AttributeClass.Equals(benchmarkAttributeTypeSymbol, SymbolEqualityComparer.Default))
129+
if (attributeData.AttributeClass != null)
128130
{
129-
return true;
131+
if (attributeData.AttributeClass.Equals(benchmarkAttributeTypeSymbol, SymbolEqualityComparer.Default))
132+
{
133+
return true;
134+
}
130135
}
131136
}
132137
}
133138
}
139+
140+
baseType = baseType.BaseType;
134141
}
135142

136143
return false;

tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,78 @@ private void BenchmarkMethod3()
9797
await RunAsync();
9898
}
9999

100+
[Theory, CombinatorialData]
101+
public async Task Invoking_with_type_argument_class_having_at_least_one_public_method_annotated_with_the_benchmark_attribute_in_one_of_its_ancestor_classes_should_not_trigger_diagnostic([CombinatorialValues("", "abstract ")] string abstractModifier)
102+
{
103+
const string classWithOneBenchmarkMethodName = "TopLevelBenchmarkClass";
104+
105+
const string testCode = /* lang=c#-test */ $$"""
106+
using BenchmarkDotNet.Running;
107+
108+
public class Program
109+
{
110+
public static void Main(string[] args) {
111+
BenchmarkRunner.Run<{{classWithOneBenchmarkMethodName}}>();
112+
}
113+
}
114+
""";
115+
116+
const string benchmarkClassDocument = /* lang=c#-test */ $$"""
117+
using BenchmarkDotNet.Attributes;
118+
119+
public class {{classWithOneBenchmarkMethodName}} : BenchmarkClassAncestor1
120+
{
121+
}
122+
""";
123+
124+
var benchmarkClassAncestor1Document = /* lang=c#-test */ $$"""
125+
using BenchmarkDotNet.Attributes;
126+
127+
public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2
128+
{
129+
}
130+
""";
131+
132+
var benchmarkClassAncestor2Document = /* lang=c#-test */ $$"""
133+
using BenchmarkDotNet.Attributes;
134+
135+
public {{abstractModifier}}class BenchmarkClassAncestor2 : BenchmarkClassAncestor3
136+
{
137+
}
138+
""";
139+
140+
var benchmarkClassAncestor3Document = /* lang=c#-test */ $$"""
141+
using BenchmarkDotNet.Attributes;
142+
143+
public {{abstractModifier}}class BenchmarkClassAncestor3
144+
{
145+
[Benchmark]
146+
public void BenchmarkMethod()
147+
{
148+
149+
}
150+
151+
public void BenchmarkMethod2()
152+
{
153+
154+
}
155+
156+
private void BenchmarkMethod3()
157+
{
158+
159+
}
160+
}
161+
""";
162+
163+
TestCode = testCode;
164+
AddSource(benchmarkClassDocument);
165+
AddSource(benchmarkClassAncestor1Document);
166+
AddSource(benchmarkClassAncestor2Document);
167+
AddSource(benchmarkClassAncestor3Document);
168+
169+
await RunAsync();
170+
}
171+
100172
[Fact]
101173
public async Task Invoking_with_type_argument_class_having_no_public_method_annotated_with_the_benchmark_attribute_should_trigger_diagnostic()
102174
{

0 commit comments

Comments
 (0)