From 690fe0b3d20e3d58f3ff9747109ddd8aeb0e9ed5 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Wed, 5 May 2021 21:56:02 +0800 Subject: [PATCH] Performance Improvement - Perform test filtering earlier in process --- .../Explorers/AssemblyExplorer.cs | 113 ++++++++++++++---- .../Runner/Impl/AssemblyRunner.cs | 15 ++- .../Runner/Impl/DefaultRunner.cs | 53 ++------ .../Utility/KeyValuePairExtensions.cs | 16 +++ 4 files changed, 120 insertions(+), 77 deletions(-) create mode 100644 src/Machine.Specifications/Utility/KeyValuePairExtensions.cs diff --git a/src/Machine.Specifications/Explorers/AssemblyExplorer.cs b/src/Machine.Specifications/Explorers/AssemblyExplorer.cs index 0a5d20484..c24a33be6 100644 --- a/src/Machine.Specifications/Explorers/AssemblyExplorer.cs +++ b/src/Machine.Specifications/Explorers/AssemblyExplorer.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; - using Machine.Specifications.Factories; using Machine.Specifications.Model; +using Machine.Specifications.Runner; using Machine.Specifications.Sdk; using Machine.Specifications.Utility; @@ -19,37 +19,73 @@ public AssemblyExplorer() _contextFactory = new ContextFactory(); } + public Context FindContexts(Type type, RunOptions options = null) + { + var types = new[] {type}; + + return types + .Where(IsContext) + .FilterBy(options) + .Select(CreateContextFrom) + .FirstOrDefault(); + } + + public Context FindContexts(FieldInfo info, RunOptions options = null) + { + var types = new[] {info.DeclaringType}; + + return types + .Where(IsContext) + .FilterBy(options) + .Select(t => CreateContextFrom(t, info)) + .FirstOrDefault(); + } + public IEnumerable FindContextsIn(Assembly assembly) { - return EnumerateContextsIn(assembly).Select(CreateContextFrom); + return FindContextsIn(assembly, options: null); + } + + public IEnumerable FindContextsIn(Assembly assembly, RunOptions options) + { + return EnumerateContextsIn(assembly) + .FilterBy(options) + .OrderBy(t => t.Namespace) + .Select(CreateContextFrom); } public IEnumerable FindContextsIn(Assembly assembly, string targetNamespace) + { + return FindContextsIn(assembly, targetNamespace, options: null); + } + + public IEnumerable FindContextsIn(Assembly assembly, string targetNamespace, RunOptions options) { return EnumerateContextsIn(assembly) - .Where(x => x.Namespace == targetNamespace) - .Select(CreateContextFrom); + .Where(x => x.Namespace == targetNamespace) + .FilterBy(options) + .Select(CreateContextFrom); } public IEnumerable FindAssemblyWideContextCleanupsIn(Assembly assembly) { return assembly.GetExportedTypes() - .Where(x => x.GetInterfaces().Contains(typeof(ICleanupAfterEveryContextInAssembly))) - .Select(x => (ICleanupAfterEveryContextInAssembly)Activator.CreateInstance(x)); + .Where(x => x.GetInterfaces().Contains(typeof(ICleanupAfterEveryContextInAssembly))) + .Select(x => (ICleanupAfterEveryContextInAssembly) Activator.CreateInstance(x)); } public IEnumerable FindSpecificationSupplementsIn(Assembly assembly) { return assembly.GetExportedTypes() - .Where(x => x.GetInterfaces().Contains(typeof(ISupplementSpecificationResults))) - .Select(x => (ISupplementSpecificationResults)Activator.CreateInstance(x)); + .Where(x => x.GetInterfaces().Contains(typeof(ISupplementSpecificationResults))) + .Select(x => (ISupplementSpecificationResults) Activator.CreateInstance(x)); } public IEnumerable FindAssemblyContextsIn(Assembly assembly) { return assembly.GetExportedTypes() - .Where(x => x.GetInterfaces().Contains(typeof(IAssemblyContext))) - .Select(x => (IAssemblyContext)Activator.CreateInstance(x)); + .Where(x => x.GetTypeInfo().IsClass && !x.GetTypeInfo().IsAbstract && x.GetInterfaces().Contains(typeof(IAssemblyContext))) + .Select(x => (IAssemblyContext) Activator.CreateInstance(x)); } Context CreateContextFrom(Type type) @@ -77,30 +113,59 @@ static bool HasSpecificationMembers(Type type) static IEnumerable EnumerateContextsIn(Assembly assembly) { return assembly - .GetTypes() - .Where(IsContext) - .OrderBy(t => t.Namespace); + .GetTypes() + .Where(IsContext); } + } - public Context FindContexts(Type type) + public static class FilteringExtensions + { + public static IEnumerable FilterBy(this IEnumerable types, RunOptions options) { - if (IsContext(type)) + if (options == null) { - return CreateContextFrom(type); + return types; } - return null; - } + var filteredTypes = types; - public Context FindContexts(FieldInfo info) - { - Type type = info.DeclaringType; - if (IsContext(type)) + var restrictToTypes = new HashSet(options.Filters, StringComparer.OrdinalIgnoreCase); + + if (restrictToTypes.Any()) + { + filteredTypes = filteredTypes.Where(x => restrictToTypes.Contains(x.FullName)); + } + + var includeTags = new HashSet(options.IncludeTags.Select(tag => new Tag(tag))); + var excludeTags = new HashSet(options.ExcludeTags.Select(tag => new Tag(tag))); + + if (includeTags.Any() || excludeTags.Any()) { - return CreateContextFrom(type, info); + var extractor = new AttributeTagExtractor(); + + var filteredTypesWithTags = filteredTypes.Select(type => new TypeWithTag {Type = type, Tags = extractor.ExtractTags(type)}); + + if (includeTags.Any()) + { + filteredTypesWithTags = filteredTypesWithTags.Where(x => x.Tags.Intersect(includeTags).Any()); + } + + if (excludeTags.Any()) + { + filteredTypesWithTags = filteredTypesWithTags.Where(x => !x.Tags.Intersect(excludeTags).Any()); + } + + filteredTypes = filteredTypesWithTags.Select(x => x.Type); } - return null; + return filteredTypes; + } + + private class TypeWithTag + { + public Type Type; + + public IEnumerable Tags; } } } diff --git a/src/Machine.Specifications/Runner/Impl/AssemblyRunner.cs b/src/Machine.Specifications/Runner/Impl/AssemblyRunner.cs index e37001372..da9e9a5bc 100644 --- a/src/Machine.Specifications/Runner/Impl/AssemblyRunner.cs +++ b/src/Machine.Specifications/Runner/Impl/AssemblyRunner.cs @@ -44,18 +44,17 @@ public void Run(Assembly assembly, IEnumerable contexts) try { - hasExecutableSpecifications = contexts.Any(x => x.HasExecutableSpecifications); - var globalCleanups = _explorer.FindAssemblyWideContextCleanupsIn(assembly).ToList(); var specificationSupplements = _explorer.FindSpecificationSupplementsIn(assembly).ToList(); - if (hasExecutableSpecifications) - { - _assemblyStart(assembly); - } - foreach (var context in contexts) { + if (!hasExecutableSpecifications) + { + _assemblyStart(assembly); + hasExecutableSpecifications = true; + } + RunContext(context, globalCleanups, specificationSupplements); } } @@ -129,4 +128,4 @@ void RunContext(Context context, runner.Run(context, _listener, _options, globalCleanups, supplements); } } -} \ No newline at end of file +} diff --git a/src/Machine.Specifications/Runner/Impl/DefaultRunner.cs b/src/Machine.Specifications/Runner/Impl/DefaultRunner.cs index e722297b9..2de013733 100644 --- a/src/Machine.Specifications/Runner/Impl/DefaultRunner.cs +++ b/src/Machine.Specifications/Runner/Impl/DefaultRunner.cs @@ -66,7 +66,7 @@ public DefaultRunner(ISpecificationRunListener listener, RunOptions options, boo public void RunAssembly(Assembly assembly) { - var contexts = _explorer.FindContextsIn(assembly); + var contexts = _explorer.FindContextsIn(assembly, _options); var map = CreateMap(assembly, contexts); StartRun(map); @@ -76,14 +76,14 @@ public void RunAssemblies(IEnumerable assemblies) { var map = new Dictionary>(); - assemblies.Each(assembly => map.Add(assembly, _explorer.FindContextsIn(assembly))); + assemblies.Each(assembly => map.Add(assembly, _explorer.FindContextsIn(assembly, _options))); StartRun(map); } public void RunNamespace(Assembly assembly, string targetNamespace) { - var contexts = _explorer.FindContextsIn(assembly, targetNamespace); + var contexts = _explorer.FindContextsIn(assembly, targetNamespace, _options); StartRun(CreateMap(assembly, contexts)); } @@ -102,7 +102,7 @@ public void RunMember(Assembly assembly, MemberInfo member) public void RunType(Assembly assembly, Type type, IEnumerable specs) { - Context context = _explorer.FindContexts(type); + Context context = _explorer.FindContexts(type, _options); IEnumerable specsToRun = context.Specifications.Where(s => specs.Contains(s.FieldInfo.Name)); context.Filter(specsToRun); @@ -112,15 +112,15 @@ public void RunType(Assembly assembly, Type type, IEnumerable specs) void RunField(MemberInfo member, Assembly assembly) { var fieldInfo = (FieldInfo)member; - var context = _explorer.FindContexts(fieldInfo); + var context = _explorer.FindContexts(fieldInfo, _options); StartRun(CreateMap(assembly, new[] { context })); } void RunClass(MemberInfo member, Assembly assembly) { - Type type = member.AsType(); - var context = _explorer.FindContexts(type); + var type = member.AsType(); + var context = _explorer.FindContexts(type, _options); if (context == null) { @@ -144,12 +144,8 @@ void StartRun(IDictionary> contextMap) _runStart.Invoke(); } - foreach (var pair in contextMap) + foreach (var (assembly, contexts) in contextMap) { - var assembly = pair.Key; - // TODO: move this filtering to a more sensible place - var contexts = pair.Value.FilteredBy(_options); - _assemblyRunner.Run(assembly, contexts); } @@ -234,37 +230,4 @@ public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink) #endif } - - public static class TagFilteringExtensions - { - public static IEnumerable FilteredBy(this IEnumerable contexts, RunOptions options) - { - if (options == null) - throw new ArgumentNullException("options"); - - var results = contexts; - - if (options.Filters.Any()) - { - var includeFilters = options.Filters; - - results = results.Where(x => includeFilters.Any(filter => StringComparer.OrdinalIgnoreCase.Equals(filter, x.Type.FullName))); - } - - if (options.IncludeTags.Any()) - { - var tags = options.IncludeTags.Select(tag => new Tag(tag)); - - results = results.Where(x => x.Tags.Intersect(tags).Any()); - } - - if (options.ExcludeTags.Any()) - { - var tags = options.ExcludeTags.Select(tag => new Tag(tag)); - results = results.Where(x => !x.Tags.Intersect(tags).Any()); - } - - return results; - } - } } diff --git a/src/Machine.Specifications/Utility/KeyValuePairExtensions.cs b/src/Machine.Specifications/Utility/KeyValuePairExtensions.cs new file mode 100644 index 000000000..f13929202 --- /dev/null +++ b/src/Machine.Specifications/Utility/KeyValuePairExtensions.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Machine.Specifications.Utility +{ + internal static class KeyValuePairExtensions + { + public static void Deconstruct( + this KeyValuePair kvp, + out TKey key, + out TValue value) + { + key = kvp.Key; + value = kvp.Value; + } + } +}