From eab4de615bfff9862348d4b5516ad639e225450e Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Tue, 17 Dec 2024 08:17:39 +1100 Subject: [PATCH 1/2] Set updated target frameworks --- Directory.Build.props | 20 +++ Machine.Specifications.sln | 6 - build/build.csproj | 8 +- .../Machine.Specifications.Analyzers.csproj | 12 +- .../ExceptionResult.cs | 2 +- .../Machine.Specifications.Core.csproj | 21 +-- src/Machine.Specifications.Core/Result.cs | 2 +- .../Runner/AssemblyInfo.cs | 2 +- .../Runner/CapturedOutput.cs | 2 +- .../Runner/ContextInfo.cs | 2 +- .../Runner/Impl/DefaultRunner.cs | 10 +- .../Redirection/ForwardingStringWriter.cs | 2 +- .../Runner/Impl/RemoteRunListener.cs | 2 +- .../Runner/Impl/RemoteRunListenerDecorator.cs | 2 +- .../Runner/RunOptions.cs | 2 +- .../Runner/SpecificationInfo.cs | 2 +- .../Sdk/RemoteRunListenerDecorator.cs | 2 +- .../Sdk/RunSpecs.cs | 2 +- .../SpecificationUsageException.cs | 4 +- .../Utility/ReflectionPolyfillExtensions.cs | 2 +- .../Machine.Specifications.Fakes.Specs.csproj | 5 +- .../Machine.Specifications.Fakes.csproj | 2 +- .../Machine.Specifications.Fixtures.csproj | 2 +- .../CompileContext.cs | 2 +- .../AppDomainRunner.cs | 27 ++- .../LongLivedMarshalByRefObject.cs | 2 +- ...chine.Specifications.Runner.Utility.csproj | 22 +-- .../NullMessageSink.cs | 2 +- .../RemoteRunListener.cs | 8 +- .../RemoteRunnerDecorator.cs | 2 +- .../RemoteRunnerMessage.cs | 4 +- .../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 | 47 +++++ ...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.csproj | 15 +- .../SpecificationException.cs | 4 +- .../Machine.Specifications.csproj | 20 +-- 69 files changed, 2048 insertions(+), 128 deletions(-) create mode 100644 Directory.Build.props 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/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..52d199dd --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,20 @@ + + + Machine.Specifications + + + + Machine Specifications + test;unit;testing;context;specification;bdd;tdd;mspec + https://github.com/machine/machine.specifications/releases + icon.png + http://github.com/machine/machine.specifications + http://github.com/machine/machine.specifications + git + MIT + + + + + + diff --git a/Machine.Specifications.sln b/Machine.Specifications.sln index a252273e..7dfb5900 100644 --- a/Machine.Specifications.sln +++ b/Machine.Specifications.sln @@ -15,8 +15,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Machine.Specifications.Shou EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Machine.Specifications.Core", "src\Machine.Specifications.Core\Machine.Specifications.Core.csproj", "{12F8A086-25B8-4636-8520-5985CA56CB01}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Machine.Specifications.Interfaces", "src\Machine.Specifications.Interfaces\Machine.Specifications.Interfaces.csproj", "{5A18854E-1B66-409F-8122-3E12A13B9F43}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Machine.Specifications.Core.Specs", "src\Machine.Specifications.Core.Specs\Machine.Specifications.Core.Specs.csproj", "{0370391F-C7F2-41E7-BBC2-3D966F6F9ED9}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Machine.Specifications.Fixtures", "src\Machine.Specifications.Fixtures\Machine.Specifications.Fixtures.csproj", "{2934AB5B-7506-4150-B61B-FE16EBB13D50}" @@ -59,10 +57,6 @@ Global {12F8A086-25B8-4636-8520-5985CA56CB01}.Debug|Any CPU.Build.0 = Debug|Any CPU {12F8A086-25B8-4636-8520-5985CA56CB01}.Release|Any CPU.ActiveCfg = Release|Any CPU {12F8A086-25B8-4636-8520-5985CA56CB01}.Release|Any CPU.Build.0 = Release|Any CPU - {5A18854E-1B66-409F-8122-3E12A13B9F43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5A18854E-1B66-409F-8122-3E12A13B9F43}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5A18854E-1B66-409F-8122-3E12A13B9F43}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5A18854E-1B66-409F-8122-3E12A13B9F43}.Release|Any CPU.Build.0 = Release|Any CPU {0370391F-C7F2-41E7-BBC2-3D966F6F9ED9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0370391F-C7F2-41E7-BBC2-3D966F6F9ED9}.Debug|Any CPU.Build.0 = Debug|Any CPU {0370391F-C7F2-41E7-BBC2-3D966F6F9ED9}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/build/build.csproj b/build/build.csproj index e061b305..c513cdae 100644 --- a/build/build.csproj +++ b/build/build.csproj @@ -2,13 +2,13 @@ Exe - net7.0 + net8.0 - - - + + + diff --git a/src/Machine.Specifications.Analyzers/Machine.Specifications.Analyzers.csproj b/src/Machine.Specifications.Analyzers/Machine.Specifications.Analyzers.csproj index 60af2214..e05b64db 100644 --- a/src/Machine.Specifications.Analyzers/Machine.Specifications.Analyzers.csproj +++ b/src/Machine.Specifications.Analyzers/Machine.Specifications.Analyzers.csproj @@ -5,22 +5,13 @@ enable enable latest + true true - $(TargetsForTfmSpecificContentInPackage);PackageItems Roslyn analyzers and code fixes for Machine.Specifications - Machine Specifications - mspec;test;unit;testing;context;specification;bdd;tdd;analyzers;roslyn - See https://github.com/machine/machine.specifications.analyzers/releases - icon.png - https://github.com/machine/machine.specifications - https://github.com/machine/machine.specifications.analyzers - git - MIT - true @@ -28,7 +19,6 @@ - diff --git a/src/Machine.Specifications.Core/ExceptionResult.cs b/src/Machine.Specifications.Core/ExceptionResult.cs index fa3fd628..4a3e7de6 100644 --- a/src/Machine.Specifications.Core/ExceptionResult.cs +++ b/src/Machine.Specifications.Core/ExceptionResult.cs @@ -6,7 +6,7 @@ namespace Machine.Specifications { -#if !NETSTANDARD +#if !NET6_0_OR_GREATER [Serializable] #endif public class ExceptionResult diff --git a/src/Machine.Specifications.Core/Machine.Specifications.Core.csproj b/src/Machine.Specifications.Core/Machine.Specifications.Core.csproj index 422e5e56..689443a7 100644 --- a/src/Machine.Specifications.Core/Machine.Specifications.Core.csproj +++ b/src/Machine.Specifications.Core/Machine.Specifications.Core.csproj @@ -1,30 +1,17 @@  - net472;netstandard2.0 - Machine.Specifications + net472;net6.0 Machine.Specifications - Machine.Specifications.Core + + + Machine.Specifications is a Context/Specification framework geared towards removing language noise and simplifying tests - Machine Specifications - test;unit;testing;context;specification;bdd;tdd;mspec - https://github.com/machine/machine.specifications/releases - icon.png - http://github.com/machine/machine.specifications - MIT - - - - - - - - diff --git a/src/Machine.Specifications.Core/Result.cs b/src/Machine.Specifications.Core/Result.cs index 8d3a4ef5..10d8d2d5 100644 --- a/src/Machine.Specifications.Core/Result.cs +++ b/src/Machine.Specifications.Core/Result.cs @@ -3,7 +3,7 @@ namespace Machine.Specifications { -#if !NETSTANDARD +#if !NET6_0_OR_GREATER [Serializable] #endif public class Result diff --git a/src/Machine.Specifications.Core/Runner/AssemblyInfo.cs b/src/Machine.Specifications.Core/Runner/AssemblyInfo.cs index 168f43e4..c5642f3a 100644 --- a/src/Machine.Specifications.Core/Runner/AssemblyInfo.cs +++ b/src/Machine.Specifications.Core/Runner/AssemblyInfo.cs @@ -2,7 +2,7 @@ namespace Machine.Specifications.Runner { -#if !NETSTANDARD +#if !NET6_0_OR_GREATER [Serializable] #endif public class AssemblyInfo : IEquatable diff --git a/src/Machine.Specifications.Core/Runner/CapturedOutput.cs b/src/Machine.Specifications.Core/Runner/CapturedOutput.cs index 8b741589..0351af51 100644 --- a/src/Machine.Specifications.Core/Runner/CapturedOutput.cs +++ b/src/Machine.Specifications.Core/Runner/CapturedOutput.cs @@ -2,7 +2,7 @@ namespace Machine.Specifications.Runner { -#if !NETSTANDARD +#if !NET6_0_OR_GREATER [Serializable] #endif public class CapturedOutput diff --git a/src/Machine.Specifications.Core/Runner/ContextInfo.cs b/src/Machine.Specifications.Core/Runner/ContextInfo.cs index bb4e50fa..9d45ea61 100644 --- a/src/Machine.Specifications.Core/Runner/ContextInfo.cs +++ b/src/Machine.Specifications.Core/Runner/ContextInfo.cs @@ -2,7 +2,7 @@ namespace Machine.Specifications.Runner { -#if !NETSTANDARD +#if !NET6_0_OR_GREATER [Serializable] #endif public class ContextInfo : IEquatable diff --git a/src/Machine.Specifications.Core/Runner/Impl/DefaultRunner.cs b/src/Machine.Specifications.Core/Runner/Impl/DefaultRunner.cs index 7de7d5da..a2a28370 100644 --- a/src/Machine.Specifications.Core/Runner/Impl/DefaultRunner.cs +++ b/src/Machine.Specifications.Core/Runner/Impl/DefaultRunner.cs @@ -3,7 +3,7 @@ using System.IO; using System.Linq; using System.Reflection; -#if !NETSTANDARD +#if !NET6_0_OR_GREATER using System.Runtime.Remoting; using System.Runtime.Remoting.Messaging; #endif @@ -16,11 +16,11 @@ namespace Machine.Specifications.Runner.Impl { -#if !NETSTANDARD +#if !NET6_0_OR_GREATER [Serializable] #endif public class DefaultRunner : -#if !NETSTANDARD +#if !NET6_0_OR_GREATER MarshalByRefObject, IMessageSink, #endif ISpecificationRunner @@ -40,7 +40,7 @@ public class DefaultRunner : private bool explicitStartAndEnd; -#if !NETSTANDARD +#if !NET6_0_OR_GREATER public DefaultRunner(object listener, string runOptionsXml, bool signalRunStartAndEnd) : this(new RemoteRunListenerDecorator(listener), RunOptions.Parse(runOptionsXml), signalRunStartAndEnd) { @@ -173,7 +173,7 @@ public void EndRun(Assembly assembly) runEnd.Invoke(); } -#if !NETSTANDARD +#if !NET6_0_OR_GREATER [SecurityCritical] public override object InitializeLifetimeService() { diff --git a/src/Machine.Specifications.Core/Runner/Impl/Listener/Redirection/ForwardingStringWriter.cs b/src/Machine.Specifications.Core/Runner/Impl/Listener/Redirection/ForwardingStringWriter.cs index ae080b53..d0c8291a 100644 --- a/src/Machine.Specifications.Core/Runner/Impl/Listener/Redirection/ForwardingStringWriter.cs +++ b/src/Machine.Specifications.Core/Runner/Impl/Listener/Redirection/ForwardingStringWriter.cs @@ -24,7 +24,7 @@ public override Encoding Encoding if (first == null) { -#if NETSTANDARD +#if NET6_0_OR_GREATER return Encoding.UTF8; #else diff --git a/src/Machine.Specifications.Core/Runner/Impl/RemoteRunListener.cs b/src/Machine.Specifications.Core/Runner/Impl/RemoteRunListener.cs index 9a7e4f6d..723a398d 100644 --- a/src/Machine.Specifications.Core/Runner/Impl/RemoteRunListener.cs +++ b/src/Machine.Specifications.Core/Runner/Impl/RemoteRunListener.cs @@ -1,4 +1,4 @@ -#if !NETSTANDARD +#if !NET6_0_OR_GREATER using System; using System.Security; diff --git a/src/Machine.Specifications.Core/Runner/Impl/RemoteRunListenerDecorator.cs b/src/Machine.Specifications.Core/Runner/Impl/RemoteRunListenerDecorator.cs index f7ac685b..7cd8b8d0 100644 --- a/src/Machine.Specifications.Core/Runner/Impl/RemoteRunListenerDecorator.cs +++ b/src/Machine.Specifications.Core/Runner/Impl/RemoteRunListenerDecorator.cs @@ -1,4 +1,4 @@ -#if !NETSTANDARD +#if !NET6_0_OR_GREATER using System; using System.Collections; using System.Collections.Generic; diff --git a/src/Machine.Specifications.Core/Runner/RunOptions.cs b/src/Machine.Specifications.Core/Runner/RunOptions.cs index 51b2dd96..fb7325b8 100644 --- a/src/Machine.Specifications.Core/Runner/RunOptions.cs +++ b/src/Machine.Specifications.Core/Runner/RunOptions.cs @@ -6,7 +6,7 @@ namespace Machine.Specifications.Runner { -#if !NETSTANDARD +#if !NET6_0_OR_GREATER [Serializable] #endif public class RunOptions diff --git a/src/Machine.Specifications.Core/Runner/SpecificationInfo.cs b/src/Machine.Specifications.Core/Runner/SpecificationInfo.cs index 4e5c2a7e..f21c9ee3 100644 --- a/src/Machine.Specifications.Core/Runner/SpecificationInfo.cs +++ b/src/Machine.Specifications.Core/Runner/SpecificationInfo.cs @@ -2,7 +2,7 @@ namespace Machine.Specifications.Runner { -#if !NETSTANDARD +#if !NET6_0_OR_GREATER [Serializable] #endif public class SpecificationInfo diff --git a/src/Machine.Specifications.Core/Sdk/RemoteRunListenerDecorator.cs b/src/Machine.Specifications.Core/Sdk/RemoteRunListenerDecorator.cs index 4c0055ac..b8c84f91 100644 --- a/src/Machine.Specifications.Core/Sdk/RemoteRunListenerDecorator.cs +++ b/src/Machine.Specifications.Core/Sdk/RemoteRunListenerDecorator.cs @@ -1,4 +1,4 @@ -#if !NETSTANDARD +#if !NET6_0_OR_GREATER using System; using System.Collections; diff --git a/src/Machine.Specifications.Core/Sdk/RunSpecs.cs b/src/Machine.Specifications.Core/Sdk/RunSpecs.cs index d7490e2c..21c30099 100644 --- a/src/Machine.Specifications.Core/Sdk/RunSpecs.cs +++ b/src/Machine.Specifications.Core/Sdk/RunSpecs.cs @@ -1,4 +1,4 @@ -#if !NETSTANDARD +#if !NET6_0_OR_GREATER using System.Collections.Generic; using System.Linq; diff --git a/src/Machine.Specifications.Core/SpecificationUsageException.cs b/src/Machine.Specifications.Core/SpecificationUsageException.cs index 414e1427..5d2b12c3 100644 --- a/src/Machine.Specifications.Core/SpecificationUsageException.cs +++ b/src/Machine.Specifications.Core/SpecificationUsageException.cs @@ -3,7 +3,7 @@ namespace Machine.Specifications { -#if !NETSTANDARD +#if !NET6_0_OR_GREATER [Serializable] #endif public class SpecificationUsageException : Exception @@ -22,7 +22,7 @@ public SpecificationUsageException(string message, Exception inner) { } -#if !NETSTANDARD +#if !NET6_0_OR_GREATER protected SpecificationUsageException( SerializationInfo info, StreamingContext context) diff --git a/src/Machine.Specifications.Core/Utility/ReflectionPolyfillExtensions.cs b/src/Machine.Specifications.Core/Utility/ReflectionPolyfillExtensions.cs index 0a96ab2a..2b2cbceb 100644 --- a/src/Machine.Specifications.Core/Utility/ReflectionPolyfillExtensions.cs +++ b/src/Machine.Specifications.Core/Utility/ReflectionPolyfillExtensions.cs @@ -5,7 +5,7 @@ namespace Machine.Specifications.Utility { internal static class ReflectionCompatExtensions { -#if NETSTANDARD +#if NET6_0_OR_GREATER public static bool IsType(this MemberInfo memberInfo) { return memberInfo is TypeInfo; 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 ee9fcb60..6c4b278c 100644 --- a/src/Machine.Specifications.Fakes.Specs/Machine.Specifications.Fakes.Specs.csproj +++ b/src/Machine.Specifications.Fakes.Specs/Machine.Specifications.Fakes.Specs.csproj @@ -2,19 +2,18 @@ net8.0 - false - - + + diff --git a/src/Machine.Specifications.Fakes/Machine.Specifications.Fakes.csproj b/src/Machine.Specifications.Fakes/Machine.Specifications.Fakes.csproj index b26d9a24..5968fe38 100644 --- a/src/Machine.Specifications.Fakes/Machine.Specifications.Fakes.csproj +++ b/src/Machine.Specifications.Fakes/Machine.Specifications.Fakes.csproj @@ -1,7 +1,7 @@  - net472;netstandard2.0 + net472;net6.0 false diff --git a/src/Machine.Specifications.Fixtures/Machine.Specifications.Fixtures.csproj b/src/Machine.Specifications.Fixtures/Machine.Specifications.Fixtures.csproj index 7e8cad68..06f17ff3 100644 --- a/src/Machine.Specifications.Fixtures/Machine.Specifications.Fixtures.csproj +++ b/src/Machine.Specifications.Fixtures/Machine.Specifications.Fixtures.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + net472;net8.0 false diff --git a/src/Machine.Specifications.Runner.Utility.Specs/CompileContext.cs b/src/Machine.Specifications.Runner.Utility.Specs/CompileContext.cs index 11aadeaa..e755bab6 100644 --- a/src/Machine.Specifications.Runner.Utility.Specs/CompileContext.cs +++ b/src/Machine.Specifications.Runner.Utility.Specs/CompileContext.cs @@ -74,7 +74,7 @@ public AssemblyPath Compile(string code, params string[] references) parameters.ReferencedAssemblies.AddRange(new [] { "System.dll", - "Machine.Specifications.dll", + "Machine.Specifications.Core.dll", "Machine.Specifications.Should.dll", "netstandard.dll" }); diff --git a/src/Machine.Specifications.Runner.Utility/AppDomainRunner.cs b/src/Machine.Specifications.Runner.Utility/AppDomainRunner.cs index ee50f968..b91a7f26 100644 --- a/src/Machine.Specifications.Runner.Utility/AppDomainRunner.cs +++ b/src/Machine.Specifications.Runner.Utility/AppDomainRunner.cs @@ -3,7 +3,7 @@ using System.IO; using System.Linq; using System.Reflection; -#if !NETSTANDARD +#if !NET6_0_OR_GREATER using System.Runtime.Remoting.Messaging; #endif using System.Security; @@ -123,7 +123,7 @@ private static void UnloadAppDomain(AppDomain appDomain) return; } -#if !NETSTANDARD +#if !NET6_0_OR_GREATER var cachePath = appDomain.SetupInformation.CachePath; try @@ -145,7 +145,7 @@ private static void UnloadAppDomain(AppDomain appDomain) [SecuritySafeCritical] private ISpecificationRunner CreateRunnerInSeparateAppDomain(AppDomain appDomain, AssemblyPath assembly) { -#if !NETSTANDARD +#if !NET6_0_OR_GREATER var path = Path.GetDirectoryName(assembly); if (path == null) @@ -154,13 +154,23 @@ private ISpecificationRunner CreateRunnerInSeparateAppDomain(AppDomain appDomain } var mspecAssemblyFilename = Path.Combine(path, "Machine.Specifications.dll"); + var coreAssemblyFilename = Path.Combine(path, "Machine.Specifications.Core.dll"); - if (!File.Exists(mspecAssemblyFilename)) + AssemblyName mspecAssemblyName = null; + + if (File.Exists(mspecAssemblyFilename)) { - return new NullSpecificationRunner(); + mspecAssemblyName = AssemblyName.GetAssemblyName(mspecAssemblyFilename); + } + else if (File.Exists(coreAssemblyFilename)) + { + mspecAssemblyName = AssemblyName.GetAssemblyName(coreAssemblyFilename); } - var mspecAssemblyName = AssemblyName.GetAssemblyName(mspecAssemblyFilename); + if (mspecAssemblyName == null) + { + return new NullSpecificationRunner(); + } var constructorArgs = new object[3]; constructorArgs[0] = listener; @@ -188,7 +198,8 @@ private ISpecificationRunner CreateRunnerInSeparateAppDomain(AppDomain appDomain throw; } #else - var runnerType = Type.GetType($"{RunnerType}, Machine.Specifications"); + var runnerType = Type.GetType($"{RunnerType}, Machine.Specifications") ?? + Type.GetType($"{RunnerType}, Machine.Specifications.Core"); if (runnerType == null) { @@ -217,7 +228,7 @@ private AppDomainAndRunner GetOrCreateAppDomainRunner(AssemblyPath assembly) return appDomainAndRunner; } -#if !NETSTANDARD +#if !NET6_0_OR_GREATER var setup = new AppDomainSetup { ApplicationBase = Path.GetDirectoryName(assembly), diff --git a/src/Machine.Specifications.Runner.Utility/LongLivedMarshalByRefObject.cs b/src/Machine.Specifications.Runner.Utility/LongLivedMarshalByRefObject.cs index feb00f62..af8c5c67 100644 --- a/src/Machine.Specifications.Runner.Utility/LongLivedMarshalByRefObject.cs +++ b/src/Machine.Specifications.Runner.Utility/LongLivedMarshalByRefObject.cs @@ -3,7 +3,7 @@ namespace Machine.Specifications.Runner.Utility { -#if !NETSTANDARD +#if !NET6_0_OR_GREATER internal class LongLivedMarshalByRefObject : MarshalByRefObject { [SecurityCritical] 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 8036fa5a..ff729e52 100644 --- a/src/Machine.Specifications.Runner.Utility/Machine.Specifications.Runner.Utility.csproj +++ b/src/Machine.Specifications.Runner.Utility/Machine.Specifications.Runner.Utility.csproj @@ -1,35 +1,19 @@  - net472;netstandard2.0 + net472;net6.0 + + Provides a version-independent runner for Machine.Specifications - Machine Specifications - test;unit;testing;context;specification;bdd;tdd;mspec;runner - https://github.com/machine/machine.specifications/releases - icon.png - http://github.com/machine/machine.specifications.runner.utility - MIT - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - \ No newline at end of file diff --git a/src/Machine.Specifications.Runner.Utility/NullMessageSink.cs b/src/Machine.Specifications.Runner.Utility/NullMessageSink.cs index c11f6bdb..be65bf0b 100644 --- a/src/Machine.Specifications.Runner.Utility/NullMessageSink.cs +++ b/src/Machine.Specifications.Runner.Utility/NullMessageSink.cs @@ -1,4 +1,4 @@ -#if !NETSTANDARD +#if !NET6_0_OR_GREATER using System.Runtime.Remoting.Messaging; namespace Machine.Specifications.Runner.Utility diff --git a/src/Machine.Specifications.Runner.Utility/RemoteRunListener.cs b/src/Machine.Specifications.Runner.Utility/RemoteRunListener.cs index 709096ba..228bc44c 100644 --- a/src/Machine.Specifications.Runner.Utility/RemoteRunListener.cs +++ b/src/Machine.Specifications.Runner.Utility/RemoteRunListener.cs @@ -1,6 +1,6 @@ using System; using System.IO; -#if !NETSTANDARD +#if !NET6_0_OR_GREATER using System.Runtime.Remoting; using System.Runtime.Remoting.Messaging; #endif @@ -13,11 +13,11 @@ namespace Machine.Specifications.Runner.Utility /// The remote run listener is a decorator class which takes the burden to implement IMessageSink and translates /// information about specification execution over app domain boundaries. /// -#if !NETSTANDARD +#if !NET6_0_OR_GREATER [Serializable] #endif internal class RemoteRunListener : LongLivedMarshalByRefObject, ISpecificationRunListener -#if !NETSTANDARD +#if !NET6_0_OR_GREATER , IMessageSink #endif { @@ -73,7 +73,7 @@ public virtual void OnFatalError(ExceptionResult exceptionResult) runListener.OnFatalError(exceptionResult); } -#if !NETSTANDARD +#if !NET6_0_OR_GREATER public IMessage SyncProcessMessage(IMessage msg) { if (msg is IMethodCallMessage methodCall) diff --git a/src/Machine.Specifications.Runner.Utility/RemoteRunnerDecorator.cs b/src/Machine.Specifications.Runner.Utility/RemoteRunnerDecorator.cs index 8aebae72..834c585a 100644 --- a/src/Machine.Specifications.Runner.Utility/RemoteRunnerDecorator.cs +++ b/src/Machine.Specifications.Runner.Utility/RemoteRunnerDecorator.cs @@ -1,4 +1,4 @@ -#if !NETSTANDARD +#if !NET6_0_OR_GREATER using System.Collections.Generic; using System.Reflection; using System.Runtime.Remoting.Messaging; diff --git a/src/Machine.Specifications.Runner.Utility/RemoteRunnerMessage.cs b/src/Machine.Specifications.Runner.Utility/RemoteRunnerMessage.cs index a383a367..62a1ecaf 100644 --- a/src/Machine.Specifications.Runner.Utility/RemoteRunnerMessage.cs +++ b/src/Machine.Specifications.Runner.Utility/RemoteRunnerMessage.cs @@ -2,14 +2,14 @@ using System.Collections; using System.Collections.Generic; using System.Reflection; -#if !NETSTANDARD +#if !NET6_0_OR_GREATER using System.Runtime.Remoting.Messaging; #endif using System.Xml.Linq; namespace Machine.Specifications.Runner.Utility { -#if !NETSTANDARD +#if !NET6_0_OR_GREATER internal class RemoteRunnerMessage : MarshalByRefObject, IMessage { public RemoteRunnerMessage(XElement root, MemberInfo info = null) 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..ebe3f257 --- /dev/null +++ b/src/Machine.Specifications.Runner.VisualStudio/Machine.Specifications.Runner.VisualStudio.csproj @@ -0,0 +1,47 @@ + + + + 0.1.0 + netcoreapp3.1;net472 + Machine.Specifications.Runner.VisualStudio + Machine.Specifications.Runner.VisualStudio.TestAdapter + NU5127,NU5128 + false + + Machine.Specifications test adapter for .NET Framework and .NET Core. + Machine Specifications + mspec;unit;testing;context;specification;bdd;tdd + https://github.com/machine/machine.specifications.runner.visualstudio/releases + Machine.png + https://github.com/machine/machine.specifications.runner.visualstudio + MIT + + + + + + + + + + + + + $(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 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 - net472;netstandard2.0 - Machine.Specifications + net472;net6.0 + + Assertion library for Machine.Specifications - Machine Specifications - test;unit;testing;context;specification;bdd;tdd;mspec - https://github.com/machine/machine.specifications.should/releases - icon.png - http://github.com/machine/machine.specifications.should - MIT - - - - diff --git a/src/Machine.Specifications.Should/SpecificationException.cs b/src/Machine.Specifications.Should/SpecificationException.cs index 52fb85ca..3ade8c43 100644 --- a/src/Machine.Specifications.Should/SpecificationException.cs +++ b/src/Machine.Specifications.Should/SpecificationException.cs @@ -3,7 +3,7 @@ namespace Machine.Specifications { -#if !NETSTANDARD +#if !NET6_0_OR_GREATER [Serializable] #endif public class SpecificationException : Exception @@ -22,7 +22,7 @@ public SpecificationException(string message, Exception inner) { } -#if !NETSTANDARD +#if !NET6_0_OR_GREATER protected SpecificationException( SerializationInfo info, StreamingContext context) diff --git a/src/Machine.Specifications/Machine.Specifications.csproj b/src/Machine.Specifications/Machine.Specifications.csproj index 29ba8768..7d12fec4 100644 --- a/src/Machine.Specifications/Machine.Specifications.csproj +++ b/src/Machine.Specifications/Machine.Specifications.csproj @@ -1,27 +1,19 @@  - net472;netstandard2.0 - - Machine.Specifications is a Context/Specification framework geared towards removing language noise and simplifying tests - Machine Specifications - test;unit;testing;context;specification;bdd;tdd;mspec - https://github.com/machine/machine.specifications/releases - icon.png - http://github.com/machine/machine.specifications - MIT + net472;net6.0 false - NU5128 + + Machine.Specifications is a Context/Specification framework geared towards removing language noise and simplifying tests + + + - - - - From 85edd13ef37975dfcfa25bf02e77334f9836eb0d Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Tue, 17 Dec 2024 08:55:17 +1100 Subject: [PATCH 2/2] Fix build --- .../CompileContext.cs | 2 +- .../AppDomainRunner.cs | 5 - .../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 | 47 ----- ...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 6064 -> 0 bytes 37 files changed, 1 insertion(+), 1958 deletions(-) delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Discovery/BuiltInSpecificationDiscoverer.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Discovery/ISpecificationDiscoverer.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Discovery/SpecTestCase.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Discovery/TestDiscoverer.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Execution/AssemblyLocationAwareRunListener.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Execution/IFrameworkLogger.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Execution/ISpecificationExecutor.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Execution/ISpecificationFilterProvider.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Execution/ProxyAssemblySpecificationRunListener.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Execution/RunStats.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Execution/SingleBehaviorTestRunListenerWrapper.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Execution/SpecificationExecutor.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Execution/SpecificationFilterProvider.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Execution/TestExecutor.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Helpers/AssemblyHelper.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Helpers/IsolatedAppDomainExecutionScope.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Helpers/NamingConversionExtensions.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Helpers/SpecTestHelper.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Helpers/VisualStudioTestIdentifier.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Machine.Specifications.Runner.VisualStudio.csproj delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Machine.Specifications.Runner.VisualStudio.props delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/MspecTestDiscoverer.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/MspecTestExecutor.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/MspecTestRunner.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Navigation/INavigationSession.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Navigation/NavigationData.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Navigation/NavigationSession.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Reflection/AssemblyData.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Reflection/CodeReader.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Reflection/InstructionData.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Reflection/MethodData.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Reflection/SequencePointData.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Reflection/SymbolReader.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Reflection/TypeData.cs delete mode 100644 src/Machine.Specifications.Runner.VisualStudio/Resources/Machine.png diff --git a/src/Machine.Specifications.Runner.Utility.Specs/CompileContext.cs b/src/Machine.Specifications.Runner.Utility.Specs/CompileContext.cs index e755bab6..11aadeaa 100644 --- a/src/Machine.Specifications.Runner.Utility.Specs/CompileContext.cs +++ b/src/Machine.Specifications.Runner.Utility.Specs/CompileContext.cs @@ -74,7 +74,7 @@ public AssemblyPath Compile(string code, params string[] references) parameters.ReferencedAssemblies.AddRange(new [] { "System.dll", - "Machine.Specifications.Core.dll", + "Machine.Specifications.dll", "Machine.Specifications.Should.dll", "netstandard.dll" }); diff --git a/src/Machine.Specifications.Runner.Utility/AppDomainRunner.cs b/src/Machine.Specifications.Runner.Utility/AppDomainRunner.cs index b91a7f26..ed2194ed 100644 --- a/src/Machine.Specifications.Runner.Utility/AppDomainRunner.cs +++ b/src/Machine.Specifications.Runner.Utility/AppDomainRunner.cs @@ -154,7 +154,6 @@ private ISpecificationRunner CreateRunnerInSeparateAppDomain(AppDomain appDomain } var mspecAssemblyFilename = Path.Combine(path, "Machine.Specifications.dll"); - var coreAssemblyFilename = Path.Combine(path, "Machine.Specifications.Core.dll"); AssemblyName mspecAssemblyName = null; @@ -162,10 +161,6 @@ private ISpecificationRunner CreateRunnerInSeparateAppDomain(AppDomain appDomain { mspecAssemblyName = AssemblyName.GetAssemblyName(mspecAssemblyFilename); } - else if (File.Exists(coreAssemblyFilename)) - { - mspecAssemblyName = AssemblyName.GetAssemblyName(coreAssemblyFilename); - } if (mspecAssemblyName == null) { diff --git a/src/Machine.Specifications.Runner.VisualStudio/Discovery/BuiltInSpecificationDiscoverer.cs b/src/Machine.Specifications.Runner.VisualStudio/Discovery/BuiltInSpecificationDiscoverer.cs deleted file mode 100644 index 56a18c92..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Discovery/BuiltInSpecificationDiscoverer.cs +++ /dev/null @@ -1,24 +0,0 @@ -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 deleted file mode 100644 index e0e723f4..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Discovery/ISpecificationDiscoverer.cs +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index 84e22f5d..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Discovery/SpecTestCase.cs +++ /dev/null @@ -1,32 +0,0 @@ -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 deleted file mode 100644 index 829c00fa..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Discovery/TestDiscoverer.cs +++ /dev/null @@ -1,102 +0,0 @@ -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 deleted file mode 100644 index 6bdd0e75..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Execution/AssemblyLocationAwareRunListener.cs +++ /dev/null @@ -1,57 +0,0 @@ -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 deleted file mode 100644 index 36ab2734..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Execution/IFrameworkLogger.cs +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index 77e538c6..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Execution/ISpecificationExecutor.cs +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index d7e1735e..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Execution/ISpecificationFilterProvider.cs +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index ee35c6d8..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Execution/ProxyAssemblySpecificationRunListener.cs +++ /dev/null @@ -1,160 +0,0 @@ -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 deleted file mode 100644 index f669b178..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Execution/RunStats.cs +++ /dev/null @@ -1,29 +0,0 @@ -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 deleted file mode 100644 index 86649d7c..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Execution/SingleBehaviorTestRunListenerWrapper.cs +++ /dev/null @@ -1,82 +0,0 @@ -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 deleted file mode 100644 index 055468d3..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Execution/SpecificationExecutor.cs +++ /dev/null @@ -1,33 +0,0 @@ -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 deleted file mode 100644 index e076449d..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Execution/SpecificationFilterProvider.cs +++ /dev/null @@ -1,104 +0,0 @@ -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 deleted file mode 100644 index 9958339c..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Execution/TestExecutor.cs +++ /dev/null @@ -1,83 +0,0 @@ -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 deleted file mode 100644 index 9712a802..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Helpers/AssemblyHelper.cs +++ /dev/null @@ -1,25 +0,0 @@ -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 deleted file mode 100644 index a622c547..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Helpers/IsolatedAppDomainExecutionScope.cs +++ /dev/null @@ -1,148 +0,0 @@ -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 deleted file mode 100644 index 86d4dc6a..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Helpers/NamingConversionExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -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 deleted file mode 100644 index 6b571dd8..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Helpers/SpecTestHelper.cs +++ /dev/null @@ -1,54 +0,0 @@ -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 deleted file mode 100644 index 39fb4919..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Helpers/VisualStudioTestIdentifier.cs +++ /dev/null @@ -1,58 +0,0 @@ -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 deleted file mode 100644 index ebe3f257..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Machine.Specifications.Runner.VisualStudio.csproj +++ /dev/null @@ -1,47 +0,0 @@ - - - - 0.1.0 - netcoreapp3.1;net472 - Machine.Specifications.Runner.VisualStudio - Machine.Specifications.Runner.VisualStudio.TestAdapter - NU5127,NU5128 - false - - Machine.Specifications test adapter for .NET Framework and .NET Core. - Machine Specifications - mspec;unit;testing;context;specification;bdd;tdd - https://github.com/machine/machine.specifications.runner.visualstudio/releases - Machine.png - https://github.com/machine/machine.specifications.runner.visualstudio - MIT - - - - - - - - - - - - - $(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 deleted file mode 100644 index 425b3009..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Machine.Specifications.Runner.VisualStudio.props +++ /dev/null @@ -1,10 +0,0 @@ - - - - - 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 deleted file mode 100644 index 01059e05..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/MspecTestDiscoverer.cs +++ /dev/null @@ -1,68 +0,0 @@ -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 deleted file mode 100644 index df400bde..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/MspecTestExecutor.cs +++ /dev/null @@ -1,82 +0,0 @@ -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 deleted file mode 100644 index 844c0b6f..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/MspecTestRunner.cs +++ /dev/null @@ -1,55 +0,0 @@ -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 deleted file mode 100644 index c7297541..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Navigation/INavigationSession.cs +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index bf61fbe8..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Navigation/NavigationData.cs +++ /dev/null @@ -1,17 +0,0 @@ -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 deleted file mode 100644 index 4fe24c69..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Navigation/NavigationSession.cs +++ /dev/null @@ -1,50 +0,0 @@ -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 deleted file mode 100644 index 7aadc489..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Reflection/AssemblyData.cs +++ /dev/null @@ -1,89 +0,0 @@ -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 deleted file mode 100644 index 0fc28f8f..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Reflection/CodeReader.cs +++ /dev/null @@ -1,154 +0,0 @@ -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 deleted file mode 100644 index 6acc2727..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Reflection/InstructionData.cs +++ /dev/null @@ -1,65 +0,0 @@ -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 deleted file mode 100644 index bcabc13a..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Reflection/MethodData.cs +++ /dev/null @@ -1,92 +0,0 @@ -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 deleted file mode 100644 index 719c2fad..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Reflection/SequencePointData.cs +++ /dev/null @@ -1,24 +0,0 @@ -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 deleted file mode 100644 index 5d65532a..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Reflection/SymbolReader.cs +++ /dev/null @@ -1,43 +0,0 @@ -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 deleted file mode 100644 index 36ceaae9..00000000 --- a/src/Machine.Specifications.Runner.VisualStudio/Reflection/TypeData.cs +++ /dev/null @@ -1,85 +0,0 @@ -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 deleted file mode 100644 index 20b80d3430e850219762fe58ef24d16cb422e32a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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&!