diff --git a/src/BenchmarkDotNet/Extensions/ReflectionExtensions.cs b/src/BenchmarkDotNet/Extensions/ReflectionExtensions.cs index d3669d4029..e9683838cd 100644 --- a/src/BenchmarkDotNet/Extensions/ReflectionExtensions.cs +++ b/src/BenchmarkDotNet/Extensions/ReflectionExtensions.cs @@ -138,6 +138,11 @@ internal static Type[] GetRunnableBenchmarks(this Assembly assembly) .ToArray(); internal static bool ContainsRunnableBenchmarks(this Type type) + { + return GetRunnableBenchmarks(type).Any(); + } + + internal static MethodInfo[] GetRunnableBenchmarks(this Type type) { var typeInfo = type.GetTypeInfo(); @@ -145,9 +150,9 @@ internal static bool ContainsRunnableBenchmarks(this Type type) || typeInfo.IsSealed || typeInfo.IsNotPublic || typeInfo.IsGenericType && !IsRunnableGenericType(typeInfo)) - return false; + return Array.Empty(); - return typeInfo.GetBenchmarks().Any(); + return typeInfo.GetBenchmarks(); } private static MethodInfo[] GetBenchmarks(this TypeInfo typeInfo) @@ -213,4 +218,4 @@ private static bool IsRunnableGenericType(TypeInfo typeInfo) internal static bool IsLinqPad(this Assembly assembly) => assembly.FullName.IndexOf("LINQPAD", StringComparison.OrdinalIgnoreCase) >= 0; } -} \ No newline at end of file +} diff --git a/src/BenchmarkDotNet/Running/BenchmarkRunnerDirty.cs b/src/BenchmarkDotNet/Running/BenchmarkRunnerDirty.cs index 0d51ae08a7..5abc632d90 100644 --- a/src/BenchmarkDotNet/Running/BenchmarkRunnerDirty.cs +++ b/src/BenchmarkDotNet/Running/BenchmarkRunnerDirty.cs @@ -27,6 +27,8 @@ public static Summary Run(IConfig config = null, string[] args = null) [PublicAPI] public static Summary Run(Type type, IConfig config = null, string[] args = null) { + AssertNotNull(type, nameof(type)); + using (DirtyAssemblyResolveHelper.Create()) return RunWithExceptionHandling(() => RunWithDirtyAssemblyResolveHelper(type, config, args)); } @@ -34,6 +36,8 @@ public static Summary Run(Type type, IConfig config = null, string[] args = null [PublicAPI] public static Summary[] Run(Type[] types, IConfig config = null, string[] args = null) { + AssertNotNull(types, nameof(types)); + using (DirtyAssemblyResolveHelper.Create()) return RunWithExceptionHandling(() => RunWithDirtyAssemblyResolveHelper(types, config, args)); } @@ -41,6 +45,9 @@ public static Summary[] Run(Type[] types, IConfig config = null, string[] args = [PublicAPI] public static Summary Run(Type type, MethodInfo[] methods, IConfig config = null) { + AssertNotNull(type, nameof(type)); + AssertNotNull(methods, nameof(methods)); + using (DirtyAssemblyResolveHelper.Create()) return RunWithExceptionHandling(() => RunWithDirtyAssemblyResolveHelper(type, methods, config)); } @@ -48,6 +55,8 @@ public static Summary Run(Type type, MethodInfo[] methods, IConfig config = null [PublicAPI] public static Summary[] Run(Assembly assembly, IConfig config = null, string[] args = null) { + AssertNotNull(assembly, nameof(assembly)); + using (DirtyAssemblyResolveHelper.Create()) return RunWithExceptionHandling(() => RunWithDirtyAssemblyResolveHelper(assembly, config, args)); } @@ -55,6 +64,8 @@ public static Summary[] Run(Assembly assembly, IConfig config = null, string[] a [PublicAPI] public static Summary Run(BenchmarkRunInfo benchmarkRunInfo) { + AssertNotNull(benchmarkRunInfo, nameof(benchmarkRunInfo)); + using (DirtyAssemblyResolveHelper.Create()) return RunWithExceptionHandling(() => RunWithDirtyAssemblyResolveHelper(new[] { benchmarkRunInfo }).Single()); } @@ -62,6 +73,8 @@ public static Summary Run(BenchmarkRunInfo benchmarkRunInfo) [PublicAPI] public static Summary[] Run(BenchmarkRunInfo[] benchmarkRunInfos) { + AssertNotNull(benchmarkRunInfos, nameof(benchmarkRunInfos)); + using (DirtyAssemblyResolveHelper.Create()) return RunWithExceptionHandling(() => RunWithDirtyAssemblyResolveHelper(benchmarkRunInfos)); } @@ -90,30 +103,50 @@ public static Summary RunSource(string source, IConfig config = null) [MethodImpl(MethodImplOptions.NoInlining)] private static Summary RunWithDirtyAssemblyResolveHelper(Type type, IConfig config, string[] args) - => (args == null - ? BenchmarkRunnerClean.Run(new[] { BenchmarkConverter.TypeToBenchmarks(type, config) }) - : new BenchmarkSwitcher(new[] { type }).RunWithDirtyAssemblyResolveHelper(args, config, false)) + { + Validate(type); + + return (args == null + ? BenchmarkRunnerClean.Run(new[] { BenchmarkConverter.TypeToBenchmarks(type, config) }) + : new BenchmarkSwitcher(new[] { type }).RunWithDirtyAssemblyResolveHelper(args, config, false)) .Single(); + } [MethodImpl(MethodImplOptions.NoInlining)] private static Summary RunWithDirtyAssemblyResolveHelper(Type type, MethodInfo[] methods, IConfig config = null) - => BenchmarkRunnerClean.Run(new[] { BenchmarkConverter.MethodsToBenchmarks(type, methods, config) }).Single(); + { + Validate(type, methods); + + return BenchmarkRunnerClean.Run(new[] { BenchmarkConverter.MethodsToBenchmarks(type, methods, config) }).Single(); + } [MethodImpl(MethodImplOptions.NoInlining)] private static Summary[] RunWithDirtyAssemblyResolveHelper(Assembly assembly, IConfig config, string[] args) - => args == null + { + Validate(assembly); + + return args == null ? BenchmarkRunnerClean.Run(assembly.GetRunnableBenchmarks().Select(type => BenchmarkConverter.TypeToBenchmarks(type, config)).ToArray()) : new BenchmarkSwitcher(assembly).RunWithDirtyAssemblyResolveHelper(args, config, false).ToArray(); + } [MethodImpl(MethodImplOptions.NoInlining)] private static Summary[] RunWithDirtyAssemblyResolveHelper(Type[] types, IConfig config, string[] args) - => args == null + { + Validate(types); + + return args == null ? BenchmarkRunnerClean.Run(types.Select(type => BenchmarkConverter.TypeToBenchmarks(type, config)).ToArray()) : new BenchmarkSwitcher(types).RunWithDirtyAssemblyResolveHelper(args, config, false).ToArray(); + } [MethodImpl(MethodImplOptions.NoInlining)] private static Summary[] RunWithDirtyAssemblyResolveHelper(BenchmarkRunInfo[] benchmarkRunInfos) - => BenchmarkRunnerClean.Run(benchmarkRunInfos); + { + Validate(benchmarkRunInfos); + + return BenchmarkRunnerClean.Run(benchmarkRunInfos); + } [MethodImpl(MethodImplOptions.NoInlining)] private static Summary RunUrlWithDirtyAssemblyResolveHelper(string url, IConfig config = null) @@ -152,5 +185,77 @@ private static Summary[] RunWithExceptionHandling(Func run) return new[] { Summary.NothingToRun(e.Message, string.Empty, string.Empty) }; } } + + private static void AssertNotNull(T instance, string paramName) + { + if (instance is null) + throw new ArgumentNullException(paramName); + } + + private static void Validate(Type[] types) + { + if (types.IsEmpty()) + throw new InvalidBenchmarkDeclarationException("No types provided."); + + if (types.Any(t => t is null)) + throw new InvalidBenchmarkDeclarationException("Null not allowed."); + + var nonBenchmarkTypes = types.Where(t => !t.ContainsRunnableBenchmarks()).ToArray(); + if (!nonBenchmarkTypes.IsEmpty()) + { + var invalidNames = string.Join("\n", nonBenchmarkTypes.Select(type => $" {type.FullName}")); + throw new InvalidBenchmarkDeclarationException($"Invalid Types:\n{invalidNames}\nOnly public, non-generic (closed generic types with public parameterless ctors are supported), non-abstract, non-sealed, non-static types with public instance [Benchmark] method(s) are supported."); + } + } + + private static void Validate(BenchmarkRunInfo[] benchmarkRunInfos) + { + if (benchmarkRunInfos.IsEmpty()) + throw new InvalidBenchmarkDeclarationException($"No BenchmarkRunInfos provided."); + + if (benchmarkRunInfos.Any(v => v is null)) + throw new InvalidBenchmarkDeclarationException($"Null not allowed."); + + foreach (var benchmarkRunInfo in benchmarkRunInfos) + { + if (benchmarkRunInfo.Config is null || + benchmarkRunInfo.BenchmarksCases is null || + benchmarkRunInfo.BenchmarksCases.IsEmpty() || + benchmarkRunInfo.BenchmarksCases.Any(c => c is null)) + throw new InvalidBenchmarkDeclarationException("BenchmarkRunInfo do not support null values."); + } + } + + private static void Validate(Type type) + { + if (!type.ContainsRunnableBenchmarks()) + throw new InvalidBenchmarkDeclarationException($"Type {type} is invalid. Only public, non-generic (closed generic types with public parameterless ctors are supported), non-abstract, non-sealed, non-static types with public instance [Benchmark] method(s) are supported."); + } + + private static void Validate(Type type, MethodInfo[] methods) + { + if (!type.ContainsRunnableBenchmarks()) + throw new InvalidBenchmarkDeclarationException($"Type {type} is invalid. Only public, non-generic (closed generic types with public parameterless ctors are supported), non-abstract, non-sealed, non-static types with public instance [Benchmark] method(s) are supported."); + + if (methods.IsEmpty()) + throw new InvalidBenchmarkDeclarationException($"No methods provided for {type}."); + + if (methods.Any(m => m is null)) + throw new InvalidBenchmarkDeclarationException($"Null not allowed."); + + var benchmarkMethods = type.GetRunnableBenchmarks(); + var invalidMethods = methods.Except(benchmarkMethods).ToArray(); + if (!invalidMethods.IsEmpty()) + { + var invalidNames = string.Join("\n", invalidMethods.Select(m => $" {m.ReflectedType?.FullName}.{m.Name}")); + throw new InvalidBenchmarkDeclarationException($"Invalid methods:\n{invalidNames}\nMethods must be of {type.FullName} type. Only public, non-generic (closed generic types with public parameterless ctors are supported), non-abstract, non-sealed, non-static types with public instance [Benchmark] method(s) are supported."); + } + } + + private static void Validate(Assembly assembly) + { + if (assembly.GetRunnableBenchmarks().IsEmpty()) + throw new InvalidBenchmarkDeclarationException("No benchmarks to choose from. Make sure you provided public non-sealed non-static types with public [Benchmark] methods."); + } } }