Skip to content

Commit e07363b

Browse files
Add ParamsValidator for readonly/const fields (see #2279)
1 parent 5148fe6 commit e07363b

File tree

4 files changed

+165
-1
lines changed

4 files changed

+165
-1
lines changed

src/BenchmarkDotNet/Configs/DefaultConfig.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public IEnumerable<IValidator> GetValidators()
6868
yield return GenericBenchmarksValidator.DontFailOnError;
6969
yield return DeferredExecutionValidator.FailOnError;
7070
yield return ParamsAllValuesValidator.FailOnError;
71+
yield return ParamsValidator.FailOnError;
7172
}
7273

7374
public IOrderer Orderer => null;

src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ public static class ImmutableConfigBuilder
2727
ShadowCopyValidator.DontFailOnError,
2828
JitOptimizationsValidator.DontFailOnError,
2929
DeferredExecutionValidator.DontFailOnError,
30-
ParamsAllValuesValidator.FailOnError
30+
ParamsAllValuesValidator.FailOnError,
31+
ParamsValidator.FailOnError
3132
};
3233

3334
/// <summary>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
using BenchmarkDotNet.Attributes;
6+
using BenchmarkDotNet.Extensions;
7+
8+
namespace BenchmarkDotNet.Validators
9+
{
10+
public class ParamsValidator : IValidator
11+
{
12+
public static readonly ParamsValidator FailOnError = new ();
13+
14+
public bool TreatsWarningsAsErrors => true;
15+
16+
public IEnumerable<ValidationError> Validate(ValidationParameters input) => input.Benchmarks
17+
.Select(benchmark => benchmark.Descriptor.Type)
18+
.Distinct()
19+
.SelectMany(Validate);
20+
21+
private IEnumerable<ValidationError> Validate(Type type)
22+
{
23+
foreach (var memberInfo in type.GetMembers(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
24+
{
25+
var attributes = new Attribute[]
26+
{
27+
memberInfo.ResolveAttribute<ParamsAttribute>(),
28+
memberInfo.ResolveAttribute<ParamsAllValuesAttribute>(),
29+
memberInfo.ResolveAttribute<ParamsSourceAttribute>()
30+
}
31+
.Where(attribute => attribute != null)
32+
.ToList();
33+
if (attributes.IsEmpty())
34+
continue;
35+
36+
string name = $"{type.Name}.{memberInfo.Name}";
37+
string? attributeString = string.Join(", ", attributes.Select(attribute => $"[{attribute.GetType().Name.Replace(nameof(Attribute), "")}]"));
38+
39+
if (memberInfo is FieldInfo fieldInfo && (fieldInfo.IsLiteral || fieldInfo.IsInitOnly))
40+
{
41+
string modifier = fieldInfo.IsInitOnly ? "readonly" : "constant";
42+
yield return new ValidationError(TreatsWarningsAsErrors,
43+
$"Unable to use {name} with {attributeString} because it's a {modifier} field. Please, remove the {modifier} modifier.");
44+
}
45+
}
46+
}
47+
}
48+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
using System.Collections.Generic;
2+
using System.Diagnostics.CodeAnalysis;
3+
using System.Linq;
4+
using BenchmarkDotNet.Attributes;
5+
using BenchmarkDotNet.Running;
6+
using BenchmarkDotNet.Validators;
7+
using Xunit;
8+
using Xunit.Abstractions;
9+
10+
namespace BenchmarkDotNet.Tests.Validators
11+
{
12+
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
13+
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
14+
public class ParamsValidatorTests
15+
{
16+
private readonly ITestOutputHelper output;
17+
18+
public ParamsValidatorTests(ITestOutputHelper output)
19+
{
20+
this.output = output;
21+
}
22+
23+
private void Check<T>(params string[] messageParts)
24+
{
25+
var typeToBenchmarks = BenchmarkConverter.TypeToBenchmarks(typeof(T));
26+
Assert.NotEmpty(typeToBenchmarks.BenchmarksCases);
27+
28+
var validationErrors = ParamsValidator.FailOnError.Validate(typeToBenchmarks).ToList();
29+
output.WriteLine("Number of validation errors: " + validationErrors.Count);
30+
foreach (var error in validationErrors)
31+
output.WriteLine("* " + error.Message);
32+
33+
Assert.Single(validationErrors);
34+
foreach (string messagePart in messageParts)
35+
Assert.Contains(messagePart, validationErrors.Single().Message);
36+
}
37+
38+
private const string P = "[Params]";
39+
private const string Pa = "[ParamsAllValues]";
40+
private const string Ps = "[ParamsSource]";
41+
42+
[Fact] public void Const1Test() => Check<Const1>(nameof(Const1.Input), "constant", P);
43+
[Fact] public void Const2Test() => Check<Const2>(nameof(Const2.Input), "constant", Pa);
44+
[Fact] public void Const3Test() => Check<Const3>(nameof(Const3.Input), "constant", Ps);
45+
[Fact] public void StaticReadonly1Test() => Check<StaticReadonly1>(nameof(StaticReadonly1.Input), "readonly", P);
46+
[Fact] public void StaticReadonly2Test() => Check<StaticReadonly2>(nameof(StaticReadonly2.Input), "readonly", Pa);
47+
[Fact] public void StaticReadonly3Test() => Check<StaticReadonly3>(nameof(StaticReadonly3.Input), "readonly", Ps);
48+
[Fact] public void NonStaticReadonly1Test() => Check<NonStaticReadonly1>(nameof(NonStaticReadonly1.Input), "readonly", P);
49+
[Fact] public void NonStaticReadonly2Test() => Check<NonStaticReadonly2>(nameof(NonStaticReadonly2.Input), "readonly", Pa);
50+
[Fact] public void NonStaticReadonly3Test() => Check<NonStaticReadonly3>(nameof(NonStaticReadonly3.Input), "readonly", Ps);
51+
52+
public class Base
53+
{
54+
[Benchmark]
55+
public void Foo() { }
56+
57+
public static IEnumerable<bool> Source() => new[] { false, true };
58+
}
59+
60+
public class Const1 : Base
61+
{
62+
[Params(false, true)]
63+
public const bool Input = false;
64+
}
65+
66+
public class Const2 : Base
67+
{
68+
[ParamsAllValues]
69+
public const bool Input = false;
70+
}
71+
72+
public class Const3 : Base
73+
{
74+
[ParamsSource(nameof(Source))]
75+
public const bool Input = false;
76+
}
77+
78+
public class StaticReadonly1 : Base
79+
{
80+
[Params(false, true)]
81+
public static readonly bool Input = false;
82+
}
83+
84+
public class StaticReadonly2 : Base
85+
{
86+
[ParamsAllValues]
87+
public static readonly bool Input = false;
88+
}
89+
90+
public class StaticReadonly3 : Base
91+
{
92+
[ParamsSource(nameof(Source))]
93+
public static readonly bool Input = false;
94+
}
95+
96+
public class NonStaticReadonly1 : Base
97+
{
98+
[Params(false, true)]
99+
public readonly bool Input = false;
100+
}
101+
102+
public class NonStaticReadonly2 : Base
103+
{
104+
[ParamsAllValues]
105+
public readonly bool Input = false;
106+
}
107+
108+
public class NonStaticReadonly3 : Base
109+
{
110+
[ParamsSource(nameof(Source))]
111+
public readonly bool Input = false;
112+
}
113+
}
114+
}

0 commit comments

Comments
 (0)