Improve ParamsValidator (#2283)
This commit is contained in:
Родитель
bfb192769b
Коммит
492e58eb6f
|
@ -158,31 +158,26 @@ namespace BenchmarkDotNet.Extensions
|
|||
.Where(method => method.GetCustomAttributes(true).OfType<BenchmarkAttribute>().Any())
|
||||
.ToArray();
|
||||
|
||||
internal static (string Name, TAttribute Attribute, bool IsPrivate, bool IsStatic, Type ParameterType)[] GetTypeMembersWithGivenAttribute<TAttribute>(this Type type, BindingFlags reflectionFlags)
|
||||
where TAttribute : Attribute
|
||||
internal static (string Name, TAttribute Attribute, bool IsStatic, Type ParameterType)[]
|
||||
GetTypeMembersWithGivenAttribute<TAttribute>(this Type type, BindingFlags reflectionFlags) where TAttribute : Attribute
|
||||
{
|
||||
var allFields = type.GetFields(reflectionFlags)
|
||||
.Select(f => (
|
||||
Name: f.Name,
|
||||
Attribute: f.ResolveAttribute<TAttribute>(),
|
||||
IsPrivate: f.IsPrivate,
|
||||
IsStatic: f.IsStatic,
|
||||
ParameterType: f.FieldType));
|
||||
var allFields = type
|
||||
.GetFields(reflectionFlags)
|
||||
.Select(f => (
|
||||
Name: f.Name,
|
||||
Attribute: f.ResolveAttribute<TAttribute>(),
|
||||
IsStatic: f.IsStatic,
|
||||
ParameterType: f.FieldType));
|
||||
|
||||
var allProperties = type.GetProperties(reflectionFlags)
|
||||
.Select(p => (
|
||||
Name: p.Name,
|
||||
Attribute: p.ResolveAttribute<TAttribute>(),
|
||||
IsPrivate: p.GetSetMethod() == null,
|
||||
IsStatic: p.GetSetMethod() != null && p.GetSetMethod().IsStatic,
|
||||
PropertyType: p.PropertyType));
|
||||
var allProperties = type
|
||||
.GetProperties(reflectionFlags)
|
||||
.Select(p => (
|
||||
Name: p.Name,
|
||||
Attribute: p.ResolveAttribute<TAttribute>(),
|
||||
IsStatic: p.GetSetMethod() != null && p.GetSetMethod().IsStatic,
|
||||
PropertyType: p.PropertyType));
|
||||
|
||||
var joined = allFields.Concat(allProperties).Where(member => member.Attribute != null).ToArray();
|
||||
|
||||
foreach (var member in joined.Where(m => m.IsPrivate))
|
||||
throw new InvalidOperationException($"Member \"{member.Name}\" must be public if it has the [{typeof(TAttribute).Name}] attribute applied to it");
|
||||
|
||||
return joined;
|
||||
return allFields.Concat(allProperties).Where(member => member.Attribute != null).ToArray();
|
||||
}
|
||||
|
||||
internal static bool IsStackOnlyWithImplicitCast(this Type argumentType, object? argumentInstance)
|
||||
|
|
|
@ -20,7 +20,9 @@ namespace BenchmarkDotNet.Validators
|
|||
|
||||
private IEnumerable<ValidationError> Validate(Type type)
|
||||
{
|
||||
foreach (var memberInfo in type.GetMembers(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
|
||||
const BindingFlags reflectionFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance |
|
||||
BindingFlags.FlattenHierarchy;
|
||||
foreach (var memberInfo in type.GetMembers(reflectionFlags))
|
||||
{
|
||||
var attributes = new Attribute[]
|
||||
{
|
||||
|
@ -40,17 +42,33 @@ namespace BenchmarkDotNet.Validators
|
|||
yield return new ValidationError(TreatsWarningsAsErrors,
|
||||
$"Unable to use {name} with {attributeString} at the same time. Please, use a single attribute.");
|
||||
|
||||
if (memberInfo is FieldInfo fieldInfo && (fieldInfo.IsLiteral || fieldInfo.IsInitOnly))
|
||||
if (memberInfo is FieldInfo fieldInfo)
|
||||
{
|
||||
string modifier = fieldInfo.IsInitOnly ? "readonly" : "constant";
|
||||
yield return new ValidationError(TreatsWarningsAsErrors,
|
||||
$"Unable to use {name} with {attributeString} because it's a {modifier} field. Please, remove the {modifier} modifier.");
|
||||
if (fieldInfo.IsLiteral || fieldInfo.IsInitOnly)
|
||||
{
|
||||
string modifier = fieldInfo.IsInitOnly ? "readonly" : "constant";
|
||||
yield return new ValidationError(TreatsWarningsAsErrors,
|
||||
$"Unable to use {name} with {attributeString} because it's a {modifier} field. Please, remove the {modifier} modifier.");
|
||||
}
|
||||
|
||||
if (!fieldInfo.IsPublic)
|
||||
yield return new ValidationError(TreatsWarningsAsErrors,
|
||||
$"Unable to use {name} with {attributeString} because it's not public. Please, make it public.");
|
||||
}
|
||||
|
||||
if (memberInfo is PropertyInfo propertyInfo && propertyInfo.IsInitOnly())
|
||||
if (memberInfo is PropertyInfo propertyInfo)
|
||||
{
|
||||
yield return new ValidationError(TreatsWarningsAsErrors,
|
||||
$"Unable to use {name} with {attributeString} because it's init-only. Please, provide a public setter.");
|
||||
if (propertyInfo.IsInitOnly())
|
||||
yield return new ValidationError(TreatsWarningsAsErrors,
|
||||
$"Unable to use {name} with {attributeString} because it's init-only. Please, provide a public setter.");
|
||||
|
||||
if (propertyInfo.SetMethod == null)
|
||||
yield return new ValidationError(TreatsWarningsAsErrors,
|
||||
$"Unable to use {name} with {attributeString} because it has no setter. Please, provide a public setter.");
|
||||
|
||||
if (propertyInfo.SetMethod != null && !propertyInfo.SetMethod.IsPublic)
|
||||
yield return new ValidationError(TreatsWarningsAsErrors,
|
||||
$"Unable to use {name} with {attributeString} because its setter is not public. Please, make the setter public.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,22 +30,6 @@ namespace BenchmarkDotNet.IntegrationTests
|
|||
public void Benchmark() => Console.WriteLine($"// ### New Parameter {ParamProperty} ###");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParamsDoesNotSupportPropertyWithoutPublicSetter()
|
||||
{
|
||||
// System.InvalidOperationException : Property "ParamProperty" must be public and writable if it has the [Params(..)] attribute applied to it
|
||||
Assert.Throws<InvalidOperationException>(() => CanExecute<ParamsTestPrivatePropertyError>());
|
||||
}
|
||||
|
||||
public class ParamsTestPrivatePropertyError
|
||||
{
|
||||
[Params(1, 2)]
|
||||
public int ParamProperty { get; private set; }
|
||||
|
||||
[Benchmark]
|
||||
public void Benchmark() => Console.WriteLine($"// ### New Parameter {ParamProperty} ###");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParamsSupportPublicFields()
|
||||
{
|
||||
|
@ -67,22 +51,6 @@ namespace BenchmarkDotNet.IntegrationTests
|
|||
public void Benchmark() => Console.WriteLine($"// ### New Parameter {ParamField} ###");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParamsDoesNotSupportPrivateFields()
|
||||
{
|
||||
// System.InvalidOperationException : Field "ParamField" must be public if it has the [Params(..)] attribute applied to it
|
||||
Assert.Throws<InvalidOperationException>(() => CanExecute<ParamsTestPrivateFieldError>());
|
||||
}
|
||||
|
||||
public class ParamsTestPrivateFieldError
|
||||
{
|
||||
[Params(1, 2)]
|
||||
private int ParamField = 0;
|
||||
|
||||
[Benchmark]
|
||||
public void Benchmark() => Console.WriteLine($"// ### New Parameter {ParamField} ###");
|
||||
}
|
||||
|
||||
public enum NestedOne
|
||||
{
|
||||
SampleValue = 1234
|
||||
|
@ -249,31 +217,5 @@ namespace BenchmarkDotNet.IntegrationTests
|
|||
throw new ArgumentException($"{nameof(StaticParamProperty)} has wrong value: {StaticParamProperty}!");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParamsPropertiesMustHavePublicSetter()
|
||||
=> Assert.Throws<InvalidOperationException>(() => CanExecute<WithStaticParamsPropertyWithNoPublicSetter>());
|
||||
|
||||
public class WithStaticParamsPropertyWithNoPublicSetter
|
||||
{
|
||||
[Params(3)]
|
||||
public static int StaticParamProperty { get; private set; }
|
||||
|
||||
[Benchmark]
|
||||
public int Benchmark() => StaticParamProperty;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParamsFieldsMustBePublic()
|
||||
=> Assert.Throws<InvalidOperationException>(() => CanExecute<WithStaticPrivateParamsField>());
|
||||
|
||||
public class WithStaticPrivateParamsField
|
||||
{
|
||||
[Params(4)]
|
||||
private static int StaticParamField = 0;
|
||||
|
||||
[Benchmark]
|
||||
public int Benchmark() => StaticParamField;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -290,40 +290,6 @@ namespace BenchmarkDotNet.Tests.Validators
|
|||
public void NonThrowing() { }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NonPublicPropertiesWithParamsAreDiscovered()
|
||||
{
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => ExecutionValidator.FailOnError.Validate(BenchmarkConverter.TypeToBenchmarks(typeof(NonPublicPropertyWithParams))));
|
||||
}
|
||||
|
||||
public class NonPublicPropertyWithParams
|
||||
{
|
||||
[Params(1)]
|
||||
[UsedImplicitly]
|
||||
internal int Property { get; set; }
|
||||
|
||||
[Benchmark]
|
||||
public void NonThrowing() { }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyWithoutPublicSetterParamsAreDiscovered()
|
||||
{
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => ExecutionValidator.FailOnError.Validate(BenchmarkConverter.TypeToBenchmarks(typeof(PropertyWithoutPublicSetterParams))));
|
||||
}
|
||||
|
||||
public class PropertyWithoutPublicSetterParams
|
||||
{
|
||||
[Params(1)]
|
||||
[UsedImplicitly]
|
||||
internal int Property { get; }
|
||||
|
||||
[Benchmark]
|
||||
public void NonThrowing() { }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FieldsWithoutParamsValuesAreDiscovered()
|
||||
{
|
||||
|
|
|
@ -6,11 +6,13 @@ using BenchmarkDotNet.Running;
|
|||
using BenchmarkDotNet.Validators;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
#pragma warning disable CS0414
|
||||
|
||||
namespace BenchmarkDotNet.Tests.Validators
|
||||
{
|
||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
|
||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
|
||||
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
|
||||
public class ParamsValidatorTests
|
||||
{
|
||||
private readonly ITestOutputHelper output;
|
||||
|
@ -56,6 +58,18 @@ namespace BenchmarkDotNet.Tests.Validators
|
|||
[Fact] public void PropMultiple2Test() => Check<PropMultiple2>(nameof(PropMultiple2.Input), "single attribute", P, Ps);
|
||||
[Fact] public void PropMultiple3Test() => Check<PropMultiple3>(nameof(PropMultiple3.Input), "single attribute", Pa, Ps);
|
||||
[Fact] public void PropMultiple4Test() => Check<PropMultiple4>(nameof(PropMultiple4.Input), "single attribute", P, Pa, Ps);
|
||||
[Fact] public void PrivateSetter1Test() => Check<PrivateSetter1>(nameof(PrivateSetter1.Input), "setter is not public", P);
|
||||
[Fact] public void PrivateSetter2Test() => Check<PrivateSetter2>(nameof(PrivateSetter2.Input), "setter is not public", Pa);
|
||||
[Fact] public void PrivateSetter3Test() => Check<PrivateSetter3>(nameof(PrivateSetter3.Input), "setter is not public", Ps);
|
||||
[Fact] public void NoSetter1Test() => Check<NoSetter1>(nameof(NoSetter1.Input), "no setter", P);
|
||||
[Fact] public void NoSetter2Test() => Check<NoSetter2>(nameof(NoSetter2.Input), "no setter", Pa);
|
||||
[Fact] public void NoSetter3Test() => Check<NoSetter3>(nameof(NoSetter3.Input), "no setter", Ps);
|
||||
[Fact] public void InternalField1Test() => Check<InternalField1>(nameof(InternalField1.Input), "it's not public", P);
|
||||
[Fact] public void InternalField2Test() => Check<InternalField2>(nameof(InternalField2.Input), "it's not public", Pa);
|
||||
[Fact] public void InternalField3Test() => Check<InternalField3>(nameof(InternalField3.Input), "it's not public", Ps);
|
||||
[Fact] public void InternalProp1Test() => Check<InternalProp1>(nameof(InternalProp1.Input), "setter is not public", P);
|
||||
[Fact] public void InternalProp2Test() => Check<InternalProp2>(nameof(InternalProp2.Input), "setter is not public", Pa);
|
||||
[Fact] public void InternalProp3Test() => Check<InternalProp3>(nameof(InternalProp3.Input), "setter is not public", Ps);
|
||||
|
||||
public class Base
|
||||
{
|
||||
|
@ -119,6 +133,78 @@ namespace BenchmarkDotNet.Tests.Validators
|
|||
public readonly bool Input = false;
|
||||
}
|
||||
|
||||
public class PrivateSetter1 : Base
|
||||
{
|
||||
[Params(false, true)]
|
||||
public bool Input { get; private set; }
|
||||
}
|
||||
|
||||
public class PrivateSetter2 : Base
|
||||
{
|
||||
[ParamsAllValues]
|
||||
public bool Input { get; private set; }
|
||||
}
|
||||
|
||||
public class PrivateSetter3 : Base
|
||||
{
|
||||
[ParamsSource(nameof(Source))]
|
||||
public bool Input { get; private set; }
|
||||
}
|
||||
|
||||
public class NoSetter1 : Base
|
||||
{
|
||||
[Params(false, true)]
|
||||
public bool Input { get; } = false;
|
||||
}
|
||||
|
||||
public class NoSetter2 : Base
|
||||
{
|
||||
[ParamsAllValues]
|
||||
public bool Input { get; } = false;
|
||||
}
|
||||
|
||||
public class NoSetter3 : Base
|
||||
{
|
||||
[ParamsSource(nameof(Source))]
|
||||
public bool Input { get; } = false;
|
||||
}
|
||||
|
||||
public class InternalField1 : Base
|
||||
{
|
||||
[Params(false, true)]
|
||||
internal bool Input = false;
|
||||
}
|
||||
|
||||
public class InternalField2 : Base
|
||||
{
|
||||
[ParamsAllValues]
|
||||
internal bool Input = false;
|
||||
}
|
||||
|
||||
public class InternalField3 : Base
|
||||
{
|
||||
[ParamsSource(nameof(Source))]
|
||||
internal bool Input = false;
|
||||
}
|
||||
|
||||
public class InternalProp1 : Base
|
||||
{
|
||||
[Params(false, true)]
|
||||
internal bool Input { get; set; }
|
||||
}
|
||||
|
||||
public class InternalProp2 : Base
|
||||
{
|
||||
[ParamsAllValues]
|
||||
internal bool Input { get; set; }
|
||||
}
|
||||
|
||||
public class InternalProp3 : Base
|
||||
{
|
||||
[ParamsSource(nameof(Source))]
|
||||
internal bool Input { get; set; }
|
||||
}
|
||||
|
||||
public class FieldMultiple1 : Base
|
||||
{
|
||||
[Params(false, true)]
|
||||
|
|
Загрузка…
Ссылка в новой задаче