Skip to content

Commit babecb5

Browse files
authored
Add TestCaseFilter support to VSTest Adapter (#95)
* Spec Filtering * MSpecTestAdapter() improvements * VisualStudioTestIdentifier[] to IEnumerable<VisualStudioTestIdentifier>
1 parent d05f487 commit babecb5

14 files changed

+289
-159
lines changed

src/Machine.Specifications.Runner.VisualStudio.Specs/Configuration/When_adapter_runs_tests.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
using Machine.VSTestAdapter.Configuration;
55
using Machine.VSTestAdapter.Discovery;
66
using Machine.VSTestAdapter.Execution;
7+
using Machine.VSTestAdapter.Helpers;
8+
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
79
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
810

911
namespace Machine.VSTestAdapter.Specs.Configuration
@@ -20,7 +22,8 @@ public class When_adapter_runs_tests : WithFakes
2022
</MSpec>
2123
</RunSettings>";
2224

23-
static MSpecTestAdapter adapter;
25+
static MSpecTestAdapterExecutor adapter;
26+
static MSpecTestAdapterDiscoverer discoverer;
2427

2528
Establish establish = () =>
2629
{
@@ -32,24 +35,27 @@ public class When_adapter_runs_tests : WithFakes
3235
.WhenToldTo(context => context.RunSettings)
3336
.Return(The<IRunSettings>());
3437

35-
adapter = new MSpecTestAdapter(An<ISpecificationDiscoverer>(), The<ISpecificationExecutor>());
38+
discoverer = new MSpecTestAdapterDiscoverer(An<ISpecificationDiscoverer>());
39+
adapter = new MSpecTestAdapterExecutor(The<ISpecificationExecutor>(), discoverer, An<ISpecificationFilterProvider>());
3640
};
3741

38-
Because of = () =>
39-
adapter.RunTests(new[] { "dll" }, The<IRunContext>(), An<IFrameworkHandle>());
42+
Because of = () => adapter.RunTests(new[] { new TestCase("a", MSpecTestAdapter.Uri, "dll") }, The<IRunContext>(), An<IFrameworkHandle>());
4043

4144
It should_pick_up_DisableFullTestNameInIDE = () =>
4245
The<ISpecificationExecutor>()
43-
.WasToldTo(d => d.RunAssembly("dll",
46+
.WasToldTo(d => d.RunAssemblySpecifications("dll",
47+
Param<VisualStudioTestIdentifier[]>.IsAnything,
4448
Param<Settings>.Matches(s => s.DisableFullTestNameInIDE),
4549
Param<Uri>.IsAnything,
4650
Param<IFrameworkHandle>.IsAnything));
4751

4852
It should_pick_up_DisableFullTestNameInOutput = () =>
4953
The<ISpecificationExecutor>()
50-
.WasToldTo(d => d.RunAssembly("dll",
54+
.WasToldTo(d => d.RunAssemblySpecifications("dll",
55+
Param<VisualStudioTestIdentifier[]>.IsAnything,
5156
Param<Settings>.Matches(s => s.DisableFullTestNameInOutput),
5257
Param<Uri>.IsAnything,
5358
Param<IFrameworkHandle>.IsAnything));
59+
5460
}
5561
}

src/Machine.Specifications.Runner.VisualStudio.Specs/Configuration/When_parsing_configuration_and_mspec_section_is_missing.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
namespace Machine.VSTestAdapter.Specs.Configuration
55
{
6+
[Tags("Tag1","Tag2")]
67
[Subject(typeof(Settings), "Configuration")]
78
public class When_parsing_configuration_and_mspec_section_is_missing
89
{

src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/With_AssemblyExecutionSetup.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,21 @@ namespace Machine.VSTestAdapter.Specs.Execution
1111
{
1212
public abstract class With_AssemblyExecutionSetup : WithFakes
1313
{
14-
static ISpecificationExecutor Executor;
14+
static MSpecTestAdapter Executor;
1515
static CompileContext compiler;
1616
static Assembly assembly;
1717

1818
Establish context = () =>
1919
{
2020
compiler = new CompileContext();
21-
Executor = new SpecificationExecutor();
21+
Executor = new MSpecTestAdapter();
2222

2323
var assemblyPath = compiler.Compile(SampleFixture.Code);
2424
assembly = Assembly.LoadFile(assemblyPath);
2525
};
2626

2727
Because of = () =>
28-
Executor.RunAssembly(assembly.Location, An<Settings>(), new Uri("bla://executor"), The<IFrameworkHandle>());
28+
Executor.RunTests(new[] { assembly.Location }, An<IRunContext>(), The<IFrameworkHandle>());
2929

3030
Cleanup after = () =>
3131
compiler.Dispose();

src/Machine.Specifications.Runner.VisualStudio.Specs/When_there_is_an_unhandled_error_during_test_discovery.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class When_there_is_an_unhandled_error_during_test_discovery : WithFakes
1919
throw new InvalidOperationException();
2020
});
2121

22-
Adapter = new MSpecTestAdapter(The<ISpecificationDiscoverer>(), An<ISpecificationExecutor>());
22+
Adapter = new MSpecTestAdapter(The<ISpecificationDiscoverer>(), An<ISpecificationExecutor>(), An<ISpecificationFilterProvider>());
2323
};
2424

2525
Because of = () => {

src/Machine.Specifications.Runner.VisualStudio.Specs/When_there_is_an_unhandled_error_during_test_execution.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,35 @@
11
using System;
2+
using System.Collections.Generic;
23
using Machine.Fakes;
34
using Machine.Specifications;
45
using Machine.VSTestAdapter.Configuration;
56
using Machine.VSTestAdapter.Discovery;
67
using Machine.VSTestAdapter.Execution;
8+
using Machine.VSTestAdapter.Helpers;
9+
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
710
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
811
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
912

1013
namespace Machine.VSTestAdapter.Specs
1114
{
1215
public class When_there_is_an_unhandled_error_during_test_execution : WithFakes
1316
{
14-
static MSpecTestAdapter Adapter;
17+
static MSpecTestAdapterExecutor Adapter;
1518

1619
Establish context = () =>
1720
{
1821
The<ISpecificationExecutor>()
19-
.WhenToldTo(d => d.RunAssembly(Param<string>.IsAnything, Param<Settings>.IsNotNull,
22+
.WhenToldTo(d => d.RunAssemblySpecifications(Param<string>.IsAnything, Param<VisualStudioTestIdentifier[]>.IsAnything, Param<Settings>.IsNotNull,
2023
Param<Uri>.IsAnything, Param<IFrameworkHandle>.IsAnything))
2124
.Throw(new InvalidOperationException());
2225

23-
Adapter = new MSpecTestAdapter(An<ISpecificationDiscoverer>(), The<ISpecificationExecutor>());
26+
var adapterDiscoverer = new MSpecTestAdapterDiscoverer(An<ISpecificationDiscoverer>());
27+
Adapter = new MSpecTestAdapterExecutor(The<ISpecificationExecutor>(), adapterDiscoverer, An<ISpecificationFilterProvider>());
2428
};
2529

26-
Because of = () => { Adapter.RunTests(new[] {"bla"}, An<IRunContext>(), The<IFrameworkHandle>()); };
27-
30+
Because of = () =>
31+
Adapter.RunTests(new[] {new TestCase("a", MSpecTestAdapter.Uri, "dll"), }, An<IRunContext>(), The<IFrameworkHandle>());
32+
2833
It should_send_an_error_notification_to_visual_studio = () =>
2934
{
3035
The<IFrameworkHandle>()

src/Machine.Specifications.Runner.VisualStudio/Configuration/Settings.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,17 @@ public static Settings Parse(string xml)
1818
Settings config = new Settings();
1919

2020
XElement mspecConfig = null;
21-
try {
22-
mspecConfig = XDocument.Parse(xml).XPathSelectElement("RunSettings/MSpec");
23-
} catch { }
21+
try
22+
{
23+
if(!string.IsNullOrEmpty(xml))
24+
{
25+
mspecConfig = XDocument.Parse(xml).XPathSelectElement("RunSettings/MSpec");
26+
}
27+
}
28+
catch
29+
{
30+
// ignored
31+
}
2432

2533
if (mspecConfig == null)
2634
return config;
Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
2-
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
1+
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
32
using System;
43
using System.Collections.Generic;
54
using Machine.VSTestAdapter.Helpers;
@@ -9,8 +8,6 @@ namespace Machine.VSTestAdapter.Execution
98
{
109
public interface ISpecificationExecutor
1110
{
12-
void RunAssembly(string assemblyPath, Settings settings, Uri adaptorUri, IFrameworkHandle frameworkHandle);
13-
14-
void RunAssemblySpecifications(string assemblyPath, IEnumerable<VisualStudioTestIdentifier> specifications, Settings settings, Uri adaptorUri, IFrameworkHandle frameworkHandle);
11+
void RunAssemblySpecifications(string assemblyPath, IEnumerable<VisualStudioTestIdentifier> specifications, Settings settings, Uri adapterUri, IFrameworkHandle frameworkHandle);
1512
}
16-
}
13+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.Collections.Generic;
2+
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
3+
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
4+
5+
namespace Machine.VSTestAdapter.Execution
6+
{
7+
public interface ISpecificationFilterProvider
8+
{
9+
IEnumerable<TestCase> FilteredTests(IEnumerable<TestCase> testCases, IRunContext runContext, IFrameworkHandle frameworkHandle);
10+
}
11+
}

src/Machine.Specifications.Runner.VisualStudio/Execution/SpecificationExecutor.cs

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,10 @@ namespace Machine.VSTestAdapter.Execution
1111
{
1212
public class SpecificationExecutor : ISpecificationExecutor
1313
{
14-
public void RunAssembly(string source, Settings settings, Uri executorUri, IFrameworkHandle frameworkHandle)
15-
{
16-
source = Path.GetFullPath(source);
17-
18-
#if !NETSTANDARD
19-
using (var scope = new IsolatedAppDomainExecutionScope<TestExecutor>(source)) {
20-
TestExecutor executor = scope.CreateInstance();
21-
#else
22-
TestExecutor executor = new TestExecutor();
23-
#endif
24-
25-
VSProxyAssemblySpecificationRunListener listener = new VSProxyAssemblySpecificationRunListener(source, frameworkHandle, executorUri, settings);
26-
executor.RunAllTestsInAssembly(source, listener);
27-
#if !NETSTANDARD
28-
}
29-
#endif
30-
}
31-
3214
public void RunAssemblySpecifications(string assemblyPath,
3315
IEnumerable<VisualStudioTestIdentifier> specifications,
3416
Settings settings,
35-
Uri executorUri,
17+
Uri adapterUri,
3618
IFrameworkHandle frameworkHandle)
3719
{
3820
assemblyPath = Path.GetFullPath(assemblyPath);
@@ -43,12 +25,12 @@ public void RunAssemblySpecifications(string assemblyPath,
4325
#else
4426
TestExecutor executor = new TestExecutor();
4527
#endif
46-
VSProxyAssemblySpecificationRunListener listener = new VSProxyAssemblySpecificationRunListener(assemblyPath, frameworkHandle, executorUri, settings);
28+
VSProxyAssemblySpecificationRunListener listener = new VSProxyAssemblySpecificationRunListener(assemblyPath, frameworkHandle, adapterUri, settings);
4729

4830
executor.RunTestsInAssembly(assemblyPath, specifications, listener);
4931
#if !NETSTANDARD
5032
}
5133
#endif
5234
}
5335
}
54-
}
36+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Machine.Specifications.Model;
5+
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
6+
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
7+
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
8+
9+
namespace Machine.VSTestAdapter.Execution
10+
{
11+
public class SpecificationFilterProvider : ISpecificationFilterProvider
12+
{
13+
static readonly TestProperty TagProperty = TestProperty.Register(nameof(Tag), nameof(Tag), typeof(string), typeof(TestCase));
14+
static readonly TestProperty SubjectProperty = TestProperty.Register(nameof(Subject), nameof(Subject), typeof(string), typeof(TestCase));
15+
16+
readonly Dictionary<string, TestProperty> testCaseProperties = new Dictionary<string, TestProperty>(StringComparer.OrdinalIgnoreCase)
17+
{
18+
[TestCaseProperties.FullyQualifiedName.Id] = TestCaseProperties.FullyQualifiedName,
19+
[TestCaseProperties.DisplayName.Id] = TestCaseProperties.DisplayName
20+
};
21+
22+
readonly Dictionary<string, TestProperty> traitProperties = new Dictionary<string, TestProperty>(StringComparer.OrdinalIgnoreCase)
23+
{
24+
[TagProperty.Id] = TagProperty,
25+
[SubjectProperty.Id] = SubjectProperty
26+
};
27+
28+
readonly string[] supportedProperties;
29+
30+
public SpecificationFilterProvider()
31+
{
32+
supportedProperties = testCaseProperties.Keys
33+
.Concat(traitProperties.Keys)
34+
.ToArray();
35+
}
36+
37+
38+
public IEnumerable<TestCase> FilteredTests(IEnumerable<TestCase> testCases, IRunContext runContext, IFrameworkHandle handle)
39+
{
40+
var filterExpression = runContext.GetTestCaseFilter(supportedProperties, propertyName =>
41+
{
42+
if (testCaseProperties.TryGetValue(propertyName, out var testProperty))
43+
{
44+
return testProperty;
45+
}
46+
if (traitProperties.TryGetValue(propertyName, out var traitProperty))
47+
{
48+
return traitProperty;
49+
}
50+
return null;
51+
});
52+
53+
if (filterExpression == null)
54+
{
55+
return testCases;
56+
}
57+
58+
handle?.SendMessage(TestMessageLevel.Informational, $"Machine Specifications Visual Studio Test Adapter - Filter property set '{filterExpression.TestCaseFilterValue}'");
59+
60+
var filteredTests = testCases
61+
.Where(testCase => filterExpression.MatchTestCase(testCase, propertyName =>
62+
{
63+
var value = GetPropertyValue(propertyName, testCase);
64+
return value;
65+
}));
66+
67+
return filteredTests;
68+
}
69+
70+
object GetPropertyValue(string propertyName, TestObject testCase)
71+
{
72+
if (testCaseProperties.TryGetValue(propertyName, out var testProperty))
73+
{
74+
if (testCase.Properties.Contains(testProperty))
75+
{
76+
return testCase.GetPropertyValue(testProperty);
77+
}
78+
}
79+
80+
if (traitProperties.TryGetValue(propertyName, out var traitProperty))
81+
{
82+
var val = TraitContains(testCase, traitProperty.Id);
83+
84+
if (val.Length == 1)
85+
{
86+
return val[0];
87+
}
88+
89+
if (val.Length > 1)
90+
{
91+
return val;
92+
}
93+
}
94+
95+
return null;
96+
}
97+
98+
static string[] TraitContains(TestObject testCase, string traitName)
99+
{
100+
return testCase?.Traits?
101+
.Where(x => x.Name == traitName)
102+
.Select(x => x.Value)
103+
.ToArray();
104+
}
105+
}
106+
}

0 commit comments

Comments
 (0)