Skip to content

Commit 690fe0b

Browse files
Performance Improvement - Perform test filtering earlier in process
1 parent 8d930f8 commit 690fe0b

File tree

4 files changed

+120
-77
lines changed

4 files changed

+120
-77
lines changed

src/Machine.Specifications/Explorers/AssemblyExplorer.cs

Lines changed: 89 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Reflection;
5-
65
using Machine.Specifications.Factories;
76
using Machine.Specifications.Model;
7+
using Machine.Specifications.Runner;
88
using Machine.Specifications.Sdk;
99
using Machine.Specifications.Utility;
1010

@@ -19,37 +19,73 @@ public AssemblyExplorer()
1919
_contextFactory = new ContextFactory();
2020
}
2121

22+
public Context FindContexts(Type type, RunOptions options = null)
23+
{
24+
var types = new[] {type};
25+
26+
return types
27+
.Where(IsContext)
28+
.FilterBy(options)
29+
.Select(CreateContextFrom)
30+
.FirstOrDefault();
31+
}
32+
33+
public Context FindContexts(FieldInfo info, RunOptions options = null)
34+
{
35+
var types = new[] {info.DeclaringType};
36+
37+
return types
38+
.Where(IsContext)
39+
.FilterBy(options)
40+
.Select(t => CreateContextFrom(t, info))
41+
.FirstOrDefault();
42+
}
43+
2244
public IEnumerable<Context> FindContextsIn(Assembly assembly)
2345
{
24-
return EnumerateContextsIn(assembly).Select(CreateContextFrom);
46+
return FindContextsIn(assembly, options: null);
47+
}
48+
49+
public IEnumerable<Context> FindContextsIn(Assembly assembly, RunOptions options)
50+
{
51+
return EnumerateContextsIn(assembly)
52+
.FilterBy(options)
53+
.OrderBy(t => t.Namespace)
54+
.Select(CreateContextFrom);
2555
}
2656

2757
public IEnumerable<Context> FindContextsIn(Assembly assembly, string targetNamespace)
58+
{
59+
return FindContextsIn(assembly, targetNamespace, options: null);
60+
}
61+
62+
public IEnumerable<Context> FindContextsIn(Assembly assembly, string targetNamespace, RunOptions options)
2863
{
2964
return EnumerateContextsIn(assembly)
30-
.Where(x => x.Namespace == targetNamespace)
31-
.Select(CreateContextFrom);
65+
.Where(x => x.Namespace == targetNamespace)
66+
.FilterBy(options)
67+
.Select(CreateContextFrom);
3268
}
3369

3470
public IEnumerable<ICleanupAfterEveryContextInAssembly> FindAssemblyWideContextCleanupsIn(Assembly assembly)
3571
{
3672
return assembly.GetExportedTypes()
37-
.Where(x => x.GetInterfaces().Contains(typeof(ICleanupAfterEveryContextInAssembly)))
38-
.Select(x => (ICleanupAfterEveryContextInAssembly)Activator.CreateInstance(x));
73+
.Where(x => x.GetInterfaces().Contains(typeof(ICleanupAfterEveryContextInAssembly)))
74+
.Select(x => (ICleanupAfterEveryContextInAssembly) Activator.CreateInstance(x));
3975
}
4076

4177
public IEnumerable<ISupplementSpecificationResults> FindSpecificationSupplementsIn(Assembly assembly)
4278
{
4379
return assembly.GetExportedTypes()
44-
.Where(x => x.GetInterfaces().Contains(typeof(ISupplementSpecificationResults)))
45-
.Select(x => (ISupplementSpecificationResults)Activator.CreateInstance(x));
80+
.Where(x => x.GetInterfaces().Contains(typeof(ISupplementSpecificationResults)))
81+
.Select(x => (ISupplementSpecificationResults) Activator.CreateInstance(x));
4682
}
4783

4884
public IEnumerable<IAssemblyContext> FindAssemblyContextsIn(Assembly assembly)
4985
{
5086
return assembly.GetExportedTypes()
51-
.Where(x => x.GetInterfaces().Contains(typeof(IAssemblyContext)))
52-
.Select(x => (IAssemblyContext)Activator.CreateInstance(x));
87+
.Where(x => x.GetTypeInfo().IsClass && !x.GetTypeInfo().IsAbstract && x.GetInterfaces().Contains(typeof(IAssemblyContext)))
88+
.Select(x => (IAssemblyContext) Activator.CreateInstance(x));
5389
}
5490

