Skip to content

Commit

Permalink
Performance Improvement - Perform test filtering earlier in process
Browse files Browse the repository at this point in the history
  • Loading branch information
robertcoltheart committed May 5, 2021
1 parent 8d930f8 commit 690fe0b
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 77 deletions.
113 changes: 89 additions & 24 deletions src/Machine.Specifications/Explorers/AssemblyExplorer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<Context> FindContextsIn(Assembly assembly)
{
return EnumerateContextsIn(assembly).Select(CreateContextFrom);
return FindContextsIn(assembly, options: null);
}

public IEnumerable<Context> FindContextsIn(Assembly assembly, RunOptions options)
{
return EnumerateContextsIn(assembly)
.FilterBy(options)
.OrderBy(t => t.Namespace)
.Select(CreateContextFrom);
}

public IEnumerable<Context> FindContextsIn(Assembly assembly, string targetNamespace)
{
return FindContextsIn(assembly, targetNamespace, options: null);
}

public IEnumerable<Context> 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<ICleanupAfterEveryContextInAssembly> 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<ISupplementSpecificationResults> 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<IAssemblyContext> 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)
Expand Down Expand Up @@ -77,30 +113,59 @@ static bool HasSpecificationMembers(Type type)
static IEnumerable<Type> 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<Type> FilterBy(this IEnumerable<Type> 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<string>(options.Filters, StringComparer.OrdinalIgnoreCase);

if (restrictToTypes.Any())
{
filteredTypes = filteredTypes.Where(x => restrictToTypes.Contains(x.FullName));
}

var includeTags = new HashSet<Tag>(options.IncludeTags.Select(tag => new Tag(tag)));
var excludeTags = new HashSet<Tag>(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<Tag> Tags;
}
}
}
15 changes: 7 additions & 8 deletions src/Machine.Specifications/Runner/Impl/AssemblyRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,17 @@ public void Run(Assembly assembly, IEnumerable<Context> 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);
}
}
Expand Down Expand Up @@ -129,4 +128,4 @@ public void EndExplicitRunScope(Assembly assembly)
runner.Run(context, _listener, _options, globalCleanups, supplements);
}
}
}
}
53 changes: 8 additions & 45 deletions src/Machine.Specifications/Runner/Impl/DefaultRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -76,14 +76,14 @@ public void RunAssemblies(IEnumerable<Assembly> assemblies)
{
var map = new Dictionary<Assembly, IEnumerable<Context>>();

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));
}
Expand All @@ -102,7 +102,7 @@ public void RunMember(Assembly assembly, MemberInfo member)

public void RunType(Assembly assembly, Type type, IEnumerable<string> specs)
{
Context context = _explorer.FindContexts(type);
Context context = _explorer.FindContexts(type, _options);
IEnumerable<Specification> specsToRun = context.Specifications.Where(s => specs.Contains(s.FieldInfo.Name));
context.Filter(specsToRun);

Expand All @@ -112,15 +112,15 @@ public void RunType(Assembly assembly, Type type, IEnumerable<string> 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)
{
Expand All @@ -144,12 +144,8 @@ void StartRun(IDictionary<Assembly, IEnumerable<Context>> 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);
}

Expand Down Expand Up @@ -234,37 +230,4 @@ public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)

#endif
}

public static class TagFilteringExtensions
{
public static IEnumerable<Context> FilteredBy(this IEnumerable<Context> 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;
}
}
}
16 changes: 16 additions & 0 deletions src/Machine.Specifications/Utility/KeyValuePairExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Collections.Generic;

namespace Machine.Specifications.Utility
{
internal static class KeyValuePairExtensions
{
public static void Deconstruct<TKey, TValue>(
this KeyValuePair<TKey, TValue> kvp,
out TKey key,
out TValue value)
{
key = kvp.Key;
value = kvp.Value;
}
}
}

0 comments on commit 690fe0b

Please sign in to comment.