diff --git a/xunit.runner.visualstudio.nuspec b/xunit.runner.visualstudio.nuspec index 8a075f52..10ab5bcc 100644 --- a/xunit.runner.visualstudio.nuspec +++ b/xunit.runner.visualstudio.nuspec @@ -24,6 +24,9 @@ Supported platforms: + + + diff --git a/xunit.runner.visualstudio.testadapter/Sinks/VsDiscoverySink.cs b/xunit.runner.visualstudio.testadapter/Sinks/VsDiscoverySink.cs index 15abbd34..fb647fe9 100644 --- a/xunit.runner.visualstudio.testadapter/Sinks/VsDiscoverySink.cs +++ b/xunit.runner.visualstudio.testadapter/Sinks/VsDiscoverySink.cs @@ -6,6 +6,8 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Xunit.Abstractions; +using Xunit.Sdk; +using Newtonsoft.Json; #if PLATFORM_DOTNET using System.Reflection; @@ -65,14 +67,32 @@ public static TestCase CreateVsTestCase(string source, ITestFrameworkDiscoverer { try { - var serializedTestCase = discoverer.Serialize(xunitTestCase); var fqTestMethodName = $"{xunitTestCase.TestMethod.TestClass.Class.Name}.{xunitTestCase.TestMethod.Method.Name}"; var uniqueName = forceUniqueNames ? $"{fqTestMethodName} ({xunitTestCase.UniqueID})" : fqTestMethodName; var result = new TestCase(uniqueName, uri, source) { DisplayName = Escape(xunitTestCase.DisplayName) }; - result.SetPropertyValue(VsTestRunner.SerializedTestCaseProperty, serializedTestCase); + + if (VsTestRunner.AppDomain != AppDomainSupport.Denied) + { + var serializedTestCase = discoverer.Serialize(xunitTestCase); + result.SetPropertyValue(VsTestRunner.SerializedTestCaseProperty, serializedTestCase); + } result.Id = GuidFromString(uri + xunitTestCase.UniqueID); + if (xunitTestCase.TestMethodArguments != null) + { + var serializedTestArguments = JsonConvert.SerializeObject(xunitTestCase.TestMethodArguments, new JsonSerializerSettings + { + TypeNameHandling = TypeNameHandling.Auto + }); + result.SetPropertyValue(VsTestRunner.SerializedTestCaseArgumentProperty, serializedTestArguments); + } + + if (xunitTestCase is XunitTheoryTestCase) + { + result.SetPropertyValue(VsTestRunner.TheoryAgrumentProperty, "Theory"); + } + if (addTraitThunk != null) { foreach (var key in xunitTestCase.Traits.Keys) diff --git a/xunit.runner.visualstudio.testadapter/Utility/AssemblyRunInfo.cs b/xunit.runner.visualstudio.testadapter/Utility/AssemblyRunInfo.cs index 13c4a02c..95b25698 100644 --- a/xunit.runner.visualstudio.testadapter/Utility/AssemblyRunInfo.cs +++ b/xunit.runner.visualstudio.testadapter/Utility/AssemblyRunInfo.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Xunit.Abstractions; namespace Xunit.Runner.VisualStudio { @@ -8,5 +9,6 @@ public class AssemblyRunInfo public string AssemblyFileName; public TestAssemblyConfiguration Configuration; public IList TestCases; + public Dictionary TestCaseMap; } } diff --git a/xunit.runner.visualstudio.testadapter/VsTestRunner.cs b/xunit.runner.visualstudio.testadapter/VsTestRunner.cs index edbd7f1f..c4a44253 100644 --- a/xunit.runner.visualstudio.testadapter/VsTestRunner.cs +++ b/xunit.runner.visualstudio.testadapter/VsTestRunner.cs @@ -9,6 +9,9 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Xunit.Abstractions; +using System.Reflection; +using Xunit.Sdk; +using Newtonsoft.Json; #if !PLATFORM_DOTNET using System.Xml; @@ -24,11 +27,13 @@ namespace Xunit.Runner.VisualStudio.TestAdapter public class VsTestRunner : ITestDiscoverer, ITestExecutor { public static TestProperty SerializedTestCaseProperty = GetTestProperty(); + public static TestProperty SerializedTestCaseArgumentProperty = GetTestArgumentProperty(); + public static TestProperty TheoryAgrumentProperty = GetTheoryAgrumentProperty(); #if PLATFORM_DOTNET - static readonly AppDomainSupport AppDomainDefaultBehavior = AppDomainSupport.Denied; + internal static AppDomainSupport AppDomain = AppDomainSupport.Denied; #else - static readonly AppDomainSupport AppDomainDefaultBehavior = AppDomainSupport.Required; + internal static AppDomainSupport AppDomain = AppDomainSupport.Required; #endif static readonly HashSet platformAssemblies = new HashSet(StringComparer.OrdinalIgnoreCase) @@ -111,10 +116,16 @@ void ITestExecutor.RunTests(IEnumerable tests, IRunContext runContext, var stopwatch = Stopwatch.StartNew(); var logger = new LoggerHelper(frameworkHandle, stopwatch); + Dictionary testCaseMap = null; + if (AppDomain == AppDomainSupport.Denied) + { + testCaseMap = GetXunitTestCaseMap(tests); + } + RunTests( runContext, frameworkHandle, logger, () => tests.GroupBy(testCase => testCase.Source) - .Select(group => new AssemblyRunInfo { AssemblyFileName = group.Key, Configuration = LoadConfiguration(group.Key), TestCases = group.ToList() }) + .Select(group => new AssemblyRunInfo { AssemblyFileName = group.Key, Configuration = LoadConfiguration(group.Key), TestCases = group.ToList(), TestCaseMap = testCaseMap }) .ToList() ); } @@ -257,6 +268,12 @@ static Stream GetConfigurationStreamForAssembly(string assemblyName) static TestProperty GetTestProperty() => TestProperty.Register("XunitTestCase", "xUnit.net Test Case", typeof(string), typeof(VsTestRunner)); + static TestProperty GetTestArgumentProperty() + => TestProperty.Register("XunitTestCaseArgument", "xUnit.net Test Case Argument", typeof(string), typeof(object[])); + + static TestProperty GetTheoryAgrumentProperty() + => TestProperty.Register("TheoryAgrument", "xUnit.net Theory Argument", typeof(string), typeof(object[])); + List GetTests(IEnumerable sources, LoggerHelper logger, IRunContext runContext) { // For store apps, the files are copied to the AppX dir, we need to load it from there @@ -280,16 +297,45 @@ List GetTests(IEnumerable sources, LoggerHelper logger, vsFilteredTestCases = filterHelper.GetFilteredTestList(vsFilteredTestCases, runContext, logger, source).ToList(); // Re-create testcases with unique names if there is more than 1 - var testCases = visitor.TestCases.Where(tc => vsFilteredTestCases.Any(vsTc => vsTc.DisplayName == tc.DisplayName)) - .GroupBy(tc => $"{tc.TestMethod.TestClass.Class.Name}.{tc.TestMethod.Method.Name}") - .SelectMany(group => group.Select(testCase => VsDiscoverySink.CreateVsTestCase(source, discoverer, testCase, forceUniqueNames: group.Count() > 1, logger: logger, knownTraitNames: knownTraitNames)) - .Where(vsTestCase => vsTestCase != null)).ToList(); // pre-enumerate these as it populates the known trait names collection + Dictionary testCaseMap = null; + List testCases = null; + + if (AppDomain == AppDomainSupport.Denied) + { + testCaseMap = visitor.TestCases.Where(tc => vsFilteredTestCases.Any(vsTc => vsTc.DisplayName == tc.DisplayName)).GroupBy(tc => $"{tc.TestMethod.TestClass.Class.Name}.{tc.TestMethod.Method.Name}") + .SelectMany(group => group.Select((testCase, i) => new + { + testCase, + v = VsDiscoverySink.CreateVsTestCase( + source, + discoverer, + testCase, + forceUniqueNames: group.Count() > 1, + logger: logger, + knownTraitNames: knownTraitNames) + })).ToDictionary(x => x.testCase, x => x.v); + + } + else + { + testCases = visitor.TestCases.Where(tc => vsFilteredTestCases.Any(vsTc => vsTc.DisplayName == tc.DisplayName)).GroupBy(tc => $"{tc.TestMethod.TestClass.Class.Name}.{tc.TestMethod.Method.Name}") + .SelectMany(group => group.Select(testCase => + VsDiscoverySink.CreateVsTestCase( + source, + discoverer, + testCase, + forceUniqueNames: group.Count() > 1, + logger: logger, + knownTraitNames: knownTraitNames)) + .Where(vsTestCase => vsTestCase != null)).ToList(); // pre-enumerate these as it populates the known trait names collection + } var runInfo = new AssemblyRunInfo { AssemblyFileName = source, Configuration = LoadConfiguration(source), - TestCases = testCases + TestCases = testCases, + TestCaseMap = testCaseMap }; result.Add(runInfo); } @@ -377,9 +423,18 @@ void RunTestsInAssembly(IRunContext runContext, var diagnosticMessageVisitor = new DiagnosticMessageSink(logger, assemblyDisplayName, runInfo.Configuration.DiagnosticMessagesOrDefault); using (var controller = new XunitFrontController(appDomain, assemblyFileName: assemblyFileName, configFileName: null, shadowCopy: shadowCopy, diagnosticMessageSink: diagnosticMessageVisitor)) { - var xunitTestCases = runInfo.TestCases.Select(tc => new { vs = tc, xunit = Deserialize(logger, controller, tc) }) - .Where(tc => tc.xunit != null) - .ToDictionary(tc => tc.xunit, tc => tc.vs); + Dictionary xunitTestCases; + if (AppDomain == AppDomainSupport.Denied) + { + xunitTestCases = runInfo.TestCaseMap; + } + else + { + xunitTestCases = runInfo.TestCases.Select(tc => new { vs = tc, xunit = Deserialize(logger, controller, tc) }) + .Where(tc => tc.xunit != null) + .ToDictionary(tc => tc.xunit, tc => tc.vs); + } + var executionOptions = TestFrameworkOptions.ForExecution(runInfo.Configuration); executionOptions.SetSynchronousMessageReporting(true); @@ -452,6 +507,111 @@ IEnumerator IEnumerable.GetEnumerator() } } + Dictionary GetXunitTestCaseMap(IEnumerable tests) + { + // -- Get the xunit ITestCase from TestCase + Dictionary testcaselist = new Dictionary(); + + var dict = new Dictionary(); + foreach (TestCase test in tests) + { + var asmName = test.Source.Substring(test.Source.LastIndexOf('\\') + 1, test.Source.LastIndexOf('.') - test.Source.LastIndexOf('\\') - 1); + Assembly asm = null; + if (!dict.ContainsKey(asmName)) + { +#if PLATFORM_DOTNET + // todo : revisit this + asm = Assembly.Load(new AssemblyName(asmName)); +#else + asm = Assembly.LoadFrom(test.Source); +#endif + dict.Add(asmName, asm); + } + else + { + asm = dict[asmName]; + } + + IAssemblyInfo assemblyInfo = new ReflectionAssemblyInfo(asm); + ITestAssembly testAssembly = new TestAssembly(assemblyInfo); + + var lastIndexOfDot = test.FullyQualifiedName.LastIndexOf('.'); + var testname = test.FullyQualifiedName.Split(' ')[0].Substring(lastIndexOfDot + 1); + var classname = test.FullyQualifiedName.Substring(0, lastIndexOfDot); + + var methodClass = asm.GetType(classname); + + ITypeInfo typeInfo = new ReflectionTypeInfo(methodClass); + TestCollection testCollection = new TestCollection(testAssembly, typeInfo, "Test collection for " + classname); + TestClass tc = new TestClass(testCollection, typeInfo); + + //IMethodInfo +#if PLATFORM_DOTNET + var methodinfos = methodClass.GetRuntimeMethods().ToList(); +#else + var methodinfos = methodClass.GetMethods().ToList(); +#endif + + XunitTestCase xunitTestCase = null; + var serializedTestArgs = test.GetPropertyValue(SerializedTestCaseArgumentProperty, null); + + + Object[] testarguments = serializedTestArgs == null ? null : JsonConvert.DeserializeObject(serializedTestArgs, new JsonSerializerSettings + { + TypeNameHandling = TypeNameHandling.Auto + }); + + if (testarguments != null) + { + testname = testname.Split('(')[0]; + } + + + MethodInfo methodinfo = null; + for (int i = 0; i < methodinfos.Count(); i++) + { + if (methodinfos[i].Name.Equals(testname)) + { + methodinfo = methodinfos[i]; + break; + } + } + + ReflectionMethodInfo refMethodInfo = new ReflectionMethodInfo(methodinfo); + ITestMethod method = new TestMethod(tc, refMethodInfo); + NullMessageSink sink = new NullMessageSink(); + + if (testarguments == null && !string.IsNullOrEmpty(test.GetPropertyValue(TheoryAgrumentProperty, null))) + { +#if !PLATFORM_DOTNET + Type type = typeof(XunitTheoryTestCase); + ConstructorInfo ctor = (ConstructorInfo)type.GetConstructors().GetValue(1); + xunitTestCase = (XunitTheoryTestCase)ctor.Invoke(new object[] { sink, 1, method }); +#else + xunitTestCase = new XunitTheoryTestCase(sink, Xunit.Sdk.TestMethodDisplay.ClassAndMethod, method); + +#endif + } + else + { +#if !PLATFORM_DOTNET + Type type = typeof(XunitTestCase); + ConstructorInfo ctor = (ConstructorInfo)type.GetConstructors().GetValue(1); + xunitTestCase = (XunitTestCase)ctor.Invoke(new object[] { sink, 1, method, testarguments }); +#else + xunitTestCase = new XunitTestCase(sink, Xunit.Sdk.TestMethodDisplay.ClassAndMethod, method, testarguments); +#endif + } + + xunitTestCase.SourceInformation = new SourceInformation(); + xunitTestCase.SourceInformation.FileName = test.CodeFilePath; + xunitTestCase.SourceInformation.LineNumber = test.LineNumber; + + testcaselist.Add(xunitTestCase, test); + } + return testcaselist; + } + bool DisableAppDomainRequestedInRunContext(string settingsXml) { #if !PLATFORM_DOTNET diff --git a/xunit.runner.visualstudio.testadapter/packages.config b/xunit.runner.visualstudio.testadapter/packages.config index 74e0228f..2f3d955a 100644 --- a/xunit.runner.visualstudio.testadapter/packages.config +++ b/xunit.runner.visualstudio.testadapter/packages.config @@ -1,6 +1,10 @@  + + + + \ No newline at end of file diff --git a/xunit.runner.visualstudio.testadapter/xunit.runner.visualstudio.testadapter.csproj b/xunit.runner.visualstudio.testadapter/xunit.runner.visualstudio.testadapter.csproj index 77a8ee2f..18b72669 100644 --- a/xunit.runner.visualstudio.testadapter/xunit.runner.visualstudio.testadapter.csproj +++ b/xunit.runner.visualstudio.testadapter/xunit.runner.visualstudio.testadapter.csproj @@ -9,7 +9,7 @@ Properties Xunit.Runner.VisualStudio.TestAdapter xunit.runner.visualstudio.testadapter - v3.5 + v4.5 512 @@ -39,6 +39,10 @@ False ..\packages\Microsoft.VisualStudio.TestPlatform.ObjectModel.0.0.6\lib\net35\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + + ..\packages\Newtonsoft.Json.9.0.1\lib\net35\Newtonsoft.Json.dll + True + @@ -46,6 +50,14 @@ ..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll True + + ..\packages\xunit.extensibility.core.2.2.0-beta3-build3335\lib\net45\xunit.core.dll + True + + + ..\packages\xunit.extensibility.execution.2.2.0-beta3-build3335\lib\net45\xunit.execution.desktop.dll + True + ..\packages\xunit.runner.utility.2.2.0-beta3-build3335\lib\net35\xunit.runner.utility.desktop.dll True @@ -72,7 +84,7 @@ - +