5591
Context CreateContextFrom(Type type)
@@ -77,30 +113,59 @@ static bool HasSpecificationMembers(Type type)
77113
static IEnumerable<Type> EnumerateContextsIn(Assembly assembly)
78114
{
79115
return assembly
80-
.GetTypes()
81-
.Where(IsContext)
82-
.OrderBy(t => t.Namespace);
116+
.GetTypes()
117+
.Where(IsContext);
83118
}
119+
}
84120

85-
public Context FindContexts(Type type)
121+
public static class FilteringExtensions
122+
{
123+
public static IEnumerable<Type> FilterBy(this IEnumerable<Type> types, RunOptions options)
86124
{
87-
if (IsContext(type))
125+
if (options == null)
88126
{
89-
return CreateContextFrom(type);
127+
return types;
90128
}
91129

92-
return null;
93-
}
130+
var filteredTypes = types;
94131

95-
public Context FindContexts(FieldInfo info)
96-
{
97-
Type type = info.DeclaringType;
98-
if (IsContext(type))
132+
var restrictToTypes = new HashSet<string>(options.Filters, StringComparer.OrdinalIgnoreCase);
133+
134+
if (restrictToTypes.Any())
135+
{
136+
filteredTypes = filteredTypes.Where(x => restrictToTypes.Contains(x.FullName));
137+
}
138+
139+
var includeTags = new HashSet<Tag>(options.IncludeTags.Select(tag => new Tag(tag)));
140+
var excludeTags = new HashSet<Tag>(options.ExcludeTags.Select(tag => new Tag(tag)));
141+
142+
if (includeTags.Any() || excludeTags.Any())
99143
{
100-
return CreateContextFrom(type, info);
144+
var extractor = new AttributeTagExtractor();
145+
146+
var filteredTypesWithTags = filteredTypes.Select(type => new TypeWithTag {Type = type, Tags = extractor.ExtractTags(type)});
147+
148+
if (includeTags.Any())
149+
{
150+
filteredTypesWithTags = filteredTypesWithTags.Where(x => x.Tags.Intersect(includeTags).Any());
151+
}
152+
153+
if (excludeTags.Any())
154+
{
155+
filteredTypesWithTags = filteredTypesWithTags.Where(x => !x.Tags.Intersect(excludeTags).Any());
156+
}
157+
158+
filteredTypes = filteredTypesWithTags.Select(x => x.Type);
101159
}
102160

103-
return null;
161+
return filteredTypes;
162+
}
163+
164+
private class TypeWithTag
165+
{
166+
public Type Type;
167+
168+
public IEnumerable<Tag> Tags;
104169
}
105170
}
106171
}

src/Machine.Specifications/Runner/Impl/AssemblyRunner.cs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,17 @@ public void Run(Assembly assembly, IEnumerable<Context> contexts)
4444

4545
try
4646
{
47-
hasExecutableSpecifications = contexts.Any(x => x.HasExecutableSpecifications);
48-
4947
var globalCleanups = _explorer.FindAssemblyWideContextCleanupsIn(assembly).ToList();
5048
var specificationSupplements = _explorer.FindSpecificationSupplementsIn(assembly).ToList();
5149

52-
if (hasExecutableSpecifications)
53-
{
54-
_assemblyStart(assembly);
55-
}
56-
5750
foreach (var context in contexts)
5851
{
52+
if (!hasExecutableSpecifications)
53+
{
54+
_assemblyStart(assembly);
55+
hasExecutableSpecifications = true;
56+
}
57+
5958
RunContext(context, globalCleanups, specificationSupplements);
6059
}
6160
}
@@ -129,4 +128,4 @@ void RunContext(Context context,
129128
runner.Run(context, _listener, _options, globalCleanups, supplements);
130129
}
131130
}
132-
}
131+
}

src/Machine.Specifications/Runner/Impl/DefaultRunner.cs

