From b8b67d1056f3c730e4064dce6ad267c063f45df3 Mon Sep 17 00:00:00 2001
From: Robert Coltheart <13191652+robertcoltheart@users.noreply.github.com>
Date: Fri, 20 Dec 2024 21:29:09 +1100
Subject: [PATCH] Consolidate VS runner into mspec repo (#539)
---
.github/workflows/build.yml | 8 +-
.github/workflows/publish.yml | 8 +-
Directory.Build.props | 4 +-
Machine.Specifications.sln | 8 +-
...hine.Specifications.Analyzers.Tests.csproj | 11 +-
.../Machine.Specifications.Analyzers.csproj | 3 +-
...sModifierShouldNotBeUsedCodeFixProvider.cs | 2 +-
.../Machine.Specifications.Core.Specs.csproj | 9 +-
.../Machine.Specifications.Core.csproj | 6 +-
.../Machine.Specifications.Fakes.Specs.csproj | 7 +-
.../Machine.Specifications.Fakes.csproj | 5 +-
.../Machine.Specifications.Fixtures.csproj | 7 +-
...Specifications.Runner.Utility.Specs.csproj | 13 +-
...chine.Specifications.Runner.Utility.csproj | 3 +
.../FixtureCode.cs | 103 +++++++++++
...ations.Runner.VisualStudio.Fixtures.csproj | 21 +++
.../.editorconfig | 37 ++++
.../Discovery/BehaviorsSpecs.cs | 33 ++++
.../Discovery/CustomActAssertSpecs.cs | 21 +++
.../Discovery/DisabledTestNameOffSpecs.cs | 31 ++++
.../Discovery/DiscoverySpecs.cs | 45 +++++
.../Discovery/NestedTypesDiscoverySpecs.cs | 23 +++
.../Discovery/WithDiscoverySetup.cs | 27 +++
.../DiscoveryUnhandledErrorSpecs.cs | 31 ++++
.../WhenSpecificationEndsWithAFail.cs | 53 ++++++
.../WhenSpecificationEndsWithAPass.cs | 52 ++++++
.../RunListener/WhenSpecificationStarts.cs | 38 +++++
.../RunListener/WhenThereIsAnErrorReported.cs | 27 +++
.../Execution/WhenCleaningUpAContext.cs | 31 ++++
.../Execution/WhenRunningANestedSpecPasses.cs | 27 +++
.../WhenRunningASingleBehaviorPasses.cs | 27 +++
.../Execution/WhenRunningASpecThatFails.cs | 27 +++
.../WhenRunningASpecThatIsIgnored.cs | 27 +++
.../WhenRunningASpecThatIsNotImplemented.cs | 29 ++++
.../Execution/WhenRunningASpecThatPasses.cs | 27 +++
.../Execution/WhenRunningASpecThatThrows.cs | 28 +++
...ASpecWithCustomActAssertDelegatesPasses.cs | 27 +++
.../WhenRunningAnAssemblyWithBehaviors.cs | 31 ++++
.../Execution/WithAssemblyExecutionSetup.cs | 24 +++
.../WithMultipleSpecExecutionSetup.cs | 29 ++++
.../Execution/WithSingleSpecExecutionSetup.cs | 29 ++++
.../ExecutionUnhandledErrorSpecs.cs | 38 +++++
...fications.Runner.VisualStudio.Specs.csproj | 20 +++
.../BuiltInSpecificationDiscoverer.cs | 24 +++
.../Discovery/ISpecificationDiscoverer.cs | 9 +
.../Discovery/SpecTestCase.cs | 32 ++++
.../Discovery/TestDiscoverer.cs | 102 +++++++++++
.../AssemblyLocationAwareRunListener.cs | 57 +++++++
.../Execution/IFrameworkLogger.cs | 9 +
.../Execution/ISpecificationExecutor.cs | 12 ++
.../Execution/ISpecificationFilterProvider.cs | 11 ++
.../ProxyAssemblySpecificationRunListener.cs | 160 ++++++++++++++++++
.../Execution/RunStats.cs | 29 ++++
.../SingleBehaviorTestRunListenerWrapper.cs | 82 +++++++++
.../Execution/SpecificationExecutor.cs | 33 ++++
.../Execution/SpecificationFilterProvider.cs | 104 ++++++++++++
.../Execution/TestExecutor.cs | 83 +++++++++
.../Helpers/AssemblyHelper.cs | 25 +++
.../IsolatedAppDomainExecutionScope.cs | 148 ++++++++++++++++
.../Helpers/NamingConversionExtensions.cs | 30 ++++
.../Helpers/SpecTestHelper.cs | 54 ++++++
.../Helpers/VisualStudioTestIdentifier.cs | 58 +++++++
....Specifications.Runner.VisualStudio.csproj | 41 +++++
...e.Specifications.Runner.VisualStudio.props | 10 ++
.../MspecTestDiscoverer.cs | 68 ++++++++
.../MspecTestExecutor.cs | 82 +++++++++
.../MspecTestRunner.cs | 55 ++++++
.../Navigation/INavigationSession.cs | 9 +
.../Navigation/NavigationData.cs | 17 ++
.../Navigation/NavigationSession.cs | 50 ++++++
.../Reflection/AssemblyData.cs | 89 ++++++++++
.../Reflection/CodeReader.cs | 154 +++++++++++++++++
.../Reflection/InstructionData.cs | 65 +++++++
.../Reflection/MethodData.cs | 92 ++++++++++
.../Reflection/SequencePointData.cs | 24 +++
.../Reflection/SymbolReader.cs | 43 +++++
.../Reflection/TypeData.cs | 85 ++++++++++
.../Resources/Machine.png | Bin 0 -> 6064 bytes
...Machine.Specifications.Should.Specs.csproj | 9 +-
.../ShouldBeLikeSpecs.cs | 23 +--
.../Machine.Specifications.Should.csproj | 3 +
.../Internal/PrettyPrintingExtensions.cs | 8 +-
82 files changed, 2992 insertions(+), 54 deletions(-)
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Fixtures/FixtureCode.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Fixtures/Machine.Specifications.Runner.VisualStudio.Fixtures.csproj
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/.editorconfig
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/BehaviorsSpecs.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/CustomActAssertSpecs.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/DisabledTestNameOffSpecs.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/DiscoverySpecs.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/NestedTypesDiscoverySpecs.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/WithDiscoverySetup.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/DiscoveryUnhandledErrorSpecs.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/RunListener/WhenSpecificationEndsWithAFail.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/RunListener/WhenSpecificationEndsWithAPass.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/RunListener/WhenSpecificationStarts.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/RunListener/WhenThereIsAnErrorReported.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenCleaningUpAContext.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningANestedSpecPasses.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASingleBehaviorPasses.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecThatFails.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecThatIsIgnored.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecThatIsNotImplemented.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecThatPasses.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecThatThrows.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecWithCustomActAssertDelegatesPasses.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningAnAssemblyWithBehaviors.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WithAssemblyExecutionSetup.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WithMultipleSpecExecutionSetup.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WithSingleSpecExecutionSetup.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/ExecutionUnhandledErrorSpecs.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio.Specs/Machine.Specifications.Runner.VisualStudio.Specs.csproj
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Discovery/BuiltInSpecificationDiscoverer.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Discovery/ISpecificationDiscoverer.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Discovery/SpecTestCase.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Discovery/TestDiscoverer.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Execution/AssemblyLocationAwareRunListener.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Execution/IFrameworkLogger.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Execution/ISpecificationExecutor.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Execution/ISpecificationFilterProvider.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Execution/ProxyAssemblySpecificationRunListener.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Execution/RunStats.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Execution/SingleBehaviorTestRunListenerWrapper.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Execution/SpecificationExecutor.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Execution/SpecificationFilterProvider.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Execution/TestExecutor.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Helpers/AssemblyHelper.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Helpers/IsolatedAppDomainExecutionScope.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Helpers/NamingConversionExtensions.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Helpers/SpecTestHelper.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Helpers/VisualStudioTestIdentifier.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Machine.Specifications.Runner.VisualStudio.csproj
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Machine.Specifications.Runner.VisualStudio.props
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/MspecTestDiscoverer.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/MspecTestExecutor.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/MspecTestRunner.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Navigation/INavigationSession.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Navigation/NavigationData.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Navigation/NavigationSession.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Reflection/AssemblyData.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Reflection/CodeReader.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Reflection/InstructionData.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Reflection/MethodData.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Reflection/SequencePointData.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Reflection/SymbolReader.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Reflection/TypeData.cs
create mode 100644 src/Machine.Specifications.Runner.VisualStudio/Resources/Machine.png
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d68c7727..31aa4108 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -12,16 +12,16 @@ env:
jobs:
build:
name: build
- runs-on: windows-latest
+ runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Fetch all tags and branches
run: git fetch --prune --unshallow
- name: Build
- run: ./build.ps1
+ run: ./build.sh
- name: Upload artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
path: artifacts/*.nupkg
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 09754584..08301029 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -10,18 +10,18 @@ env:
jobs:
publish:
name: publish
- runs-on: windows-latest
+ runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Fetch all tags and branches
run: git fetch --prune --unshallow
- name: Deploy
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
- run: ./build.ps1 publish
+ run: ./build.sh publish
- name: Upload artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
path: artifacts/*.nupkg
diff --git a/Directory.Build.props b/Directory.Build.props
index 52d199dd..5cb6d1d6 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,7 +1,4 @@
-
- Machine.Specifications
-
Machine Specifications
@@ -17,4 +14,5 @@
+
diff --git a/Machine.Specifications.sln b/Machine.Specifications.sln
index 7dfb5900..690c67c1 100644
--- a/Machine.Specifications.sln
+++ b/Machine.Specifications.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
-VisualStudioVersion = 17.12.35527.113 d17.12
+VisualStudioVersion = 17.12.35527.113
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Machine.Specifications", "src\Machine.Specifications\Machine.Specifications.csproj", "{EC054D80-8858-4A61-9FD9-0185EA3F4643}"
EndProject
@@ -27,6 +27,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Machine.Specifications.Fake
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Machine.Specifications.Fakes.Specs", "src\Machine.Specifications.Fakes.Specs\Machine.Specifications.Fakes.Specs.csproj", "{B897126F-BF01-4A97-965D-C2DE1BC63460}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Machine.Specifications.Runner.VisualStudio", "src\Machine.Specifications.Runner.VisualStudio\Machine.Specifications.Runner.VisualStudio.csproj", "{D7689810-9604-4E3E-9821-28ABA130B28E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -81,6 +83,10 @@ Global
{B897126F-BF01-4A97-965D-C2DE1BC63460}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B897126F-BF01-4A97-965D-C2DE1BC63460}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B897126F-BF01-4A97-965D-C2DE1BC63460}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D7689810-9604-4E3E-9821-28ABA130B28E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D7689810-9604-4E3E-9821-28ABA130B28E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D7689810-9604-4E3E-9821-28ABA130B28E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D7689810-9604-4E3E-9821-28ABA130B28E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/Machine.Specifications.Analyzers.Tests/Machine.Specifications.Analyzers.Tests.csproj b/src/Machine.Specifications.Analyzers.Tests/Machine.Specifications.Analyzers.Tests.csproj
index f5030e7a..9c570f19 100644
--- a/src/Machine.Specifications.Analyzers.Tests/Machine.Specifications.Analyzers.Tests.csproj
+++ b/src/Machine.Specifications.Analyzers.Tests/Machine.Specifications.Analyzers.Tests.csproj
@@ -2,9 +2,11 @@
net8.0
+ Exe
enable
enable
latest
+ true
false
@@ -13,15 +15,10 @@
-
+
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
+
diff --git a/src/Machine.Specifications.Analyzers/Machine.Specifications.Analyzers.csproj b/src/Machine.Specifications.Analyzers/Machine.Specifications.Analyzers.csproj
index e05b64db..9b077b5c 100644
--- a/src/Machine.Specifications.Analyzers/Machine.Specifications.Analyzers.csproj
+++ b/src/Machine.Specifications.Analyzers/Machine.Specifications.Analyzers.csproj
@@ -5,6 +5,7 @@
enable
enable
latest
+ false
true
true
$(TargetsForTfmSpecificContentInPackage);PackageItems
@@ -15,7 +16,7 @@
-
+
diff --git a/src/Machine.Specifications.Analyzers/Maintainability/AccessModifierShouldNotBeUsedCodeFixProvider.cs b/src/Machine.Specifications.Analyzers/Maintainability/AccessModifierShouldNotBeUsedCodeFixProvider.cs
index 52d0b017..1d002082 100644
--- a/src/Machine.Specifications.Analyzers/Maintainability/AccessModifierShouldNotBeUsedCodeFixProvider.cs
+++ b/src/Machine.Specifications.Analyzers/Maintainability/AccessModifierShouldNotBeUsedCodeFixProvider.cs
@@ -87,7 +87,7 @@ private SyntaxNode HandleDeclaration(MemberDeclarationSyntax declaration)
.WithLeadingTrivia(trivia);
}
- private SyntaxNode GetParentDeclaration(SyntaxNode declaration)
+ private SyntaxNode? GetParentDeclaration(SyntaxNode? declaration)
{
while (declaration != null)
{
diff --git a/src/Machine.Specifications.Core.Specs/Machine.Specifications.Core.Specs.csproj b/src/Machine.Specifications.Core.Specs/Machine.Specifications.Core.Specs.csproj
index e0c4d44b..5c4485b5 100644
--- a/src/Machine.Specifications.Core.Specs/Machine.Specifications.Core.Specs.csproj
+++ b/src/Machine.Specifications.Core.Specs/Machine.Specifications.Core.Specs.csproj
@@ -1,18 +1,21 @@
-
+
net472;net8.0
+ enable
+ enable
+ latest
false
-
-
+
+
diff --git a/src/Machine.Specifications.Core/Machine.Specifications.Core.csproj b/src/Machine.Specifications.Core/Machine.Specifications.Core.csproj
index 689443a7..999fb2f0 100644
--- a/src/Machine.Specifications.Core/Machine.Specifications.Core.csproj
+++ b/src/Machine.Specifications.Core/Machine.Specifications.Core.csproj
@@ -2,8 +2,12 @@
net472;net6.0
+ Machine.Specifications
Machine.Specifications
Machine.Specifications.Core
+ enable
+ enable
+ latest
@@ -11,7 +15,7 @@
-
+
diff --git a/src/Machine.Specifications.Fakes.Specs/Machine.Specifications.Fakes.Specs.csproj b/src/Machine.Specifications.Fakes.Specs/Machine.Specifications.Fakes.Specs.csproj
index 6c4b278c..517212a4 100644
--- a/src/Machine.Specifications.Fakes.Specs/Machine.Specifications.Fakes.Specs.csproj
+++ b/src/Machine.Specifications.Fakes.Specs/Machine.Specifications.Fakes.Specs.csproj
@@ -2,17 +2,20 @@
net8.0
+ enable
+ enable
+ latest
false
-
-
+
+
diff --git a/src/Machine.Specifications.Fakes/Machine.Specifications.Fakes.csproj b/src/Machine.Specifications.Fakes/Machine.Specifications.Fakes.csproj
index 5968fe38..ed8b9e57 100644
--- a/src/Machine.Specifications.Fakes/Machine.Specifications.Fakes.csproj
+++ b/src/Machine.Specifications.Fakes/Machine.Specifications.Fakes.csproj
@@ -2,11 +2,14 @@
net472;net6.0
+ enable
+ enable
+ latest
false
-
+
diff --git a/src/Machine.Specifications.Fixtures/Machine.Specifications.Fixtures.csproj b/src/Machine.Specifications.Fixtures/Machine.Specifications.Fixtures.csproj
index 06f17ff3..48182e80 100644
--- a/src/Machine.Specifications.Fixtures/Machine.Specifications.Fixtures.csproj
+++ b/src/Machine.Specifications.Fixtures/Machine.Specifications.Fixtures.csproj
@@ -2,13 +2,12 @@
net472;net8.0
+ enable
+ enable
+ latest
false
-
-
-
-
diff --git a/src/Machine.Specifications.Runner.Utility.Specs/Machine.Specifications.Runner.Utility.Specs.csproj b/src/Machine.Specifications.Runner.Utility.Specs/Machine.Specifications.Runner.Utility.Specs.csproj
index 95aee073..3c11e7ec 100644
--- a/src/Machine.Specifications.Runner.Utility.Specs/Machine.Specifications.Runner.Utility.Specs.csproj
+++ b/src/Machine.Specifications.Runner.Utility.Specs/Machine.Specifications.Runner.Utility.Specs.csproj
@@ -1,19 +1,22 @@
-
+
- net472;net8.0
+ net8.0
+ enable
+ enable
+ latest
false
-
-
-
+
+
+
diff --git a/src/Machine.Specifications.Runner.Utility/Machine.Specifications.Runner.Utility.csproj b/src/Machine.Specifications.Runner.Utility/Machine.Specifications.Runner.Utility.csproj
index ff729e52..683d5bfa 100644
--- a/src/Machine.Specifications.Runner.Utility/Machine.Specifications.Runner.Utility.csproj
+++ b/src/Machine.Specifications.Runner.Utility/Machine.Specifications.Runner.Utility.csproj
@@ -2,6 +2,9 @@
net472;net6.0
+ enable
+ enable
+ latest
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Fixtures/FixtureCode.cs b/src/Machine.Specifications.Runner.VisualStudio.Fixtures/FixtureCode.cs
new file mode 100644
index 00000000..dfc5e2f9
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Fixtures/FixtureCode.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using Machine.Specifications;
+
+namespace SampleSpecs
+{
+ [Behaviors]
+ public class SampleBehavior
+ {
+ It sample_behavior_test = () =>
+ true.ShouldBeTrue();
+ }
+
+ class BehaviorSampleSpec
+ {
+ Because of = () => { };
+
+ Behaves_like some_behavior;
+ }
+
+ class CleanupSpec
+ {
+ static int cleanup_count;
+
+ Because of = () => { };
+
+ Cleanup after = () =>
+ cleanup_count++;
+
+ It should_not_increment_cleanup = () =>
+ cleanup_count.ShouldEqual(0);
+
+ It should_have_no_cleanups = () =>
+ cleanup_count.ShouldEqual(0);
+ }
+
+ [AssertDelegate]
+ public delegate void They();
+
+ [ActDelegate]
+ public delegate void WhenDoing();
+
+ class CustomActAssertDelegateSpec
+ {
+ static string a;
+ static string b;
+
+ static int resultA;
+ static int resultB;
+
+ Establish context = () =>
+ {
+ a = "foo";
+ b = "foo";
+ };
+
+ WhenDoing of = () =>
+ {
+ resultA = a.GetHashCode();
+ resultB = b.GetHashCode();
+ };
+
+ They should_have_the_same_hash_code = () => resultA.ShouldEqual(resultB);
+ }
+
+ class Parent
+ {
+ class NestedSpec
+ {
+ It should_remember_that_true_is_true = () =>
+ true.ShouldBeTrue();
+ }
+ }
+
+ class StandardSpec
+ {
+ Because of = () => { };
+
+ It should_pass = () =>
+ 1.ShouldEqual(1);
+
+ [Ignore("reason")]
+ It should_be_ignored = () => { };
+
+ It should_fail = () =>
+ 1.ShouldEqual(2);
+
+ It unhandled_exception = () =>
+ {
+ throw new NotImplementedException();
+ };
+
+ It not_implemented;
+ }
+
+ class When_something
+ {
+ Because of = () => { };
+
+ It should_pass = () =>
+ 1.ShouldEqual(1);
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Fixtures/Machine.Specifications.Runner.VisualStudio.Fixtures.csproj b/src/Machine.Specifications.Runner.VisualStudio.Fixtures/Machine.Specifications.Runner.VisualStudio.Fixtures.csproj
new file mode 100644
index 00000000..98b564ca
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Fixtures/Machine.Specifications.Runner.VisualStudio.Fixtures.csproj
@@ -0,0 +1,21 @@
+
+
+
+ netstandard2.0
+ false
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/.editorconfig b/src/Machine.Specifications.Runner.VisualStudio.Specs/.editorconfig
new file mode 100644
index 00000000..3683dcfb
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/.editorconfig
@@ -0,0 +1,37 @@
+; This file is for unifying the coding style for different editors and IDEs.
+; More information at http://EditorConfig.org
+
+[*.cs]
+dotnet_style_require_accessibility_modifiers = never
+dotnet_style_readonly_field = false
+
+# Severities
+dotnet_diagnostic.IDE0052.severity = none
+
+# Naming styles
+dotnet_naming_style.snake_case.capitalization = all_lower
+dotnet_naming_style.snake_case.word_separator = _
+
+# Symbol specifications
+dotnet_naming_symbols.static_field.applicable_kinds = field
+dotnet_naming_symbols.static_field.applicable_accessibilities = *
+dotnet_naming_symbols.static_field.required_modifiers = static
+
+dotnet_naming_symbols.specs_delegate.applicable_kinds = field
+dotnet_naming_symbols.specs_delegate.applicable_accessibilities = *
+
+dotnet_naming_symbols.specs_class.applicable_kinds = class
+dotnet_naming_symbols.specs_class.applicable_accessibilities = private
+
+# Naming rules
+dotnet_naming_rule.static_field_should_be_snake_case.severity = error
+dotnet_naming_rule.static_field_should_be_snake_case.symbols = static_field
+dotnet_naming_rule.static_field_should_be_snake_case.style = snake_case
+
+dotnet_naming_rule.machine_delegate_should_be_snake_case.severity = error
+dotnet_naming_rule.machine_delegate_should_be_snake_case.symbols = specs_delegate
+dotnet_naming_rule.machine_delegate_should_be_snake_case.style = snake_case
+
+dotnet_naming_rule.specs_class_should_be_snake_case.severity = error
+dotnet_naming_rule.specs_class_should_be_snake_case.symbols = specs_class
+dotnet_naming_rule.specs_class_should_be_snake_case.style = snake_case
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/BehaviorsSpecs.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/BehaviorsSpecs.cs
new file mode 100644
index 00000000..d1df543e
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/BehaviorsSpecs.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Linq;
+using Machine.Specifications.Runner.VisualStudio.Discovery;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Discovery
+{
+ class BehaviorsSpecs : WithDiscoverySetup
+ {
+ It should_pick_up_the_behavior = () =>
+ {
+ var discoveredSpec = results.SingleOrDefault(x =>
+ "sample_behavior_test".Equals(x.SpecificationName, StringComparison.Ordinal) &&
+ "BehaviorSampleSpec".Equals(x.ClassName, StringComparison.Ordinal));
+
+ discoveredSpec.ShouldNotBeNull();
+
+ discoveredSpec.LineNumber.ShouldEqual(10);
+ discoveredSpec.CodeFilePath.EndsWith("BehaviorSample.cs", StringComparison.Ordinal);
+ };
+
+ It should_pick_up_the_behavior_field_type_and_name = () =>
+ {
+ var discoveredSpec = results.SingleOrDefault(x =>
+ "sample_behavior_test".Equals(x.SpecificationName, StringComparison.Ordinal) &&
+ "BehaviorSampleSpec".Equals(x.ClassName, StringComparison.Ordinal));
+
+ discoveredSpec.ShouldNotBeNull();
+
+ discoveredSpec.BehaviorFieldName.ShouldEqual("some_behavior");
+ discoveredSpec.BehaviorFieldType.ShouldEqual("SampleSpecs.SampleBehavior");
+ };
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/CustomActAssertSpecs.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/CustomActAssertSpecs.cs
new file mode 100644
index 00000000..dacc1f5f
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/CustomActAssertSpecs.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Linq;
+using Machine.Specifications.Runner.VisualStudio.Discovery;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Discovery
+{
+ class CustomActAssertSpecs : WithDiscoverySetup
+ {
+ It should_find_some = () =>
+ {
+ var discoveredSpec = results.SingleOrDefault(x =>
+ "should_have_the_same_hash_code".Equals(x.SpecificationName, StringComparison.Ordinal) &&
+ "CustomActAssertDelegateSpec".Equals(x.ClassName, StringComparison.Ordinal));
+
+ discoveredSpec.ShouldNotBeNull();
+
+ discoveredSpec.LineNumber.ShouldEqual(63);
+ discoveredSpec.CodeFilePath.EndsWith("CustomActAssertDelegateSpec.cs", StringComparison.Ordinal);
+ };
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/DisabledTestNameOffSpecs.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/DisabledTestNameOffSpecs.cs
new file mode 100644
index 00000000..da1f39b8
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/DisabledTestNameOffSpecs.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Reflection;
+using Machine.Fakes;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
+using SampleSpecs;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Discovery
+{
+ class DisabledTestNameOffSpecs : WithFakes
+ {
+ static Assembly assembly;
+
+ Establish context = () =>
+ assembly = typeof(StandardSpec).Assembly;
+
+ Because of = () =>
+ The()
+ .DiscoverTests(new[] {assembly.GetType("SampleSpecs.When_something").GetTypeInfo().Assembly.Location},
+ An(),
+ An(),
+ The());
+
+
+ It should_use_full_type_and_field_name_for_display_name = () =>
+ The()
+ .WasToldTo(d => d.SendTestCase(Param.Matches(t => t.DisplayName.Equals("When something it should pass", StringComparison.Ordinal))))
+ .OnlyOnce();
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/DiscoverySpecs.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/DiscoverySpecs.cs
new file mode 100644
index 00000000..b9a35c94
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/DiscoverySpecs.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Linq;
+using Machine.Specifications.Runner.VisualStudio.Discovery;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Discovery
+{
+ class DiscoverySpecs : WithDiscoverySetup
+ {
+ It should_find_spec = () =>
+ {
+ var discoveredSpec = results.SingleOrDefault(x =>
+ "should_pass".Equals(x.SpecificationName, StringComparison.Ordinal) &&
+ "StandardSpec".Equals(x.ClassName, StringComparison.Ordinal));
+
+ discoveredSpec.ShouldNotBeNull();
+
+ discoveredSpec.LineNumber.ShouldEqual(79);
+ discoveredSpec.CodeFilePath.EndsWith("StandardSpec.cs", StringComparison.Ordinal);
+ };
+
+ It should_find_empty_spec = () =>
+ {
+ var discoveredSpec = results.SingleOrDefault(x =>
+ "should_be_ignored".Equals(x.SpecificationName, StringComparison.Ordinal) &&
+ "StandardSpec".Equals(x.ClassName, StringComparison.Ordinal));
+
+ discoveredSpec.ShouldNotBeNull();
+
+ discoveredSpec.LineNumber.ShouldEqual(83);
+ discoveredSpec.CodeFilePath.EndsWith("StandardSpec.cs", StringComparison.Ordinal);
+ };
+
+ It should_find_ignored_spec_but_will_not_find_line_number = () =>
+ {
+ var discoveredSpec = results.SingleOrDefault(x =>
+ "not_implemented".Equals(x.SpecificationName, StringComparison.Ordinal) &&
+ "StandardSpec".Equals(x.ClassName, StringComparison.Ordinal));
+
+ discoveredSpec.ShouldNotBeNull();
+
+ discoveredSpec.LineNumber.ShouldEqual(0);
+ discoveredSpec.CodeFilePath.ShouldBeNull();
+ };
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/NestedTypesDiscoverySpecs.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/NestedTypesDiscoverySpecs.cs
new file mode 100644
index 00000000..b5d7cd4d
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/NestedTypesDiscoverySpecs.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Linq;
+using Machine.Specifications.Runner.VisualStudio.Discovery;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Discovery.BuiltIn
+{
+ class NestedTypesDiscoverySpecs : WithDiscoverySetup
+ {
+ It should_discover_the_sample_behavior = () =>
+ {
+ var discoveredSpec = results.SingleOrDefault(x =>
+ "should_remember_that_true_is_true".Equals(x.SpecificationName, StringComparison.Ordinal) &&
+ "NestedSpec".Equals(x.ClassName, StringComparison.Ordinal));
+
+ discoveredSpec.ShouldNotBeNull();
+
+ discoveredSpec.ContextDisplayName.ShouldEqual("Parent NestedSpec");
+
+ discoveredSpec.LineNumber.ShouldEqual(70);
+ discoveredSpec.CodeFilePath.EndsWith("NestedSpecSample.cs", StringComparison.Ordinal);
+ };
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/WithDiscoverySetup.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/WithDiscoverySetup.cs
new file mode 100644
index 00000000..f9a17eca
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Discovery/WithDiscoverySetup.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+using System.Reflection;
+using Machine.Specifications.Runner.VisualStudio.Discovery;
+using SampleSpecs;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Discovery
+{
+ public abstract class WithDiscoverySetup
+ where TDiscoverer : ISpecificationDiscoverer, new()
+ {
+ static ISpecificationDiscoverer discoverer;
+
+ static Assembly assembly;
+
+ protected static IEnumerable results;
+
+ Establish context = () =>
+ {
+ discoverer = new TDiscoverer();
+
+ assembly = typeof(StandardSpec).Assembly;
+ };
+
+ Because of = () =>
+ results = discoverer.DiscoverSpecs(assembly.Location);
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/DiscoveryUnhandledErrorSpecs.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/DiscoveryUnhandledErrorSpecs.cs
new file mode 100644
index 00000000..18e29661
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/DiscoveryUnhandledErrorSpecs.cs
@@ -0,0 +1,31 @@
+using System;
+using Machine.Fakes;
+using Machine.Specifications.Runner.VisualStudio.Discovery;
+using Machine.Specifications.Runner.VisualStudio.Execution;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs
+{
+ class DiscoveryUnhandledErrorSpecs : WithFakes
+ {
+ static MspecTestRunner runner;
+
+ Establish context = () =>
+ {
+ The()
+ .WhenToldTo(d => d.DiscoverSpecs(Param.IsAnything))
+ .Return(() => throw new InvalidOperationException());
+
+ runner = new MspecTestRunner(The(), An(), An());
+ };
+
+ Because of = () =>
+ runner.DiscoverTests(new[] { "bla" }, An(), The(), An());
+
+ It should_send_an_error_notification_to_visual_studio = () =>
+ The()
+ .WasToldTo(logger => logger.SendMessage(TestMessageLevel.Error, Param.IsNotNull))
+ .OnlyOnce();
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/RunListener/WhenSpecificationEndsWithAFail.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/RunListener/WhenSpecificationEndsWithAFail.cs
new file mode 100644
index 00000000..ed7ba0b6
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/RunListener/WhenSpecificationEndsWithAFail.cs
@@ -0,0 +1,53 @@
+using System;
+using Machine.Fakes;
+using Machine.Specifications.Runner.VisualStudio.Execution;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Execution.RunListener
+{
+ [Subject(typeof(ProxyAssemblySpecificationRunListener))]
+ class WhenSpecificationEndsWithAFail : WithFakes
+ {
+ static ProxyAssemblySpecificationRunListener run_listener;
+
+ protected static TestCase test_case;
+
+ static SpecificationInfo specification_info = new SpecificationInfo("leader", "field name", "ContainingType", "field_name");
+
+ Establish context = () =>
+ {
+ The()
+ .WhenToldTo(f => f.RecordEnd(Param.IsAnything, Param.IsAnything))
+ .Callback((TestCase testCase, TestOutcome outcome) => test_case = testCase);
+
+ run_listener = new ProxyAssemblySpecificationRunListener("assemblyPath", The(), new Uri("bla://executorUri"));
+ };
+
+
+ Because of = () =>
+ run_listener.OnSpecificationEnd(specification_info, Result.Failure(new NotImplementedException()));
+
+ It should_notify_visual_studio_of_the_test_outcome = () =>
+ The()
+ .WasToldTo(f => f.RecordEnd(Param.IsNotNull, Param.Matches(outcome => outcome == TestOutcome.Failed)))
+ .OnlyOnce();
+
+ It should_notify_visual_studio_of_the_test_result = () =>
+ The()
+ .WasToldTo(f => f.RecordResult(Param.Matches(result =>
+ result.Outcome == TestOutcome.Failed &&
+ result.ComputerName == Environment.MachineName &&
+ result.ErrorMessage == new NotImplementedException().Message &&
+ !string.IsNullOrWhiteSpace(result.ErrorStackTrace)
+ )))
+ .OnlyOnce();
+
+ It should_provide_correct_details_to_visual_studio = () =>
+ {
+ test_case.FullyQualifiedName.ShouldEqual("ContainingType::field_name");
+ test_case.ExecutorUri.ShouldEqual(new Uri("bla://executorUri"));
+ test_case.Source.ShouldEqual("assemblyPath");
+ };
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/RunListener/WhenSpecificationEndsWithAPass.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/RunListener/WhenSpecificationEndsWithAPass.cs
new file mode 100644
index 00000000..c1d20bab
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/RunListener/WhenSpecificationEndsWithAPass.cs
@@ -0,0 +1,52 @@
+using System;
+using Machine.Fakes;
+using Machine.Specifications.Runner.VisualStudio.Execution;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Execution.RunListener
+{
+ [Subject(typeof(ProxyAssemblySpecificationRunListener))]
+ class WhenSpecificationEndsWithAPass : WithFakes
+ {
+ static ProxyAssemblySpecificationRunListener run_listener;
+
+ protected static TestCase test_case;
+
+ static SpecificationInfo specification_info = new SpecificationInfo("leader", "field name", "ContainingType", "field_name");
+
+ Establish context = () =>
+ {
+ The()
+ .WhenToldTo(f => f.RecordEnd(Param.IsAnything, Param.IsAnything))
+ .Callback((TestCase testCase, TestOutcome outcome) => test_case = testCase);
+
+ run_listener = new ProxyAssemblySpecificationRunListener("assemblyPath", The(), new Uri("bla://executorUri"));
+ };
+
+ Because of = () =>
+ run_listener.OnSpecificationEnd(specification_info, Result.Pass());
+
+ It should_notify_visual_studio_of_the_test_outcome = () =>
+ The()
+ .WasToldTo(f => f.RecordEnd(Param.IsNotNull, Param.Matches(outcome => outcome == TestOutcome.Passed)))
+ .OnlyOnce();
+
+ It should_notify_visual_studio_of_the_test_result = () =>
+ The()
+ .WasToldTo(f => f.RecordResult(Param.Matches(result =>
+ result.Outcome == TestOutcome.Passed &&
+ result.ComputerName == Environment.MachineName &&
+ result.ErrorMessage == null &&
+ result.ErrorStackTrace == null
+ )))
+ .OnlyOnce();
+
+ It should_provide_correct_details_to_visual_studio = () =>
+ {
+ test_case.FullyQualifiedName.ShouldEqual("ContainingType::field_name");
+ test_case.ExecutorUri.ShouldEqual(new Uri("bla://executorUri"));
+ test_case.Source.ShouldEqual("assemblyPath");
+ };
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/RunListener/WhenSpecificationStarts.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/RunListener/WhenSpecificationStarts.cs
new file mode 100644
index 00000000..b3da426b
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/RunListener/WhenSpecificationStarts.cs
@@ -0,0 +1,38 @@
+using System;
+using Machine.Fakes;
+using Machine.Specifications.Runner.VisualStudio.Execution;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Execution.RunListener
+{
+ [Subject(typeof(ProxyAssemblySpecificationRunListener))]
+ class WhenSpecificationStarts : WithFakes
+ {
+ static ProxyAssemblySpecificationRunListener run_listener;
+
+ protected static TestCase test_case;
+
+ Establish context = () =>
+ {
+ The()
+ .WhenToldTo(f => f.RecordStart(Param.IsAnything))
+ .Callback((TestCase testCase) => test_case = testCase);
+
+ run_listener = new ProxyAssemblySpecificationRunListener("assemblyPath", The(), new Uri("bla://executorUri"));
+ };
+
+ Because of = () =>
+ run_listener.OnSpecificationStart(new SpecificationInfo("leader", "field name", "ContainingType", "field_name"));
+
+ It should_notify_visual_studio = () =>
+ The().WasToldTo(f => f.RecordStart(Param.IsNotNull));
+
+ It should_provide_correct_details_to_visual_studio = () =>
+ {
+ test_case.FullyQualifiedName.ShouldEqual("ContainingType::field_name");
+ test_case.ExecutorUri.ShouldEqual(new Uri("bla://executorUri"));
+ test_case.Source.ShouldEqual("assemblyPath");
+ };
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/RunListener/WhenThereIsAnErrorReported.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/RunListener/WhenThereIsAnErrorReported.cs
new file mode 100644
index 00000000..6c6578cc
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/RunListener/WhenThereIsAnErrorReported.cs
@@ -0,0 +1,27 @@
+using System;
+using Machine.Fakes;
+using Machine.Specifications.Runner.VisualStudio.Execution;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Execution.RunListener
+{
+ [Subject(typeof(ProxyAssemblySpecificationRunListener))]
+ class WhenThereIsAnErrorReported : WithFakes
+ {
+ static ProxyAssemblySpecificationRunListener run_listener;
+
+ Establish context = () =>
+ run_listener = new ProxyAssemblySpecificationRunListener("assemblyPath", The(), new Uri("bla://executorUri"));
+
+ Because of = () =>
+ run_listener.OnFatalError(new ExceptionResult(new InvalidOperationException()));
+
+ It should_notify_visual_studio_of_the_error_outcome = () =>
+ The()
+ .WasToldTo(f => f.SendMessage(
+ Param.Matches(level => level == TestMessageLevel.Error),
+ Param.Matches(message => message.Contains("InvalidOperationException"))))
+ .OnlyOnce();
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenCleaningUpAContext.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenCleaningUpAContext.cs
new file mode 100644
index 00000000..803740f1
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenCleaningUpAContext.cs
@@ -0,0 +1,31 @@
+using System.Linq;
+using Machine.Fakes;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Execution
+{
+ class WhenCleaningUpAContext : WithMultipleSpecExecutionSetup
+ {
+ Establish context = () =>
+ specifications_to_run = new[]
+ {
+ new VisualStudioTestIdentifier("SampleSpecs.CleanupSpec", "should_not_increment_cleanup"),
+ new VisualStudioTestIdentifier("SampleSpecs.CleanupSpec", "should_have_no_cleanups")
+ };
+
+ It should_tell_visual_studio_it_passed = () =>
+ {
+ The()
+ .WasToldTo(x => x.RecordEnd(
+ Param.Matches(y => specifications_to_run.Contains(y.ToVisualStudioTestIdentifier())),
+ Param.Matches(y => y == TestOutcome.Passed)))
+ .Twice();
+
+ The()
+ .WasToldTo(x =>x.RecordResult(Param.Matches(y => y.Outcome == TestOutcome.Passed)))
+ .Twice();
+ };
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningANestedSpecPasses.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningANestedSpecPasses.cs
new file mode 100644
index 00000000..d22cfac9
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningANestedSpecPasses.cs
@@ -0,0 +1,27 @@
+using Machine.Fakes;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Execution
+{
+ class WhenRunningANestedSpecPasses : WithSingleSpecExecutionSetup
+ {
+ Establish context = () =>
+ specification_to_run = new VisualStudioTestIdentifier("SampleSpecs.Parent+NestedSpec", "should_remember_that_true_is_true");
+
+ It should_tell_visual_studio_it_passed = () =>
+ {
+ The()
+ .WasToldTo(handle =>
+ handle.RecordEnd(
+ Param.Matches(x => x.ToVisualStudioTestIdentifier().Equals(specification_to_run)),
+ Param.Matches(x => x == TestOutcome.Passed)))
+ .OnlyOnce();
+
+ The()
+ .WasToldTo(x => x.RecordResult(Param.Matches(result => result.Outcome == TestOutcome.Passed)))
+ .OnlyOnce();
+ };
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASingleBehaviorPasses.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASingleBehaviorPasses.cs
new file mode 100644
index 00000000..25926a5c
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASingleBehaviorPasses.cs
@@ -0,0 +1,27 @@
+using Machine.Fakes;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Execution
+{
+ class WhenRunningASingleBehaviorPasses : WithSingleSpecExecutionSetup
+ {
+ Establish context = () =>
+ specification_to_run = new VisualStudioTestIdentifier("SampleSpecs.BehaviorSampleSpec", "sample_behavior_test");
+
+ It should_tell_visual_studio_it_passed = () =>
+ {
+ The()
+ .WasToldTo(handle =>
+ handle.RecordEnd(Param.Matches(testCase => testCase.ToVisualStudioTestIdentifier().Equals(specification_to_run)),
+ Param.Matches(outcome => outcome == TestOutcome.Passed)))
+ .OnlyOnce();
+
+ The()
+ .WasToldTo(handle =>
+ handle.RecordResult(Param.Matches(result => result.Outcome == TestOutcome.Passed)))
+ .OnlyOnce();
+ };
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecThatFails.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecThatFails.cs
new file mode 100644
index 00000000..111297f3
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecThatFails.cs
@@ -0,0 +1,27 @@
+using Machine.Fakes;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Execution
+{
+ class WhenRunningASpecThatFails : WithSingleSpecExecutionSetup
+ {
+ Establish context = () =>
+ specification_to_run = new VisualStudioTestIdentifier("SampleSpecs.StandardSpec", "should_fail");
+
+ It should_tell_visual_studio_it_failed= () =>
+ {
+ The()
+ .WasToldTo(handle =>
+ handle.RecordEnd(Param.Matches(testCase => testCase.ToVisualStudioTestIdentifier().Equals(specification_to_run)),
+ Param.Matches(outcome => outcome == TestOutcome.Failed)))
+ .OnlyOnce();
+
+ The()
+ .WasToldTo(handle =>
+ handle.RecordResult(Param.Matches(result => result.Outcome == TestOutcome.Failed)))
+ .OnlyOnce();
+ };
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecThatIsIgnored.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecThatIsIgnored.cs
new file mode 100644
index 00000000..cea8b674
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecThatIsIgnored.cs
@@ -0,0 +1,27 @@
+using Machine.Fakes;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Execution
+{
+ class WhenRunningASpecThatIsIgnored : WithSingleSpecExecutionSetup
+ {
+ Establish context = () =>
+ specification_to_run = new VisualStudioTestIdentifier("SampleSpecs.StandardSpec", "should_be_ignored");
+
+ It should_tell_visual_studio_it_was_skipped = () =>
+ {
+ The()
+ .WasToldTo(handle =>
+ handle.RecordEnd(Param.Matches(testCase => testCase.ToVisualStudioTestIdentifier().Equals(specification_to_run)),
+ Param.Matches(outcome => outcome == TestOutcome.Skipped)))
+ .OnlyOnce();
+
+ The()
+ .WasToldTo(handle =>
+ handle.RecordResult(Param.Matches(result => result.Outcome == TestOutcome.Skipped)))
+ .OnlyOnce();
+ };
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecThatIsNotImplemented.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecThatIsNotImplemented.cs
new file mode 100644
index 00000000..a588baca
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecThatIsNotImplemented.cs
@@ -0,0 +1,29 @@
+using Machine.Fakes;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Execution
+{
+ class WhenRunningASpecThatIsNotImplemented : WithSingleSpecExecutionSetup
+ {
+ Establish context = () =>
+ specification_to_run = new VisualStudioTestIdentifier("SampleSpecs.StandardSpec", "not_implemented");
+
+ It should_tell_visual_studio_it_was_not_found = () =>
+ {
+ The()
+ .WasToldTo(handle =>
+ handle.RecordEnd(
+ Param.Matches(testCase =>
+ testCase.ToVisualStudioTestIdentifier().Equals(specification_to_run)),
+ Param.Matches(outcome => outcome == TestOutcome.NotFound)))
+ .OnlyOnce();
+
+ The()
+ .WasToldTo(handle =>
+ handle.RecordResult(Param.Matches(result => result.Outcome == TestOutcome.NotFound)))
+ .OnlyOnce();
+ };
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecThatPasses.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecThatPasses.cs
new file mode 100644
index 00000000..b33dc695
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecThatPasses.cs
@@ -0,0 +1,27 @@
+using Machine.Fakes;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Execution
+{
+ class WhenRunningASpecThatPasses : WithSingleSpecExecutionSetup
+ {
+ Establish context = () =>
+ specification_to_run = new VisualStudioTestIdentifier("SampleSpecs.StandardSpec", "should_pass");
+
+ It should_tell_visual_studio_it_passed = () =>
+ {
+ The()
+ .WasToldTo(handle =>
+ handle.RecordEnd(Param.Matches(testCase => testCase.ToVisualStudioTestIdentifier().Equals(specification_to_run)),
+ Param.Matches(outcome => outcome == TestOutcome.Passed)))
+ .OnlyOnce();
+
+ The()
+ .WasToldTo(handle =>
+ handle.RecordResult(Param.Matches(result => result.Outcome == TestOutcome.Passed)))
+ .OnlyOnce();
+ };
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecThatThrows.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecThatThrows.cs
new file mode 100644
index 00000000..e3592b5c
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecThatThrows.cs
@@ -0,0 +1,28 @@
+using Machine.Fakes;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Execution
+{
+ class WhenRunningASpecThatThrows : WithSingleSpecExecutionSetup
+ {
+ Establish context = () =>
+ specification_to_run = new VisualStudioTestIdentifier("SampleSpecs.StandardSpec", "unhandled_exception");
+
+ It should_tell_visual_studio_it_failed = () =>
+ {
+ The()
+ .WasToldTo(handle =>
+ handle.RecordEnd(
+ Param.Matches(testCase => testCase.ToVisualStudioTestIdentifier().Equals(specification_to_run)),
+ Param.Matches(outcome => outcome == TestOutcome.Failed)))
+ .OnlyOnce();
+
+ The()
+ .WasToldTo(handle =>
+ handle.RecordResult(Param.Matches(result => result.Outcome == TestOutcome.Failed)))
+ .OnlyOnce();
+ };
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecWithCustomActAssertDelegatesPasses.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecWithCustomActAssertDelegatesPasses.cs
new file mode 100644
index 00000000..53da8edf
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningASpecWithCustomActAssertDelegatesPasses.cs
@@ -0,0 +1,27 @@
+using Machine.Fakes;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Execution
+{
+ class WhenRunningASpecWithCustomActAssertDelegatesPasses : WithSingleSpecExecutionSetup
+ {
+ Establish context = () =>
+ specification_to_run = new VisualStudioTestIdentifier("SampleSpecs.CustomActAssertDelegateSpec", "should_have_the_same_hash_code");
+
+ It should_tell_visual_studio_it_passed = () =>
+ {
+ The()
+ .WasToldTo(handle =>
+ handle.RecordEnd(Param.Matches(testCase => testCase.ToVisualStudioTestIdentifier().Equals(specification_to_run)),
+ Param.Matches(outcome => outcome == TestOutcome.Passed)))
+ .OnlyOnce();
+
+ The()
+ .WasToldTo(handle =>
+ handle.RecordResult(Param.Matches(result => result.Outcome == TestOutcome.Passed)))
+ .OnlyOnce();
+ };
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningAnAssemblyWithBehaviors.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningAnAssemblyWithBehaviors.cs
new file mode 100644
index 00000000..56849e58
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WhenRunningAnAssemblyWithBehaviors.cs
@@ -0,0 +1,31 @@
+using Machine.Fakes;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Execution
+{
+ class WhenRunningAnAssemblyWithBehaviors : WithAssemblyExecutionSetup
+ {
+ static VisualStudioTestIdentifier specification_expected_to_run;
+
+ Establish context = () =>
+ specification_expected_to_run = new VisualStudioTestIdentifier("SampleSpecs.BehaviorSampleSpec", "sample_behavior_test");
+
+ It should_run_all_behaviors = () =>
+ {
+ The()
+ .WasToldTo(handle =>
+ handle.RecordEnd(Param.Matches(testCase => testCase.ToVisualStudioTestIdentifier().Equals(specification_expected_to_run)),
+ Param.Matches(outcome => outcome == TestOutcome.Passed)))
+ .OnlyOnce();
+
+ The()
+ .WasToldTo(handle =>
+ handle.RecordResult(Param.Matches(result =>
+ result.Outcome == TestOutcome.Passed &&
+ result.TestCase.ToVisualStudioTestIdentifier().Equals(specification_expected_to_run))))
+ .OnlyOnce();
+ };
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WithAssemblyExecutionSetup.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WithAssemblyExecutionSetup.cs
new file mode 100644
index 00000000..5a8afb6b
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WithAssemblyExecutionSetup.cs
@@ -0,0 +1,24 @@
+using System.Reflection;
+using Machine.Fakes;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using SampleSpecs;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Execution
+{
+ public abstract class WithAssemblyExecutionSetup : WithFakes
+ {
+ static MspecTestRunner executor;
+
+ static Assembly assembly;
+
+ Establish context = () =>
+ {
+ executor = new MspecTestRunner();
+
+ assembly = typeof(StandardSpec).Assembly;
+ };
+
+ Because of = () =>
+ executor.RunTests(new[] { assembly.Location }, An(), The());
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WithMultipleSpecExecutionSetup.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WithMultipleSpecExecutionSetup.cs
new file mode 100644
index 00000000..12186170
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WithMultipleSpecExecutionSetup.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Reflection;
+using Machine.Fakes;
+using Machine.Specifications.Runner.VisualStudio.Execution;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using SampleSpecs;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Execution
+{
+ public abstract class WithMultipleSpecExecutionSetup : WithFakes
+ {
+ static ISpecificationExecutor executor;
+
+ static Assembly assembly;
+
+ protected static VisualStudioTestIdentifier[] specifications_to_run;
+
+ Establish context = () =>
+ {
+ executor = new SpecificationExecutor();
+
+ assembly = typeof(StandardSpec).Assembly;
+ };
+
+ Because of = () =>
+ executor.RunAssemblySpecifications(assembly.Location, specifications_to_run, new Uri("bla://executor"), The());
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WithSingleSpecExecutionSetup.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WithSingleSpecExecutionSetup.cs
new file mode 100644
index 00000000..e9f6b9e6
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Execution/WithSingleSpecExecutionSetup.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Reflection;
+using Machine.Fakes;
+using Machine.Specifications.Runner.VisualStudio.Execution;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using SampleSpecs;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs.Execution
+{
+ public abstract class WithSingleSpecExecutionSetup : WithFakes
+ {
+ static ISpecificationExecutor executor;
+
+ static Assembly assembly;
+
+ protected static VisualStudioTestIdentifier specification_to_run;
+
+ Establish context = () =>
+ {
+ executor = new SpecificationExecutor();
+
+ assembly = typeof(StandardSpec).Assembly;
+ };
+
+ Because of = () =>
+ executor.RunAssemblySpecifications(assembly.Location, new[] { specification_to_run }, new Uri("bla://executor"), The());
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/ExecutionUnhandledErrorSpecs.cs b/src/Machine.Specifications.Runner.VisualStudio.Specs/ExecutionUnhandledErrorSpecs.cs
new file mode 100644
index 00000000..7826068d
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/ExecutionUnhandledErrorSpecs.cs
@@ -0,0 +1,38 @@
+using System;
+using Machine.Fakes;
+using Machine.Specifications.Runner.VisualStudio.Discovery;
+using Machine.Specifications.Runner.VisualStudio.Execution;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
+
+namespace Machine.Specifications.Runner.VisualStudio.Specs
+{
+ class ExecutionUnhandledErrorSpecs : WithFakes
+ {
+ static MspecTestExecutor adapter;
+
+ Establish context = () =>
+ {
+ The()
+ .WhenToldTo(d => d.RunAssemblySpecifications(
+ Param.IsAnything,
+ Param.IsAnything,
+ Param.IsAnything,
+ Param.IsAnything))
+ .Throw(new InvalidOperationException());
+
+ var adapterDiscoverer = new MspecTestDiscoverer(An());
+ adapter = new MspecTestExecutor(The(), adapterDiscoverer, An());
+ };
+
+ Because of = () =>
+ adapter.RunTests(new[] {new TestCase("a", MspecTestRunner.Uri, "dll"), }, An(), The());
+
+ It should_send_an_error_notification_to_visual_studio = () =>
+ The()
+ .WasToldTo(logger => logger.SendMessage(TestMessageLevel.Error, Param.IsNotNull))
+ .OnlyOnce();
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio.Specs/Machine.Specifications.Runner.VisualStudio.Specs.csproj b/src/Machine.Specifications.Runner.VisualStudio.Specs/Machine.Specifications.Runner.VisualStudio.Specs.csproj
new file mode 100644
index 00000000..1e1cda41
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio.Specs/Machine.Specifications.Runner.VisualStudio.Specs.csproj
@@ -0,0 +1,20 @@
+
+
+
+ netcoreapp3.1;net472
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Discovery/BuiltInSpecificationDiscoverer.cs b/src/Machine.Specifications.Runner.VisualStudio/Discovery/BuiltInSpecificationDiscoverer.cs
new file mode 100644
index 00000000..56a18c92
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Discovery/BuiltInSpecificationDiscoverer.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using System.Linq;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+
+namespace Machine.Specifications.Runner.VisualStudio.Discovery
+{
+ public class BuiltInSpecificationDiscoverer : ISpecificationDiscoverer
+ {
+ public IEnumerable DiscoverSpecs(string assemblyFilePath)
+ {
+#if NETFRAMEWORK
+ using (var scope = new IsolatedAppDomainExecutionScope(assemblyFilePath))
+ {
+ var discoverer = scope.CreateInstance();
+#else
+ var discoverer = new TestDiscoverer();
+#endif
+ return discoverer.DiscoverTests(assemblyFilePath).ToList();
+#if NETFRAMEWORK
+ }
+#endif
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Discovery/ISpecificationDiscoverer.cs b/src/Machine.Specifications.Runner.VisualStudio/Discovery/ISpecificationDiscoverer.cs
new file mode 100644
index 00000000..e0e723f4
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Discovery/ISpecificationDiscoverer.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace Machine.Specifications.Runner.VisualStudio.Discovery
+{
+ public interface ISpecificationDiscoverer
+ {
+ IEnumerable DiscoverSpecs(string assemblyPath);
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Discovery/SpecTestCase.cs b/src/Machine.Specifications.Runner.VisualStudio/Discovery/SpecTestCase.cs
new file mode 100644
index 00000000..84e22f5d
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Discovery/SpecTestCase.cs
@@ -0,0 +1,32 @@
+using System;
+
+namespace Machine.Specifications.Runner.VisualStudio.Discovery
+{
+#if NETFRAMEWORK
+ [Serializable]
+#endif
+ public class SpecTestCase
+ {
+ public string Subject { get; set; }
+
+ public string ContextFullType { get; set; }
+
+ public object ContextDisplayName { get; set; }
+
+ public string ClassName { get; set; }
+
+ public string SpecificationDisplayName { get; set; }
+
+ public string SpecificationName { get; set; }
+
+ public string BehaviorFieldName { get; set; }
+
+ public string BehaviorFieldType { get; set; }
+
+ public string CodeFilePath { get; set; }
+
+ public int LineNumber { get; set; }
+
+ public string[] Tags { get; set; }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Discovery/TestDiscoverer.cs b/src/Machine.Specifications.Runner.VisualStudio/Discovery/TestDiscoverer.cs
new file mode 100644
index 00000000..829c00fa
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Discovery/TestDiscoverer.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Machine.Specifications.Explorers;
+using Machine.Specifications.Model;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+using Machine.Specifications.Runner.VisualStudio.Navigation;
+
+namespace Machine.Specifications.Runner.VisualStudio.Discovery
+{
+ public class TestDiscoverer
+#if NETFRAMEWORK
+ : MarshalByRefObject
+#endif
+ {
+#if NETFRAMEWORK
+ [System.Security.SecurityCritical]
+ public override object InitializeLifetimeService()
+ {
+ return null;
+ }
+#endif
+ private readonly PropertyInfo behaviorProperty = typeof(BehaviorSpecification).GetProperty("BehaviorFieldInfo");
+
+ public IEnumerable DiscoverTests(string assemblyPath)
+ {
+ var assemblyExplorer = new AssemblyExplorer();
+
+ var assembly = AssemblyHelper.Load(assemblyPath);
+ var contexts = assemblyExplorer.FindContextsIn(assembly);
+
+ using (var session = new NavigationSession(assemblyPath))
+ {
+ return contexts.SelectMany(context => CreateTestCase(context, session)).ToList();
+ }
+ }
+
+ private IEnumerable CreateTestCase(Context context, NavigationSession session)
+ {
+ foreach (var spec in context.Specifications.ToList())
+ {
+ var testCase = new SpecTestCase
+ {
+ ClassName = context.Type.Name,
+ ContextFullType = context.Type.FullName,
+ ContextDisplayName = GetContextDisplayName(context.Type),
+ SpecificationName = spec.FieldInfo.Name,
+ SpecificationDisplayName = spec.Name
+ };
+
+ string fieldDeclaringType;
+
+ if (spec.FieldInfo.DeclaringType.GetTypeInfo().IsGenericType && !spec.FieldInfo.DeclaringType.GetTypeInfo().IsGenericTypeDefinition)
+ fieldDeclaringType = spec.FieldInfo.DeclaringType.GetGenericTypeDefinition().FullName;
+ else
+ fieldDeclaringType = spec.FieldInfo.DeclaringType.FullName;
+
+ var locationInfo = session.GetNavigationData(fieldDeclaringType, spec.FieldInfo.Name);
+
+ if (locationInfo != null)
+ {
+ testCase.CodeFilePath = locationInfo.CodeFile;
+ testCase.LineNumber = locationInfo.LineNumber;
+ }
+
+ if (spec is BehaviorSpecification behaviorSpec)
+ PopulateBehaviorField(testCase, behaviorSpec);
+
+ if (context.Tags != null)
+ testCase.Tags = context.Tags.Select(tag => tag.Name).ToArray();
+
+ if (context.Subject != null)
+ testCase.Subject = context.Subject.FullConcern;
+
+ yield return testCase;
+ }
+ }
+
+ private void PopulateBehaviorField(SpecTestCase testCase, BehaviorSpecification specification)
+ {
+ if (behaviorProperty?.GetValue(specification) is FieldInfo field)
+ {
+ testCase.BehaviorFieldName = field.Name;
+ testCase.BehaviorFieldType = field.FieldType.GenericTypeArguments.FirstOrDefault()?.FullName;
+ }
+ }
+
+ private string GetContextDisplayName(Type contextType)
+ {
+ var displayName = contextType.Name.Replace("_", " ");
+
+ if (contextType.IsNested)
+ {
+ return GetContextDisplayName(contextType.DeclaringType) + " " + displayName;
+ }
+
+ return displayName;
+ }
+ }
+
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Execution/AssemblyLocationAwareRunListener.cs b/src/Machine.Specifications.Runner.VisualStudio/Execution/AssemblyLocationAwareRunListener.cs
new file mode 100644
index 00000000..6bdd0e75
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Execution/AssemblyLocationAwareRunListener.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+
+namespace Machine.Specifications.Runner.VisualStudio.Execution
+{
+ public class AssemblyLocationAwareRunListener : ISpecificationRunListener
+ {
+ private readonly IEnumerable assemblies;
+
+ public AssemblyLocationAwareRunListener(IEnumerable assemblies)
+ {
+ this.assemblies = assemblies ?? throw new ArgumentNullException(nameof(assemblies));
+ }
+
+ public void OnAssemblyStart(AssemblyInfo assembly)
+ {
+ var loadedAssembly = assemblies.FirstOrDefault(a => a.GetName().Name.Equals(assembly.Name, StringComparison.OrdinalIgnoreCase));
+
+ Directory.SetCurrentDirectory(Path.GetDirectoryName(loadedAssembly.Location));
+ }
+
+ public void OnAssemblyEnd(AssemblyInfo assembly)
+ {
+ }
+
+ public void OnRunStart()
+ {
+ }
+
+ public void OnRunEnd()
+ {
+ }
+
+ public void OnContextStart(ContextInfo context)
+ {
+ }
+
+ public void OnContextEnd(ContextInfo context)
+ {
+ }
+
+ public void OnSpecificationStart(SpecificationInfo specification)
+ {
+ }
+
+ public void OnSpecificationEnd(SpecificationInfo specification, Result result)
+ {
+ }
+
+ public void OnFatalError(ExceptionResult exception)
+ {
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Execution/IFrameworkLogger.cs b/src/Machine.Specifications.Runner.VisualStudio/Execution/IFrameworkLogger.cs
new file mode 100644
index 00000000..36ab2734
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Execution/IFrameworkLogger.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Machine.Specifications.Runner.VisualStudio.Execution
+{
+ public interface IFrameworkLogger
+ {
+ void SendErrorMessage(string message, Exception exception);
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Execution/ISpecificationExecutor.cs b/src/Machine.Specifications.Runner.VisualStudio/Execution/ISpecificationExecutor.cs
new file mode 100644
index 00000000..77e538c6
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Execution/ISpecificationExecutor.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+
+namespace Machine.Specifications.Runner.VisualStudio.Execution
+{
+ public interface ISpecificationExecutor
+ {
+ void RunAssemblySpecifications(string assemblyPath, IEnumerable specifications, Uri adapterUri, IFrameworkHandle frameworkHandle);
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Execution/ISpecificationFilterProvider.cs b/src/Machine.Specifications.Runner.VisualStudio/Execution/ISpecificationFilterProvider.cs
new file mode 100644
index 00000000..d7e1735e
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Execution/ISpecificationFilterProvider.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+
+namespace Machine.Specifications.Runner.VisualStudio.Execution
+{
+ public interface ISpecificationFilterProvider
+ {
+ IEnumerable FilteredTests(IEnumerable testCases, IRunContext runContext, IFrameworkHandle frameworkHandle);
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Execution/ProxyAssemblySpecificationRunListener.cs b/src/Machine.Specifications.Runner.VisualStudio/Execution/ProxyAssemblySpecificationRunListener.cs
new file mode 100644
index 00000000..ee35c6d8
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Execution/ProxyAssemblySpecificationRunListener.cs
@@ -0,0 +1,160 @@
+using System;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
+
+namespace Machine.Specifications.Runner.VisualStudio.Execution
+{
+ public class ProxyAssemblySpecificationRunListener :
+#if NETFRAMEWORK
+ MarshalByRefObject,
+#endif
+ ISpecificationRunListener, IFrameworkLogger
+ {
+ private readonly IFrameworkHandle frameworkHandle;
+
+ private readonly string assemblyPath;
+
+ private readonly Uri executorUri;
+
+ private ContextInfo currentContext;
+
+ private RunStats currentRunStats;
+
+ public ProxyAssemblySpecificationRunListener(string assemblyPath, IFrameworkHandle frameworkHandle, Uri executorUri)
+ {
+ this.frameworkHandle = frameworkHandle ?? throw new ArgumentNullException(nameof(frameworkHandle));
+ this.assemblyPath = assemblyPath ?? throw new ArgumentNullException(nameof(assemblyPath));
+ this.executorUri = executorUri ?? throw new ArgumentNullException(nameof(executorUri));
+ }
+
+#if NETFRAMEWORK
+ [System.Security.SecurityCritical]
+ public override object InitializeLifetimeService()
+ {
+ return null;
+ }
+#endif
+
+ public void OnFatalError(ExceptionResult exception)
+ {
+ if (currentRunStats != null)
+ {
+ currentRunStats.Stop();
+ currentRunStats = null;
+ }
+
+ frameworkHandle.SendMessage(TestMessageLevel.Error,
+ "Machine Specifications Visual Studio Test Adapter - Fatal error while executing test." +
+ Environment.NewLine + exception);
+ }
+
+ public void OnSpecificationStart(SpecificationInfo specification)
+ {
+ var testCase = ConvertSpecificationToTestCase(specification);
+ frameworkHandle.RecordStart(testCase);
+ currentRunStats = new RunStats();
+ }
+
+ public void OnSpecificationEnd(SpecificationInfo specification, Result result)
+ {
+ if (currentRunStats != null)
+ {
+ currentRunStats.Stop();
+ }
+
+ var testCase = ConvertSpecificationToTestCase(specification);
+
+ frameworkHandle.RecordEnd(testCase, MapSpecificationResultToTestOutcome(result));
+ frameworkHandle.RecordResult(ConverResultToTestResult(testCase, result, currentRunStats));
+ }
+
+ public void OnContextStart(ContextInfo context)
+ {
+ currentContext = context;
+ }
+
+ public void OnContextEnd(ContextInfo context)
+ {
+ currentContext = null;
+ }
+
+ private TestCase ConvertSpecificationToTestCase(SpecificationInfo specification)
+ {
+ var vsTestId = specification.ToVisualStudioTestIdentifier(currentContext);
+
+ return new TestCase(vsTestId.FullyQualifiedName, executorUri, assemblyPath)
+ {
+ DisplayName = $"{currentContext?.TypeName}.{specification.FieldName}",
+ };
+ }
+
+ private static TestOutcome MapSpecificationResultToTestOutcome(Result result)
+ {
+ switch (result.Status)
+ {
+ case Status.Failing:
+ return TestOutcome.Failed;
+
+ case Status.Passing:
+ return TestOutcome.Passed;
+
+ case Status.Ignored:
+ return TestOutcome.Skipped;
+
+ case Status.NotImplemented:
+ return TestOutcome.NotFound;
+
+ default:
+ return TestOutcome.None;
+ }
+ }
+
+ private static TestResult ConverResultToTestResult(TestCase testCase, Result result, RunStats runStats)
+ {
+ var testResult = new TestResult(testCase)
+ {
+ ComputerName = Environment.MachineName,
+ Outcome = MapSpecificationResultToTestOutcome(result),
+ DisplayName = testCase.DisplayName
+ };
+
+ if (result.Exception != null)
+ {
+ testResult.ErrorMessage = result.Exception.Message;
+ testResult.ErrorStackTrace = result.Exception.ToString();
+ }
+
+ if (runStats != null)
+ {
+ testResult.StartTime = runStats.Start;
+ testResult.EndTime = runStats.End;
+ testResult.Duration = runStats.Duration;
+ }
+
+ return testResult;
+ }
+
+ public void OnAssemblyEnd(AssemblyInfo assembly)
+ {
+ }
+
+ public void OnAssemblyStart(AssemblyInfo assembly)
+ {
+ }
+
+ public void OnRunEnd()
+ {
+ }
+
+ public void OnRunStart()
+ {
+ }
+
+ public void SendErrorMessage(string message, Exception exception)
+ {
+ frameworkHandle?.SendMessage(TestMessageLevel.Error, message + Environment.NewLine + exception);
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Execution/RunStats.cs b/src/Machine.Specifications.Runner.VisualStudio/Execution/RunStats.cs
new file mode 100644
index 00000000..f669b178
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Execution/RunStats.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Diagnostics;
+
+namespace Machine.Specifications.Runner.VisualStudio.Execution
+{
+ public class RunStats
+ {
+ private readonly Stopwatch stopwatch = new Stopwatch();
+
+ public RunStats()
+ {
+ stopwatch.Start();
+ Start = DateTime.Now;
+ }
+
+ public DateTimeOffset Start { get; }
+
+ public DateTimeOffset End { get; private set; }
+
+ public TimeSpan Duration => stopwatch.Elapsed;
+
+ public void Stop()
+ {
+ stopwatch.Stop();
+
+ End = Start + stopwatch.Elapsed;
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Execution/SingleBehaviorTestRunListenerWrapper.cs b/src/Machine.Specifications.Runner.VisualStudio/Execution/SingleBehaviorTestRunListenerWrapper.cs
new file mode 100644
index 00000000..86649d7c
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Execution/SingleBehaviorTestRunListenerWrapper.cs
@@ -0,0 +1,82 @@
+using System;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+
+namespace Machine.Specifications.Runner.VisualStudio.Execution
+{
+ ///
+ /// The purpose of this class is to ignore everything, but a single specification's notifications.
+ /// Also because [Behavior] It's get reported as belonging to the Behavior class rather than test class
+ /// we need to map from one to the other for Visual Studio to capture the results.
+ ///
+ public class SingleBehaviorTestRunListenerWrapper : ISpecificationRunListener
+ {
+ private readonly ISpecificationRunListener runListener;
+
+ private readonly VisualStudioTestIdentifier listenFor;
+
+ private ContextInfo currentContext;
+
+ public SingleBehaviorTestRunListenerWrapper(ISpecificationRunListener runListener, VisualStudioTestIdentifier listenFor)
+ {
+ this.runListener = runListener ?? throw new ArgumentNullException(nameof(runListener));
+ this.listenFor = listenFor ?? throw new ArgumentNullException(nameof(listenFor));
+ }
+
+ public void OnContextEnd(ContextInfo context)
+ {
+ currentContext = null;
+ runListener.OnContextEnd(context);
+ }
+
+ public void OnContextStart(ContextInfo context)
+ {
+ currentContext = context;
+ runListener.OnContextStart(context);
+ }
+
+ public void OnSpecificationEnd(SpecificationInfo specification, Result result)
+ {
+ if (listenFor != null && !listenFor.Equals(specification.ToVisualStudioTestIdentifier(currentContext)))
+ {
+ return;
+ }
+
+ runListener.OnSpecificationEnd(specification, result);
+ }
+
+ public void OnSpecificationStart(SpecificationInfo specification)
+ {
+ if (listenFor != null && !listenFor.Equals(specification.ToVisualStudioTestIdentifier(currentContext)))
+ {
+ return;
+ }
+
+ runListener.OnSpecificationStart(specification);
+ }
+
+ public void OnAssemblyEnd(AssemblyInfo assembly)
+ {
+ runListener.OnAssemblyEnd(assembly);
+ }
+
+ public void OnAssemblyStart(AssemblyInfo assembly)
+ {
+ runListener.OnAssemblyStart(assembly);
+ }
+
+ public void OnFatalError(ExceptionResult exception)
+ {
+ runListener.OnFatalError(exception);
+ }
+
+ public void OnRunEnd()
+ {
+ runListener.OnRunEnd();
+ }
+
+ public void OnRunStart()
+ {
+ runListener.OnRunStart();
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Execution/SpecificationExecutor.cs b/src/Machine.Specifications.Runner.VisualStudio/Execution/SpecificationExecutor.cs
new file mode 100644
index 00000000..055468d3
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Execution/SpecificationExecutor.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+
+namespace Machine.Specifications.Runner.VisualStudio.Execution
+{
+ public class SpecificationExecutor : ISpecificationExecutor
+ {
+ public void RunAssemblySpecifications(string assemblyPath,
+ IEnumerable specifications,
+ Uri adapterUri,
+ IFrameworkHandle frameworkHandle)
+ {
+ assemblyPath = Path.GetFullPath(assemblyPath);
+
+#if NETFRAMEWORK
+ using (var scope = new IsolatedAppDomainExecutionScope(assemblyPath))
+ {
+ var executor = scope.CreateInstance();
+#else
+ var executor = new TestExecutor();
+#endif
+ var listener = new ProxyAssemblySpecificationRunListener(assemblyPath, frameworkHandle, adapterUri);
+
+ executor.RunTestsInAssembly(assemblyPath, specifications, listener);
+#if NETFRAMEWORK
+ }
+#endif
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Execution/SpecificationFilterProvider.cs b/src/Machine.Specifications.Runner.VisualStudio/Execution/SpecificationFilterProvider.cs
new file mode 100644
index 00000000..e076449d
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Execution/SpecificationFilterProvider.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Machine.Specifications.Model;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
+
+namespace Machine.Specifications.Runner.VisualStudio.Execution
+{
+ public class SpecificationFilterProvider : ISpecificationFilterProvider
+ {
+ private static readonly TestProperty TagProperty =
+ TestProperty.Register(nameof(Tag), nameof(Tag), typeof(string), typeof(TestCase));
+
+ private static readonly TestProperty SubjectProperty =
+ TestProperty.Register(nameof(Subject), nameof(Subject), typeof(string), typeof(TestCase));
+
+ private readonly Dictionary testCaseProperties = new Dictionary(StringComparer.OrdinalIgnoreCase)
+ {
+ [TestCaseProperties.FullyQualifiedName.Id] = TestCaseProperties.FullyQualifiedName,
+ [TestCaseProperties.DisplayName.Id] = TestCaseProperties.DisplayName
+ };
+
+ private readonly Dictionary traitProperties = new Dictionary(StringComparer.OrdinalIgnoreCase)
+ {
+ [TagProperty.Id] = TagProperty,
+ [SubjectProperty.Id] = SubjectProperty
+ };
+
+ private readonly string[] supportedProperties;
+
+ public SpecificationFilterProvider()
+ {
+ supportedProperties = testCaseProperties.Keys
+ .Concat(traitProperties.Keys)
+ .ToArray();
+ }
+
+ public IEnumerable FilteredTests(IEnumerable testCases, IRunContext runContext, IFrameworkHandle handle)
+ {
+ var filterExpression = runContext.GetTestCaseFilter(supportedProperties, propertyName =>
+ {
+ if (testCaseProperties.TryGetValue(propertyName, out var testProperty))
+ {
+ return testProperty;
+ }
+ if (traitProperties.TryGetValue(propertyName, out var traitProperty))
+ {
+ return traitProperty;
+ }
+ return null;
+ });
+
+ handle?.SendMessage(TestMessageLevel.Informational, $"Machine Specifications Visual Studio Test Adapter - Filter property set '{filterExpression?.TestCaseFilterValue}'");
+
+ if (filterExpression == null)
+ {
+ return testCases;
+ }
+
+ var filteredTests = testCases
+ .Where(x => filterExpression.MatchTestCase(x, propertyName => GetPropertyValue(propertyName, x)));
+
+ return filteredTests;
+ }
+
+ private object GetPropertyValue(string propertyName, TestObject testCase)
+ {
+ if (testCaseProperties.TryGetValue(propertyName, out var testProperty))
+ {
+ if (testCase.Properties.Contains(testProperty))
+ {
+ return testCase.GetPropertyValue(testProperty);
+ }
+ }
+
+ if (traitProperties.TryGetValue(propertyName, out var traitProperty))
+ {
+ var val = TraitContains(testCase, traitProperty.Id);
+
+ if (val.Length == 1)
+ {
+ return val[0];
+ }
+
+ if (val.Length > 1)
+ {
+ return val;
+ }
+ }
+
+ return null;
+ }
+
+ private static string[] TraitContains(TestObject testCase, string traitName)
+ {
+ return testCase?.Traits?
+ .Where(x => x.Name == traitName)
+ .Select(x => x.Value)
+ .ToArray();
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Execution/TestExecutor.cs b/src/Machine.Specifications.Runner.VisualStudio/Execution/TestExecutor.cs
new file mode 100644
index 00000000..9958339c
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Execution/TestExecutor.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Machine.Specifications.Runner.Impl;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+
+namespace Machine.Specifications.Runner.VisualStudio.Execution
+{
+ public class TestExecutor
+#if NETFRAMEWORK
+ : MarshalByRefObject
+#endif
+ {
+#if NETFRAMEWORK
+ [System.Security.SecurityCritical]
+ public override object InitializeLifetimeService()
+ {
+ return null;
+ }
+#endif
+
+ private DefaultRunner CreateRunner(Assembly assembly, ISpecificationRunListener specificationRunListener)
+ {
+ var listener = new AggregateRunListener(new[] {
+ specificationRunListener,
+ new AssemblyLocationAwareRunListener(new[] { assembly })
+ });
+
+ return new DefaultRunner(listener, RunOptions.Default);
+ }
+
+ public void RunTestsInAssembly(string pathToAssembly, IEnumerable specsToRun, ISpecificationRunListener specificationRunListener)
+ {
+ DefaultRunner mspecRunner = null;
+ Assembly assemblyToRun = null;
+
+ try
+ {
+ assemblyToRun = AssemblyHelper.Load(pathToAssembly);
+ mspecRunner = CreateRunner(assemblyToRun, specificationRunListener);
+
+ var specsByContext = specsToRun.GroupBy(x => x.ContainerTypeFullName);
+
+ mspecRunner.StartRun(assemblyToRun);
+
+ foreach (var specs in specsByContext)
+ {
+ var fields = specs.Select(x => x.FieldName);
+
+ mspecRunner.RunType(assemblyToRun, assemblyToRun.GetType(specs.Key), fields.ToArray());
+ }
+ }
+ catch (Exception e)
+ {
+ specificationRunListener.OnFatalError(new ExceptionResult(e));
+ }
+ finally
+ {
+ try
+ {
+ if (mspecRunner != null && assemblyToRun != null)
+ {
+ mspecRunner.EndRun(assemblyToRun);
+ }
+ }
+ catch (Exception exception)
+ {
+ try
+ {
+ var frameworkLogger = specificationRunListener as IFrameworkLogger;
+
+ frameworkLogger?.SendErrorMessage("Machine Specifications Visual Studio Test Adapter - Error Ending Test Run.", exception);
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Helpers/AssemblyHelper.cs b/src/Machine.Specifications.Runner.VisualStudio/Helpers/AssemblyHelper.cs
new file mode 100644
index 00000000..9712a802
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Helpers/AssemblyHelper.cs
@@ -0,0 +1,25 @@
+using System;
+using System.IO;
+using System.Reflection;
+
+namespace Machine.Specifications.Runner.VisualStudio.Helpers
+{
+ internal static class AssemblyHelper
+ {
+ public static Assembly Load(string path)
+ {
+ try
+ {
+#if NETCOREAPP
+ return Assembly.Load(new AssemblyName(Path.GetFileNameWithoutExtension(path)));
+#else
+ return Assembly.LoadFile(path);
+#endif
+ } catch (Exception e)
+ {
+ Console.WriteLine(e);
+ throw;
+ }
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Helpers/IsolatedAppDomainExecutionScope.cs b/src/Machine.Specifications.Runner.VisualStudio/Helpers/IsolatedAppDomainExecutionScope.cs
new file mode 100644
index 00000000..a622c547
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Helpers/IsolatedAppDomainExecutionScope.cs
@@ -0,0 +1,148 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Threading;
+
+namespace Machine.Specifications.Runner.VisualStudio.Helpers
+{
+#if NETFRAMEWORK
+ public class IsolatedAppDomainExecutionScope : IDisposable
+ where T : MarshalByRefObject, new()
+ {
+ private readonly string assemblyPath;
+
+ private readonly string appName = typeof(IsolatedAppDomainExecutionScope<>).Assembly.GetName().Name;
+
+ private AppDomain appDomain;
+
+ public IsolatedAppDomainExecutionScope(string assemblyPath)
+ {
+ if (string.IsNullOrEmpty(assemblyPath))
+ {
+ throw new ArgumentException($"{nameof(assemblyPath)} is null or empty.", nameof(assemblyPath));
+ }
+
+ this.assemblyPath = assemblyPath;
+ }
+
+ public T CreateInstance()
+ {
+ if (appDomain == null)
+ {
+ // Because we need to copy files around - we create a global cross-process mutex here to avoid multi-process race conditions
+ // in the case where both of those are true:
+ // 1. VSTest is told to run tests in parallel, so it spawns multiple processes
+ // 2. There are multiple test assemblies in the same directory
+ using (var mutex = new Mutex(false, $"{appName}_{Path.GetDirectoryName(assemblyPath).Replace(Path.DirectorySeparatorChar, '_')}"))
+ {
+ try
+ {
+ mutex.WaitOne(TimeSpan.FromMinutes(1));
+ }
+ catch (AbandonedMutexException)
+ {
+ }
+
+ try
+ {
+ appDomain = CreateAppDomain(assemblyPath, appName);
+ }
+ finally
+ {
+ try
+ {
+ mutex.ReleaseMutex();
+ }
+ catch
+ {
+ }
+ }
+ }
+ }
+
+ return (T)appDomain.CreateInstanceAndUnwrap(typeof(T).Assembly.FullName, typeof(T).FullName);
+ }
+
+
+ private static AppDomain CreateAppDomain(string assemblyPath, string appName)
+ {
+ // This is needed in the following two scenarios, so that the target test dll and its dependencies are loaded correctly:
+ //
+ // 1. pre-.NET Standard (old) .csproj and Visual Studio IDE Test Explorer run
+ // 2. vstest.console.exe run against .dll which is not in the build output folder (e.g. packaged build artifact)
+ //
+ CopyRequiredRuntimeDependencies(new[]
+ {
+ typeof(IsolatedAppDomainExecutionScope<>).Assembly,
+ typeof(MetadataReaderProvider).Assembly
+ }, Path.GetDirectoryName(assemblyPath));
+
+ var setup = new AppDomainSetup();
+ setup.ApplicationName = appName;
+ setup.ShadowCopyFiles = "true";
+ setup.ApplicationBase = setup.PrivateBinPath = Path.GetDirectoryName(assemblyPath);
+ setup.CachePath = Path.Combine(Path.GetTempPath(), appName, Guid.NewGuid().ToString());
+ setup.ConfigurationFile = Path.Combine(Path.GetDirectoryName(assemblyPath), (Path.GetFileName(assemblyPath) + ".config"));
+
+ return AppDomain.CreateDomain($"{appName}.dll", null, setup);
+ }
+
+ private static void CopyRequiredRuntimeDependencies(IEnumerable assemblies, string destination)
+ {
+ foreach (Assembly assembly in assemblies)
+ {
+ var sourceAssemblyFile = assembly.Location;
+ var destinationAssemblyFile = Path.Combine(destination, Path.GetFileName(sourceAssemblyFile));
+
+ // file doesn't exist or is older
+ if (!File.Exists(destinationAssemblyFile) || File.GetLastWriteTimeUtc(sourceAssemblyFile) > File.GetLastWriteTimeUtc(destinationAssemblyFile))
+ {
+ CopyWithoutLockingSourceFile(sourceAssemblyFile, destinationAssemblyFile);
+ }
+ }
+ }
+
+ private static void CopyWithoutLockingSourceFile(string sourceFile, string destinationFile)
+ {
+ const int bufferSize = 10 * 1024;
+
+ using (var inputFile = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize))
+ using (var outputFile = new FileStream(destinationFile, FileMode.Create, FileAccess.ReadWrite, FileShare.None, bufferSize))
+ {
+ var buffer = new byte[bufferSize];
+ int bytes;
+
+ while ((bytes = inputFile.Read(buffer, 0, buffer.Length)) > 0)
+ {
+ outputFile.Write(buffer, 0, bytes);
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ if (appDomain != null)
+ {
+ try
+ {
+ var cacheDirectory = appDomain.SetupInformation.CachePath;
+
+ AppDomain.Unload(appDomain);
+ appDomain = null;
+
+ if (Directory.Exists(cacheDirectory))
+ {
+ Directory.Delete(cacheDirectory, true);
+ }
+ }
+ catch
+ {
+ // TODO: Logging here
+ }
+ }
+ }
+ }
+#endif
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Helpers/NamingConversionExtensions.cs b/src/Machine.Specifications.Runner.VisualStudio/Helpers/NamingConversionExtensions.cs
new file mode 100644
index 00000000..86d4dc6a
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Helpers/NamingConversionExtensions.cs
@@ -0,0 +1,30 @@
+using System.Globalization;
+using Machine.Specifications.Model;
+using Machine.Specifications.Runner.VisualStudio.Discovery;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+
+namespace Machine.Specifications.Runner.VisualStudio.Helpers
+{
+ public static class NamingConversionExtensions
+ {
+ public static VisualStudioTestIdentifier ToVisualStudioTestIdentifier(this SpecificationInfo specification, ContextInfo context)
+ {
+ return new VisualStudioTestIdentifier(string.Format(CultureInfo.InvariantCulture, "{0}::{1}", context?.TypeName ?? specification.ContainingType, specification.FieldName));
+ }
+
+ public static VisualStudioTestIdentifier ToVisualStudioTestIdentifier(this SpecTestCase specification)
+ {
+ return new VisualStudioTestIdentifier(string.Format(CultureInfo.InvariantCulture, "{0}::{1}", specification.ContextFullType, specification.SpecificationName));
+ }
+
+ public static VisualStudioTestIdentifier ToVisualStudioTestIdentifier(this TestCase testCase)
+ {
+ return new VisualStudioTestIdentifier(testCase.FullyQualifiedName);
+ }
+
+ public static VisualStudioTestIdentifier ToVisualStudioTestIdentifier(this Specification specification, Context context)
+ {
+ return new VisualStudioTestIdentifier(string.Format(CultureInfo.InvariantCulture, "{0}::{1}", context.Type.FullName, specification.FieldInfo.Name));
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Helpers/SpecTestHelper.cs b/src/Machine.Specifications.Runner.VisualStudio/Helpers/SpecTestHelper.cs
new file mode 100644
index 00000000..6b571dd8
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Helpers/SpecTestHelper.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Diagnostics;
+using Machine.Specifications.Runner.VisualStudio.Discovery;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+
+namespace Machine.Specifications.Runner.VisualStudio.Helpers
+{
+ public static class SpecTestHelper
+ {
+ public static TestCase GetTestCaseFromMspecTestCase(string source, SpecTestCase mspecTestCase, Uri testRunnerUri)
+ {
+ var vsTest = mspecTestCase.ToVisualStudioTestIdentifier();
+
+ var testCase = new TestCase(vsTest.FullyQualifiedName, testRunnerUri, source)
+ {
+ DisplayName = $"{mspecTestCase.ContextDisplayName} it {mspecTestCase.SpecificationDisplayName}",
+ CodeFilePath = mspecTestCase.CodeFilePath,
+ LineNumber = mspecTestCase.LineNumber
+ };
+
+ var classTrait = new Trait("ClassName", mspecTestCase.ClassName);
+ var subjectTrait = new Trait("Subject", string.IsNullOrEmpty(mspecTestCase.Subject) ? "No Subject" : mspecTestCase.Subject);
+
+ testCase.Traits.Add(classTrait);
+ testCase.Traits.Add(subjectTrait);
+
+ if (mspecTestCase.Tags != null)
+ {
+ foreach (var tag in mspecTestCase.Tags)
+ {
+ if (!string.IsNullOrEmpty(tag))
+ {
+ var tagTrait = new Trait("Tag", tag);
+ testCase.Traits.Add(tagTrait);
+ }
+ }
+ }
+
+ if (!string.IsNullOrEmpty(mspecTestCase.BehaviorFieldName))
+ {
+ testCase.Traits.Add(new Trait("BehaviorField", mspecTestCase.BehaviorFieldName));
+ }
+
+ if (!string.IsNullOrEmpty(mspecTestCase.BehaviorFieldType))
+ {
+ testCase.Traits.Add(new Trait("BehaviorType", mspecTestCase.BehaviorFieldType));
+ }
+
+ Debug.WriteLine($"TestCase {testCase.FullyQualifiedName}");
+
+ return testCase;
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Helpers/VisualStudioTestIdentifier.cs b/src/Machine.Specifications.Runner.VisualStudio/Helpers/VisualStudioTestIdentifier.cs
new file mode 100644
index 00000000..39fb4919
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Helpers/VisualStudioTestIdentifier.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Globalization;
+
+namespace Machine.Specifications.Runner.VisualStudio.Helpers
+{
+#if NETFRAMEWORK
+ [Serializable]
+#endif
+ public class VisualStudioTestIdentifier
+ {
+ public VisualStudioTestIdentifier()
+ {
+ }
+
+ public VisualStudioTestIdentifier(string containerTypeFullName, string fieldName)
+ : this(string.Format(CultureInfo.InvariantCulture, "{0}::{1}", containerTypeFullName, fieldName))
+ {
+ }
+
+ public VisualStudioTestIdentifier(string fullyQualifiedName)
+ {
+ FullyQualifiedName = fullyQualifiedName;
+ }
+
+ public string FullyQualifiedName { get; private set; }
+
+ public string FieldName
+ {
+ get
+ {
+ return FullyQualifiedName.Split(new[] { "::" }, StringSplitOptions.RemoveEmptyEntries)[1];
+ }
+ }
+
+ public string ContainerTypeFullName
+ {
+ get
+ {
+ return FullyQualifiedName.Split(new[] { "::" }, StringSplitOptions.RemoveEmptyEntries)[0];
+ }
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is VisualStudioTestIdentifier test)
+ {
+ return FullyQualifiedName.Equals(test.FullyQualifiedName, StringComparison.Ordinal);
+ }
+
+ return base.Equals(obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return FullyQualifiedName.GetHashCode();
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Machine.Specifications.Runner.VisualStudio.csproj b/src/Machine.Specifications.Runner.VisualStudio/Machine.Specifications.Runner.VisualStudio.csproj
new file mode 100644
index 00000000..8d9f6653
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Machine.Specifications.Runner.VisualStudio.csproj
@@ -0,0 +1,41 @@
+
+
+
+ net472;net6.0
+ Machine.Specifications.Runner.VisualStudio
+ Machine.Specifications.Runner.VisualStudio.TestAdapter
+ NU5127,NU5128
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(TargetsForTfmSpecificContentInPackage);NetCorePackageItems;NetFrameworkPackageItems
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Machine.Specifications.Runner.VisualStudio.props b/src/Machine.Specifications.Runner.VisualStudio/Machine.Specifications.Runner.VisualStudio.props
new file mode 100644
index 00000000..425b3009
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Machine.Specifications.Runner.VisualStudio.props
@@ -0,0 +1,10 @@
+
+
+
+
+ Machine.Specifications.Runner.VisualStudio.TestAdapter.dll
+ PreserveNewest
+ False
+
+
+
diff --git a/src/Machine.Specifications.Runner.VisualStudio/MspecTestDiscoverer.cs b/src/Machine.Specifications.Runner.VisualStudio/MspecTestDiscoverer.cs
new file mode 100644
index 00000000..01059e05
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/MspecTestDiscoverer.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Machine.Specifications.Runner.VisualStudio.Discovery;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
+
+namespace Machine.Specifications.Runner.VisualStudio
+{
+ public class MspecTestDiscoverer
+ {
+ private readonly ISpecificationDiscoverer discoverer;
+
+ public MspecTestDiscoverer(ISpecificationDiscoverer discoverer)
+ {
+ this.discoverer = discoverer;
+ }
+
+ public void DiscoverTests(IEnumerable sources, IMessageLogger logger, ITestCaseDiscoverySink discoverySink)
+ {
+ DiscoverTests(sources, logger, discoverySink.SendTestCase);
+ }
+
+ public void DiscoverTests(IEnumerable sources, IMessageLogger logger, Action discoverySinkAction)
+ {
+ logger.SendMessage(TestMessageLevel.Informational, "Machine Specifications Visual Studio Test Adapter - Discovering Specifications.");
+
+ var discoveredSpecCount = 0;
+ var sourcesWithSpecs = 0;
+
+ var sourcesArray = sources.Distinct().ToArray();
+
+ foreach (var assemblyPath in sourcesArray)
+ {
+ try
+ {
+#if NETFRAMEWORK
+ if (!File.Exists(Path.Combine(Path.GetDirectoryName(Path.GetFullPath(assemblyPath)), "Machine.Specifications.dll")))
+ continue;
+#endif
+
+ sourcesWithSpecs++;
+
+ logger.SendMessage(TestMessageLevel.Informational, $"Machine Specifications Visual Studio Test Adapter - Discovering...looking in {assemblyPath}");
+
+ var specs = discoverer.DiscoverSpecs(assemblyPath)
+ .Select(spec => SpecTestHelper.GetTestCaseFromMspecTestCase(assemblyPath, spec, MspecTestRunner.Uri))
+ .ToList();
+
+ foreach (var discoveredTest in specs)
+ {
+ discoveredSpecCount++;
+ discoverySinkAction(discoveredTest);
+ }
+ }
+ catch (Exception discoverException)
+ {
+ logger.SendMessage(TestMessageLevel.Error, $"Machine Specifications Visual Studio Test Adapter - Error while discovering specifications in assembly {assemblyPath}." + Environment.NewLine + discoverException);
+ }
+ }
+
+ logger.SendMessage(TestMessageLevel.Informational, $"Machine Specifications Visual Studio Test Adapter - Discovery Complete - {discoveredSpecCount} specifications in {sourcesWithSpecs} of {sourcesArray.Length} assemblies scanned.");
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/MspecTestExecutor.cs b/src/Machine.Specifications.Runner.VisualStudio/MspecTestExecutor.cs
new file mode 100644
index 00000000..df400bde
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/MspecTestExecutor.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Machine.Specifications.Runner.VisualStudio.Execution;
+using Machine.Specifications.Runner.VisualStudio.Helpers;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
+
+namespace Machine.Specifications.Runner.VisualStudio
+{
+ public class MspecTestExecutor
+ {
+ private readonly ISpecificationExecutor executor;
+
+ private readonly MspecTestDiscoverer discover;
+
+ private readonly ISpecificationFilterProvider specificationFilterProvider;
+
+ public MspecTestExecutor(ISpecificationExecutor executor, MspecTestDiscoverer discover, ISpecificationFilterProvider specificationFilterProvider)
+ {
+ this.executor = executor;
+ this.discover = discover;
+ this.specificationFilterProvider = specificationFilterProvider;
+ }
+
+ public void RunTests(IEnumerable sources, IRunContext runContext, IFrameworkHandle frameworkHandle)
+ {
+ frameworkHandle.SendMessage(TestMessageLevel.Informational, "Machine Specifications Visual Studio Test Adapter - Executing Source Specifications.");
+
+ var testsToRun = new List();
+
+ DiscoverTests(sources, frameworkHandle, testsToRun);
+ RunTests(testsToRun, runContext, frameworkHandle);
+
+ frameworkHandle.SendMessage(TestMessageLevel.Informational, "Machine Specifications Visual Studio Test Adapter - Executing Source Specifications Complete.");
+ }
+
+ public void RunTests(IEnumerable tests, IRunContext runContext, IFrameworkHandle frameworkHandle)
+ {
+ frameworkHandle.SendMessage(TestMessageLevel.Informational, "Machine Specifications Visual Studio Test Adapter - Executing Test Specifications.");
+
+ var totalSpecCount = 0;
+ var executedSpecCount = 0;
+ var currentAssembly = string.Empty;
+
+ try
+ {
+ var testCases = tests.ToArray();
+
+ foreach (var grouping in testCases.GroupBy(x => x.Source))
+ {
+ currentAssembly = grouping.Key;
+ totalSpecCount += grouping.Count();
+
+ var filteredTests = specificationFilterProvider.FilteredTests(grouping.AsEnumerable(), runContext, frameworkHandle);
+
+ var testsToRun = filteredTests
+ .Select(test => test.ToVisualStudioTestIdentifier())
+ .ToArray();
+
+ frameworkHandle.SendMessage(TestMessageLevel.Informational, $"Machine Specifications Visual Studio Test Adapter - Executing {testsToRun.Length} tests in '{currentAssembly}'.");
+
+ executor.RunAssemblySpecifications(grouping.Key, testsToRun, MspecTestRunner.Uri, frameworkHandle);
+
+ executedSpecCount += testsToRun.Length;
+ }
+
+ frameworkHandle.SendMessage(TestMessageLevel.Informational, $"Machine Specifications Visual Studio Test Adapter - Execution Complete - {executedSpecCount} of {totalSpecCount} specifications in {testCases.GroupBy(x => x.Source).Count()} assemblies.");
+ }
+ catch (Exception exception)
+ {
+ frameworkHandle.SendMessage(TestMessageLevel.Error, $"Machine Specifications Visual Studio Test Adapter - Error while executing specifications in assembly '{currentAssembly}'." + Environment.NewLine + exception);
+ }
+ }
+
+ private void DiscoverTests(IEnumerable sources, IMessageLogger logger, List testsToRun)
+ {
+ discover.DiscoverTests(sources, logger, testsToRun.Add);
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/MspecTestRunner.cs b/src/Machine.Specifications.Runner.VisualStudio/MspecTestRunner.cs
new file mode 100644
index 00000000..844c0b6f
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/MspecTestRunner.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using Machine.Specifications.Runner.VisualStudio.Discovery;
+using Machine.Specifications.Runner.VisualStudio.Execution;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
+
+namespace Machine.Specifications.Runner.VisualStudio
+{
+ [FileExtension(".exe")]
+ [FileExtension(".dll")]
+ [ExtensionUri(ExecutorUri)]
+ [DefaultExecutorUri(ExecutorUri)]
+ public class MspecTestRunner : ITestDiscoverer, ITestExecutor
+ {
+ private const string ExecutorUri = "executor://machine.vstestadapter";
+
+ public static readonly Uri Uri = new Uri(ExecutorUri);
+
+ private readonly MspecTestDiscoverer testDiscoverer;
+
+ private readonly MspecTestExecutor testExecutor;
+
+ public MspecTestRunner()
+ : this(new BuiltInSpecificationDiscoverer(), new SpecificationExecutor(), new SpecificationFilterProvider())
+ {
+ }
+
+ public MspecTestRunner(ISpecificationDiscoverer discoverer, ISpecificationExecutor executor, ISpecificationFilterProvider specificationFilterProvider)
+ {
+ testDiscoverer = new MspecTestDiscoverer(discoverer);
+ testExecutor = new MspecTestExecutor(executor, testDiscoverer, specificationFilterProvider);
+ }
+
+ public void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink)
+ {
+ testDiscoverer.DiscoverTests(sources, logger, discoverySink);
+ }
+
+ public void RunTests(IEnumerable tests, IRunContext runContext, IFrameworkHandle frameworkHandle)
+ {
+ testExecutor.RunTests(tests, runContext, frameworkHandle);
+ }
+
+ public void RunTests(IEnumerable sources, IRunContext runContext, IFrameworkHandle frameworkHandle)
+ {
+ testExecutor.RunTests(sources, runContext, frameworkHandle);
+ }
+
+ public void Cancel()
+ {
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Navigation/INavigationSession.cs b/src/Machine.Specifications.Runner.VisualStudio/Navigation/INavigationSession.cs
new file mode 100644
index 00000000..c7297541
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Navigation/INavigationSession.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Machine.Specifications.Runner.VisualStudio.Navigation
+{
+ public interface INavigationSession : IDisposable
+ {
+ NavigationData GetNavigationData(string typeName, string fieldName);
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Navigation/NavigationData.cs b/src/Machine.Specifications.Runner.VisualStudio/Navigation/NavigationData.cs
new file mode 100644
index 00000000..bf61fbe8
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Navigation/NavigationData.cs
@@ -0,0 +1,17 @@
+namespace Machine.Specifications.Runner.VisualStudio.Navigation
+{
+ public class NavigationData
+ {
+ public static NavigationData Unknown { get; } = new NavigationData(null, 0);
+
+ public NavigationData(string codeFile, int lineNumber)
+ {
+ CodeFile = codeFile;
+ LineNumber = lineNumber;
+ }
+
+ public string CodeFile { get; }
+
+ public int LineNumber { get; }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Navigation/NavigationSession.cs b/src/Machine.Specifications.Runner.VisualStudio/Navigation/NavigationSession.cs
new file mode 100644
index 00000000..4fe24c69
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Navigation/NavigationSession.cs
@@ -0,0 +1,50 @@
+using System.Linq;
+using System.Reflection.Emit;
+using Machine.Specifications.Runner.VisualStudio.Reflection;
+
+namespace Machine.Specifications.Runner.VisualStudio.Navigation
+{
+ public class NavigationSession : INavigationSession
+ {
+ private readonly AssemblyData assembly;
+
+ public NavigationSession(string assemblyPath)
+ {
+ assembly = AssemblyData.Read(assemblyPath);
+ }
+
+ public NavigationData GetNavigationData(string typeName, string fieldName)
+ {
+ var type = assembly.Types.FirstOrDefault(x => x.TypeName == typeName);
+ var method = type?.Constructors.FirstOrDefault();
+
+ if (method == null)
+ {
+ return NavigationData.Unknown;
+ }
+
+ var instruction = method.Instructions
+ .Where(x => x.OperandType == OperandType.InlineField)
+ .FirstOrDefault(x => x.Name == fieldName);
+
+ while (instruction != null)
+ {
+ var sequencePoint = method.GetSequencePoint(instruction);
+
+ if (sequencePoint != null && !sequencePoint.IsHidden)
+ {
+ return new NavigationData(sequencePoint.FileName, sequencePoint.StartLine);
+ }
+
+ instruction = instruction.Previous;
+ }
+
+ return NavigationData.Unknown;
+ }
+
+ public void Dispose()
+ {
+ assembly.Dispose();
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Reflection/AssemblyData.cs b/src/Machine.Specifications.Runner.VisualStudio/Reflection/AssemblyData.cs
new file mode 100644
index 00000000..7aadc489
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Reflection/AssemblyData.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Reflection.Metadata;
+using System.Reflection.PortableExecutable;
+
+namespace Machine.Specifications.Runner.VisualStudio.Reflection
+{
+ public class AssemblyData : IDisposable
+ {
+ private readonly PEReader reader;
+
+ private readonly MetadataReader metadata;
+
+ private readonly SymbolReader symbolReader;
+
+ private readonly object sync = new object();
+
+ private ReadOnlyCollection types;
+
+ private AssemblyData(string assembly)
+ {
+ reader = new PEReader(File.OpenRead(assembly));
+ metadata = reader.GetMetadataReader();
+ symbolReader = new SymbolReader(assembly);
+ }
+
+ public static AssemblyData Read(string assembly)
+ {
+ return new AssemblyData(assembly);
+ }
+
+ public IReadOnlyCollection Types
+ {
+ get
+ {
+ if (types != null)
+ {
+ return types;
+ }
+
+ lock (sync)
+ {
+ types = ReadTypes().AsReadOnly();
+ }
+
+ return types;
+ }
+ }
+
+ public void Dispose()
+ {
+ reader.Dispose();
+ }
+
+ private List ReadTypes()
+ {
+ var values = new List();
+
+ foreach (var typeHandle in metadata.TypeDefinitions)
+ {
+ ReadType(values, typeHandle);
+ }
+
+ return values;
+ }
+
+ private void ReadType(List values, TypeDefinitionHandle typeHandle, string namespaceName = null)
+ {
+ var typeDefinition = metadata.GetTypeDefinition(typeHandle);
+
+ var typeNamespace = string.IsNullOrEmpty(namespaceName)
+ ? metadata.GetString(typeDefinition.Namespace)
+ : namespaceName;
+
+ var typeName = string.IsNullOrEmpty(namespaceName)
+ ? $"{typeNamespace}.{metadata.GetString(typeDefinition.Name)}"
+ : $"{typeNamespace}+{metadata.GetString(typeDefinition.Name)}";
+
+ values.Add(new TypeData(typeName, reader, metadata, symbolReader, typeDefinition));
+
+ foreach (var nestedTypeHandle in typeDefinition.GetNestedTypes())
+ {
+ ReadType(values, nestedTypeHandle, typeName);
+ }
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Reflection/CodeReader.cs b/src/Machine.Specifications.Runner.VisualStudio/Reflection/CodeReader.cs
new file mode 100644
index 00000000..0fc28f8f
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Reflection/CodeReader.cs
@@ -0,0 +1,154 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection.Emit;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+
+namespace Machine.Specifications.Runner.VisualStudio.Reflection
+{
+ public class CodeReader
+ {
+ private static readonly OperandType[] OperandTypes = Enumerable.Repeat((OperandType) 0xff, 0x11f).ToArray();
+
+ private static readonly string[] OperandNames = new string[0x11f];
+
+ static CodeReader()
+ {
+ foreach (var field in typeof(OpCodes).GetFields())
+ {
+ var opCode = (OpCode) field.GetValue(null);
+ var index = (ushort) (((opCode.Value & 0x200) >> 1) | opCode.Value & 0xff);
+
+ OperandTypes[index] = opCode.OperandType;
+ OperandNames[index] = opCode.Name;
+ }
+ }
+
+ public IEnumerable GetInstructions(MetadataReader reader, ref BlobReader blob)
+ {
+ var instructions = new List();
+
+ InstructionData previous = null;
+
+ while (blob.RemainingBytes > 0)
+ {
+ var offset = blob.Offset;
+
+ var opCode = ReadOpCode(ref blob);
+ var opCodeName = GetDisplayName(opCode);
+ var operandType = GetOperandType(opCode);
+
+ var name = operandType != OperandType.InlineNone
+ ? ReadOperand(reader, ref blob, operandType)
+ : null;
+
+ previous = new InstructionData(opCode, opCodeName, operandType, offset, previous, name);
+
+ instructions.Add(previous);
+ }
+
+ return instructions;
+ }
+
+ private string ReadOperand(MetadataReader reader, ref BlobReader blob, OperandType operandType)
+ {
+ var name = string.Empty;
+
+ switch (operandType)
+ {
+ case OperandType.InlineI8:
+ case OperandType.InlineR:
+ blob.Offset += 8;
+ break;
+
+ case OperandType.InlineBrTarget:
+ case OperandType.InlineI:
+ case OperandType.InlineSig:
+ case OperandType.InlineString:
+ case OperandType.InlineTok:
+ case OperandType.InlineType:
+ case OperandType.ShortInlineR:
+ blob.Offset += 4;
+ break;
+
+ case OperandType.InlineField:
+ case OperandType.InlineMethod:
+ var handle = MetadataTokens.EntityHandle(blob.ReadInt32());
+
+ name = LookupToken(reader, handle);
+ break;
+
+ case OperandType.InlineSwitch:
+ var length = blob.ReadInt32();
+ blob.Offset += length * 4;
+ break;
+
+ case OperandType.InlineVar:
+ blob.Offset += 2;
+ break;
+
+ case OperandType.ShortInlineVar:
+ case OperandType.ShortInlineBrTarget:
+ case OperandType.ShortInlineI:
+ blob.Offset++;
+ break;
+ }
+
+ return name;
+ }
+
+ private string LookupToken(MetadataReader reader, EntityHandle handle)
+ {
+ if (handle.Kind == HandleKind.FieldDefinition)
+ {
+ var field = reader.GetFieldDefinition((FieldDefinitionHandle) handle);
+
+ return reader.GetString(field.Name);
+ }
+
+ if (handle.Kind == HandleKind.MethodDefinition)
+ {
+ var method = reader.GetMethodDefinition((MethodDefinitionHandle) handle);
+
+ return reader.GetString(method.Name);
+ }
+
+ return string.Empty;
+ }
+
+ private ILOpCode ReadOpCode(ref BlobReader blob)
+ {
+ var opCodeByte = blob.ReadByte();
+
+ var value = opCodeByte == 0xfe
+ ? 0xfe00 + blob.ReadByte()
+ : opCodeByte;
+
+ return (ILOpCode) value;
+ }
+
+ private OperandType GetOperandType(ILOpCode opCode)
+ {
+ var index = (ushort) ((((int) opCode & 0x200) >> 1) | ((int) opCode & 0xff));
+
+ if (index >= OperandTypes.Length)
+ {
+ return (OperandType) 0xff;
+ }
+
+ return OperandTypes[index];
+ }
+
+ private string GetDisplayName(ILOpCode opCode)
+ {
+ var index = (ushort) ((((int) opCode & 0x200) >> 1) | ((int) opCode & 0xff));
+
+ if (index >= OperandNames.Length)
+ {
+ return string.Empty;
+ }
+
+ return OperandNames[index];
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Reflection/InstructionData.cs b/src/Machine.Specifications.Runner.VisualStudio/Reflection/InstructionData.cs
new file mode 100644
index 00000000..6acc2727
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Reflection/InstructionData.cs
@@ -0,0 +1,65 @@
+using System.Reflection.Emit;
+using System.Reflection.Metadata;
+using System.Text;
+
+namespace Machine.Specifications.Runner.VisualStudio.Reflection
+{
+ public class InstructionData
+ {
+ public InstructionData(ILOpCode opCode, string opCodeName, OperandType operandType, int offset, InstructionData previous, string name = null)
+ {
+ OpCode = opCode;
+ OpCodeName = opCodeName;
+ OperandType = operandType;
+ Offset = offset;
+ Previous = previous;
+ Name = name;
+ }
+
+ public ILOpCode OpCode { get; }
+
+ public string OpCodeName { get; }
+
+ public OperandType OperandType { get; }
+
+ public string Name { get; }
+
+ public int Offset { get; }
+
+ public InstructionData Previous { get; }
+
+ public override string ToString()
+ {
+ var value = new StringBuilder();
+
+ AppendLabel(value);
+
+ value.Append(": ");
+ value.Append(OpCode);
+
+ if (!string.IsNullOrEmpty(Name))
+ {
+ value.Append(" ");
+
+ if (OperandType == OperandType.InlineString)
+ {
+ value.Append("\"");
+ value.Append(Name);
+ value.Append("\"");
+ }
+ else
+ {
+ value.Append(Name);
+ }
+ }
+
+ return value.ToString();
+ }
+
+ private void AppendLabel(StringBuilder value)
+ {
+ value.Append("IL_");
+ value.Append(Offset.ToString("x4"));
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Reflection/MethodData.cs b/src/Machine.Specifications.Runner.VisualStudio/Reflection/MethodData.cs
new file mode 100644
index 00000000..bcabc13a
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Reflection/MethodData.cs
@@ -0,0 +1,92 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reflection.Metadata;
+using System.Reflection.PortableExecutable;
+
+namespace Machine.Specifications.Runner.VisualStudio.Reflection
+{
+ public class MethodData
+ {
+ private readonly PEReader reader;
+
+ private readonly MetadataReader metadata;
+
+ private readonly SymbolReader symbolReader;
+
+ private readonly MethodDefinition definition;
+
+ private readonly MethodDefinitionHandle handle;
+
+ private readonly object sync = new object();
+
+ private ReadOnlyCollection instructions;
+
+ private List sequencePoints;
+
+ public MethodData(string name, PEReader reader, MetadataReader metadata, SymbolReader symbolReader, MethodDefinition definition, MethodDefinitionHandle handle)
+ {
+ this.reader = reader;
+ this.metadata = metadata;
+ this.symbolReader = symbolReader;
+ this.definition = definition;
+ this.handle = handle;
+
+ Name = name;
+ }
+
+ public string Name { get; }
+
+ public IReadOnlyCollection Instructions
+ {
+ get
+ {
+ if (instructions != null)
+ {
+ return instructions;
+ }
+
+ lock (sync)
+ {
+ instructions = GetInstructions().AsReadOnly();
+ }
+
+ return instructions;
+ }
+ }
+
+ public SequencePointData GetSequencePoint(InstructionData instruction)
+ {
+ if (sequencePoints == null)
+ {
+ lock (sync)
+ {
+ sequencePoints = GetSequencePoints().ToList();
+ }
+ }
+
+ return sequencePoints.FirstOrDefault(x => x.Offset == instruction.Offset);
+ }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ private List GetInstructions()
+ {
+ var blob = reader
+ .GetMethodBody(definition.RelativeVirtualAddress)
+ .GetILReader();
+
+ var codeReader = new CodeReader();
+
+ return codeReader.GetInstructions(metadata, ref blob).ToList();
+ }
+
+ private IEnumerable GetSequencePoints()
+ {
+ return symbolReader.ReadSequencePoints(handle);
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Reflection/SequencePointData.cs b/src/Machine.Specifications.Runner.VisualStudio/Reflection/SequencePointData.cs
new file mode 100644
index 00000000..719c2fad
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Reflection/SequencePointData.cs
@@ -0,0 +1,24 @@
+namespace Machine.Specifications.Runner.VisualStudio.Reflection
+{
+ public class SequencePointData
+ {
+ public SequencePointData(string fileName, int startLine, int endLine, int offset, bool isHidden)
+ {
+ FileName = fileName;
+ StartLine = startLine;
+ EndLine = endLine;
+ Offset = offset;
+ IsHidden = isHidden;
+ }
+
+ public string FileName { get; }
+
+ public int StartLine { get; }
+
+ public int EndLine { get; }
+
+ public int Offset { get; }
+
+ public bool IsHidden { get; }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Reflection/SymbolReader.cs b/src/Machine.Specifications.Runner.VisualStudio/Reflection/SymbolReader.cs
new file mode 100644
index 00000000..5d65532a
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Reflection/SymbolReader.cs
@@ -0,0 +1,43 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection.Metadata;
+
+namespace Machine.Specifications.Runner.VisualStudio.Reflection
+{
+ public class SymbolReader
+ {
+ private readonly MetadataReader reader;
+
+ public SymbolReader(string assembly)
+ {
+ var symbols = Path.ChangeExtension(assembly, "pdb");
+
+ if (File.Exists(symbols))
+ {
+ reader = MetadataReaderProvider
+ .FromPortablePdbStream(File.OpenRead(symbols))
+ .GetMetadataReader();
+ }
+ }
+
+ public IEnumerable ReadSequencePoints(MethodDefinitionHandle method)
+ {
+ if (reader == null)
+ {
+ return Enumerable.Empty();
+ }
+
+ return reader
+ .GetMethodDebugInformation(method)
+ .GetSequencePoints()
+ .Select(x =>
+ {
+ var document = reader.GetDocument(x.Document);
+ var fileName = reader.GetString(document.Name);
+
+ return new SequencePointData(fileName, x.StartLine, x.EndLine, x.Offset, x.IsHidden);
+ });
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Reflection/TypeData.cs b/src/Machine.Specifications.Runner.VisualStudio/Reflection/TypeData.cs
new file mode 100644
index 00000000..36ceaae9
--- /dev/null
+++ b/src/Machine.Specifications.Runner.VisualStudio/Reflection/TypeData.cs
@@ -0,0 +1,85 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Reflection.PortableExecutable;
+
+namespace Machine.Specifications.Runner.VisualStudio.Reflection
+{
+ public class TypeData
+ {
+ private readonly PEReader reader;
+
+ private readonly MetadataReader metadata;
+
+ private readonly SymbolReader symbolReader;
+
+ private readonly TypeDefinition definition;
+
+ private readonly object sync = new object();
+
+ private ReadOnlyCollection methods;
+
+ public TypeData(string typeName, PEReader reader, MetadataReader metadata, SymbolReader symbolReader, TypeDefinition definition)
+ {
+ this.reader = reader;
+ this.metadata = metadata;
+ this.symbolReader = symbolReader;
+ this.definition = definition;
+
+ TypeName = typeName;
+ }
+
+ public string TypeName { get; }
+
+ public IReadOnlyCollection Constructors
+ {
+ get
+ {
+ if (methods != null)
+ {
+ return methods;
+ }
+
+ lock (sync)
+ {
+ methods = GetConstructors().AsReadOnly();
+ }
+
+ return methods;
+ }
+ }
+
+ public override string ToString()
+ {
+ return TypeName;
+ }
+
+ private List GetConstructors()
+ {
+ var values = new List();
+
+ foreach (var methodHandle in definition.GetMethods())
+ {
+ var methodDefinition = metadata.GetMethodDefinition(methodHandle);
+ var parameters = methodDefinition.GetParameters();
+
+ var methodName = metadata.GetString(methodDefinition.Name);
+
+ if (IsConstructor(methodDefinition, methodName) && parameters.Count == 0)
+ {
+ values.Add(new MethodData(methodName, reader, metadata, symbolReader, methodDefinition, methodHandle));
+ }
+ }
+
+ return values;
+ }
+
+ private bool IsConstructor(MethodDefinition method, string name)
+ {
+ return method.Attributes.HasFlag(MethodAttributes.RTSpecialName) &&
+ method.Attributes.HasFlag(MethodAttributes.SpecialName) &&
+ name == ".ctor";
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Runner.VisualStudio/Resources/Machine.png b/src/Machine.Specifications.Runner.VisualStudio/Resources/Machine.png
new file mode 100644
index 0000000000000000000000000000000000000000..20b80d3430e850219762fe58ef24d16cb422e32a
GIT binary patch
literal 6064
zcmV;h7fN2bPDNB8
zb~7$DE-^4L^m3s902eGtL_t(|UhP{6bd=?}7OG-fZ`&*4QmL{gbt$rg5SFkL2!ZUA
zeVu(KlVv8E?8#)3nXDv0fIz|~s}-UkDs8>3dR3G~M3F@l#oktW&%GYKr|q?c_dd^;
ziA{1MhRxgd_?`2dVKUqIKHvL3@ACiL^zHg~eY?J0->z?HxvOjXm%1zOb~p>h87D=I60)Sl|
zFA&1+ERd|+Jh5bEi`HnCq_p&l@kuFvh>1^lHaa%miRYtiIgWn5ulvVY6&~-M`9;N!
zf|AmAjh2i{I-^l)8kWoQ6|1DFd9^gHUL%dGR?G60%~IF2LOgX1;__5WVVO&jV#>&r
zkJg+BkJ8vvb^1GfU;p>JTveWd#bvJQ(hB$IIXKw~6DI)^W&VPNQVRk$
zKE6$%ZS&*Ox_OJN+ptO2tZ$Q*Yg=SF2&rvcA(b_CQtGafyrL4ZX4@rIZy*t}G%Vt?
zWf74cgv}kkpZoh+RW)_}N?a9bW$wxg^*B*iWtB{RY>GrgMTt(Y7rntC&VoXDVo$rm
z&)c7polowOt-GF(E!%g3h>e0!k>s>=oV-@58ydw8B1$SM#Zg!!W`symnpVOhqh)c(
z(odF!N2H``^?x6sQR;FJ!ihgqi%qv~+KdxzmB87vB|afhtQj`3Wo9a*X|lOvv*
zTG_U1mu!de6;&RIiHlccO$i8)S+nOz!1U=dXWo3}1r^>JMFPFS=E#$D=!7Odfh0&!
z@RGgJF|q%@VNqI9@hDFF!xf0>E!(!snsu!b913COvP2dv43dS57KtV{R`3qON!m+F&j2+lFi$8D3NT2N7W-!RMj@9kZ_iiNfzE~
z&31^^WRdvfR7Jwbx5BT)pb(rq9`&H^2CT#nW8gDOnjo!guvNz{&V4?#s0o+-k9a7srwguUjE53
zW5-EiQnC^`yCYY*I$gIMMzurX4<9)qzx|Kj$*I$4
zp}_1Asfm>ZL5pNY;OrAi!@_U(ZTI@G;0kJa#@~yxyuFD-6*HeoRRY%
ze=KjkeL`M8bXX3)aYzmxIwY^*@!97N$W|Cs-_Ri5>T2@uY0nvh{DFfP$$6p^#ftsDc`$?re80K?x=#u5iX!0U&P$Q$s6Lr0G)yn67Uw5(gF
zNT{i;l`4-%5um&PBvKry348ZHEA4xqffqcf3a8brZL%DK$gEIW=~Y^g?JN{)POcb0
zLaN>0Qw7tO9ngYJTVw^YP4)5>QdZRw2H6M$b5@RMEg6!c)gv_~BNSqgS{KXoz`zsm
zh+BNyzxIW?KD`F)vtpYyXAW|_T}q+swRLrJ=Do9W^w?V+ClBKk0XS_%0RB#+j~#zo
zj=?+VA;fcVah`gINP+}tgU4GfRVa-3y@^Rll8(B7Mbl=4P3xA&Wfj7pwrQ2PytPtL
zUZGN=6)CaP0}@lyRgr`$kzT-1NcC<1+E#g`r$1u}+``^3B5KvF1#3B0~zcQR5rk4
zq00>dI5M^za+MpMI
z`}J>LR4z@>c)GhfaTaFM;kq#+@nz#fsPI0mQWDl-c4O(IfZY=;n*i!C=_jA)e7j21~iI*fr3
zMuPxq0s9FQXM59}z4Y`IP`Wx`V+&Pfo%_~aCcLnwqSTP9PdP5*3ZX!ro1Iaj{2
z9J#YlX3Cto^F)U@WLL+=h90nS(5(*M^&1m;B~q_
z4SWx$O@q&U_>r9Z=)8Oge1LzS1p(9mn=Mn^)Bv2k(gBdrm4Kq+5_p4G*0!vZ9XoeP
z8(Qtg=G9V(E>RUj#VCdt0=8^PM_-VTrb8A;R8?WpJcAe~J7w8qgw3pw65Y^zpG8-=
z-~oGBVy2{~sZNa^K(}RlvXp%iH9HAlx_b+;-+B6scN+S^xet}$AD#cWg8)3|Ywb_(
zmE@EZC3GtP7`bd?==Rf|fB7h!o97eI<&qqbclV{UJ
zPzV_Yn{d+aojofrzxtXgiFfUJN;ac0T7#aTVbvOO!vhM@6fyp-4yPF40jUO*!zt-7
z-q6kX9{9rYP{|?3_$r-Yc)=a*-siu5qGWd#e8wRrJF`p}Z?RgxJbkb4#18!8m+GV_
zd{q52*gWH&?*8HV^NIj^07K!FFZR=*>NK_n@lO)I3;}%SZg|zN5L(au;#bm+#%RZr
zyJZ8);pTO1r~{hCg|1N50fi+&1M$VNbt^Z3R+Uwt9m-5Epb{qXERikV60j$nCy?`P{BPd+ii0j4`
zE9B{Y`@U?2_wC;glBy*RHM+rQQr^Ps!Mx8$Lb*STX9lQ6O|Tt`|1834&%S5W>9?UB
zUWLB(;N@}WlUWPK7E>2Xry^0iiPp9(JD_C5y$SOqoYu~qDiQlGeJ7jGu#EDY0`VKoUAeU6mce=*#a4FtjkH
zN{o+S(_MZ%V|tO4I$i3sEP8VDI6BWps2EkoQ8R$G9mbcd7T68r%fu3S-HazLPe5#9
z(h3VU#fuYgBZ_xI6(`qiJwlQt~B7ej&%ssvWf>_BU?c
zEIXd~xtb7_v2lSQI#42WQbboK86ESzLUB3@ML(j~AO9PW!_70D601o7!I82EeLtEQ
zHS|!%UxC{j$H21^awLCBb+51Yz~u?xii(XpY|hLQgC#??nO>Yq6KaCh>)WIiBE`{l
zOX~)Ngjoy-iwrb5EOkkSf(?1P3ju}^y@vx~om3>D3!>DBI~1N~uw{w4k0-AmULg7a
zyJ*u4l9HS*xdEPD-`4|QPe6nwCMQ{|lRVVk1t>tus*t|wm#g@sLbq(#q;hougiI3?
ztC}YE4HQZaGpwe}ZW>Uq*ej*c^<6j74O;(PNm*o6bAM|ePu}QXD2agvq~0{q=?$WZ
zj+5M}RlUBq2fm(wbiLuOsF=7vW#l*{zoblxP=~W0udHoQ*Yi29CK0^8zaS_`;?axq
zB4;u>$Yf*;qt4Pz1DuOJ2%Rlb5a#W=k?w%erhvW)UYeg>A{Qix234jG1@ax->lE
z&&&wCYa=t)DY-=@YCPTH4fWz(-h>{#l;dbMj#?TThO`+jM!mHg0rmxMkhLDw9k2YZ
zknCVr*A4Xv+Tl(~c+4c>5gG}LjFiNbR52sm=yCJrFOVsZ1^gv%isvuAeYOYsMgZ56
zWuabPe&SHl1aaa>nga=5tW;isT7kpLS##$}!1Ni4hvt3Jy6N9<5P>NrN?0%$Ygr@>C`o6JDutz$@Y*L>!{Ba%At2oUHGr0oFi>DXO~s
zGMB3BS=wehi^Vj#4|yB?e)QrLSr#6tjAwbC27zYm)Ags%m?@)1kG^pKy+dr*od7QU
zXuyI+i@!i-kYuf1bV!Xxq)1+(;&9UDaH=jX1)&m`oT~2RFk9^1(x7QZf#kqz*v@95
zsmZ|m+1MEU`{o7uF?kXm6r(PNQgAskr{3_koH66Z{q?T9@2
zy%{%@fFVPNes4zL>|GQ*h@%=u^Wr4$*x-ONMQ`eO1X9z?-3ge2$`Hh8gR?JdJWsi|
zzxPD^&y4cO$kE68_wS#Ftz`mcc)$%MfYa351`HVBKYrq*y-Ms*x>A^;;gWHQ$?ED1
zuhqpPJtf4WI>Tvo{%HDSyQFb|`4z_NQ7R_~SS2h>BMB2sy}qX>w0?P?j{4EW7VIkv
z00Gnh?~U^SEY8;-0K(vU`=}c^&KeZb-oM{>j6c5fKEsVB;AUVDFaxmv>m7Hr
zj~zeZQxG8}Vm?GNcvYfcK-uI-X1q$;un*rOjY*`
zvZY-(KNtk$_4eZmdbGkR<1-{YEL!Du636TNb5YSv4-6E3*SK*LCB!eb*U$F`!-M(f
zEDx4m?CrH4xM~8Z1H1)=x2lc-qJd&y{VliNy7z%0Lr;z#JMK^8Cry$mlcr1hu-*yK
z;_u-T5>YS(DYAI~0k1`>YHs`ld34MeHHi7?Ll1pE_`drO2Hg{NqPOq423$1(TqJ>)
zp6>!41ww!fzzb}EN9-OnXwX5w5f7eEc*OFBc4&7Jl7?qV;Ftw6anfWNKXIar9Y0=1
zj~UBKP97OGN*;Xp;Xghwbm*Bo1`a&XuV26Q_|E2=``yyC=pN0{uHSQwxF!O)7z!j|
zAm9f~2j~qX!Ue1VHuUe;f0y5n9)4LfOm`uDNZ!Rbf79m=j~w;MNPqu7jP&!rFw)QO
z_rr#d_-OFppS&~ZuDf6P{_VFvh0kpSsO&3&dY}?0zNO!-&ZYMzzSN5`SA%ONfU7ec
zegOO!co-lN%K+Y}#MD#*c>CYfzv;f)hHM{j^ME!y=ly)0SO=^Hnt>*O277>Vpb)SF
zW*`lS8PNZ>@TkGMy)fWX4}-20*M$HslJFgX-ta@`IX%(!b)xfgo#?h;*w>Pv
zA71b&9yi1IsoQg(C9(fp3&!
+
net472;net8.0
+ enable
+ enable
+ latest
false
-
-
+
+
diff --git a/src/Machine.Specifications.Should.Specs/ShouldBeLikeSpecs.cs b/src/Machine.Specifications.Should.Specs/ShouldBeLikeSpecs.cs
index d294d5e8..83177e58 100644
--- a/src/Machine.Specifications.Should.Specs/ShouldBeLikeSpecs.cs
+++ b/src/Machine.Specifications.Should.Specs/ShouldBeLikeSpecs.cs
@@ -414,7 +414,8 @@ public class Dummy
public int[] Prop1 { get; set; }
}
- Establish context = () => obj1 = new Dummy { Prop1 = new[] { 1, 1, 1 } };
+ Establish context = () =>
+ obj1 = new Dummy { Prop1 = new[] { 1, 1, 1 } };
class and_the_objects_are_similar
{
@@ -466,15 +467,17 @@ class and_the_objects_are_different_and_have_null_values
exception.ShouldBeOfExactType();
It should_contain_message = () =>
- exception.Message.ShouldEqual(
- @"""Prop1"":" + Environment.NewLine +
- @" Expected: [null]" + Environment.NewLine +
- @" But was: System.Int32[]:" + Environment.NewLine +
- @"{" + Environment.NewLine +
- @" [1]," + Environment.NewLine +
- @" [1]," + Environment.NewLine +
- @" [1]" + Environment.NewLine +
- @"}");
+ exception.Message.Trim().ShouldEqual(
+ """
+ "Prop1":
+ Expected: [null]
+ But was: System.Int32[]:
+ {
+ [1],
+ [1],
+ [1]
+ }
+ """.Trim());
}
class and_the_objects_are_different_and_the_actual_object_has_a_null_value
diff --git a/src/Machine.Specifications.Should/Machine.Specifications.Should.csproj b/src/Machine.Specifications.Should/Machine.Specifications.Should.csproj
index e68fa443..c71e8b45 100644
--- a/src/Machine.Specifications.Should/Machine.Specifications.Should.csproj
+++ b/src/Machine.Specifications.Should/Machine.Specifications.Should.csproj
@@ -2,6 +2,9 @@
net472;net6.0
+ enable
+ enable
+ latest
diff --git a/src/Machine.Specifications.Should/Utility/Internal/PrettyPrintingExtensions.cs b/src/Machine.Specifications.Should/Utility/Internal/PrettyPrintingExtensions.cs
index 8785c1f2..abaf20f4 100644
--- a/src/Machine.Specifications.Should/Utility/Internal/PrettyPrintingExtensions.cs
+++ b/src/Machine.Specifications.Should/Utility/Internal/PrettyPrintingExtensions.cs
@@ -170,17 +170,17 @@ public static string EachToUsefulString(this IEnumerable enumerable)
var sb = new StringBuilder();
sb.AppendLine("{");
- sb.Append(string.Join(",\r\n", array.Select(x => x.ToUsefulString().Tab()).Take(10).ToArray()));
+ sb.Append(string.Join($",{Environment.NewLine}", array.Select(x => x.ToUsefulString().Tab()).Take(10).ToArray()));
if (array.Length > 10)
{
if (array.Length > 11)
{
- sb.AppendLine($",\r\n ...({array.Length - 10} more elements)");
+ sb.AppendLine($",{Environment.NewLine} ...({array.Length - 10} more elements)");
}
else
{
- sb.AppendLine(",\r\n" + array.Last().ToUsefulString().Tab());
+ sb.AppendLine($",{Environment.NewLine}" + array.Last().ToUsefulString().Tab());
}
}
else
@@ -214,7 +214,7 @@ internal static string ToUsefulString(this object obj)
{
var enumerable = items.Cast