Lines changed: 8 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public DefaultRunner(ISpecificationRunListener listener, RunOptions options, boo
6666

6767
public void RunAssembly(Assembly assembly)
6868
{
69-
var contexts = _explorer.FindContextsIn(assembly);
69+
var contexts = _explorer.FindContextsIn(assembly, _options);
7070
var map = CreateMap(assembly, contexts);
7171

7272
StartRun(map);
@@ -76,14 +76,14 @@ public void RunAssemblies(IEnumerable<Assembly> assemblies)
7676
{
7777
var map = new Dictionary<Assembly, IEnumerable<Context>>();
7878

79-
assemblies.Each(assembly => map.Add(assembly, _explorer.FindContextsIn(assembly)));
79+
assemblies.Each(assembly => map.Add(assembly, _explorer.FindContextsIn(assembly, _options)));
8080

8181
StartRun(map);
8282
}
8383

8484
public void RunNamespace(Assembly assembly, string targetNamespace)
8585
{
86-
var contexts = _explorer.FindContextsIn(assembly, targetNamespace);
86+
var contexts = _explorer.FindContextsIn(assembly, targetNamespace, _options);
8787

8888
StartRun(CreateMap(assembly, contexts));
8989
}
@@ -102,7 +102,7 @@ public void RunMember(Assembly assembly, MemberInfo member)
102102

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

@@ -112,15 +112,15 @@ public void RunType(Assembly assembly, Type type, IEnumerable<string> specs)
112112
void RunField(MemberInfo member, Assembly assembly)
113113
{
114114
var fieldInfo = (FieldInfo)member;
115-
var context = _explorer.FindContexts(fieldInfo);
115+
var context = _explorer.FindContexts(fieldInfo, _options);
116116

117117
StartRun(CreateMap(assembly, new[] { context }));
118118
}
119119

120120
void RunClass(MemberInfo member, Assembly assembly)
121121
{
122-
Type type = member.AsType();
123-
var context = _explorer.FindContexts(type);
122+
var type = member.AsType();
123+
var context = _explorer.FindContexts(type, _options);
124124

125125
if (context == null)
126126
{
@@ -144,12 +144,8 @@ void StartRun(IDictionary<Assembly, IEnumerable<Context>> contextMap)
144144
_runStart.Invoke();
145145
}
146146

147-
foreach (var pair in contextMap)
147+
foreach (var (assembly, contexts) in contextMap)
148148
{
149-
var assembly = pair.Key;
150-
// TODO: move this filtering to a more sensible place
151-
var contexts = pair.Value.FilteredBy(_options);
152-
153149
_assemblyRunner.Run(assembly, contexts);
154150
}
155151

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

235231
#endif
236232
}
237-
238-
public static class TagFilteringExtensions
239-
{
240-
public static IEnumerable<Context> FilteredBy(this IEnumerable<Context> contexts, RunOptions options)
241-
{
242-
if (options == null)
243-
throw new ArgumentNullException("options");
244-
245-
var results = contexts;
246-
247-
if (options.Filters.Any())
248-
{
249-
var includeFilters = options.Filters;
250-
251-
results = results.Where(x => includeFilters.Any(filter => StringComparer.OrdinalIgnoreCase.Equals(filter, x.Type.FullName)));
252-
}
253-
254-
if (options.IncludeTags.Any())
255-
{
256-
var tags = options.IncludeTags.Select(tag => new Tag(tag));
257-
258-
results = results.Where(x => x.Tags.Intersect(tags).Any());
259-
}
260-
261-
if (options.ExcludeTags.Any())
262-
{
263-
var tags = options.ExcludeTags.Select(tag => new Tag(tag));
264-
results = results.Where(x => !x.Tags.Intersect(tags).Any());
265-
}
266-
267-
return results;
268-
}
269-
}
270233
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System.Collections.Generic;
2+
3+
namespace Machine.Specifications.Utility
4+
{
5+
internal static class KeyValuePairExtensions
6+
{
7+
public static void Deconstruct<TKey, TValue>(
8+
this KeyValuePair<TKey, TValue> kvp,
9+
out TKey key,
10+
out TValue value)
11+
{
12+
key = kvp.Key;
13+
value = kvp.Value;
14+
}
15+
}
16+
}

0 commit comments

Comments
 (0)