diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..86468ad9 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,49 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Build", + "type": "process", + "command": "dotnet", + "args": [ + "run", + "--project", + "tools/builder", + "--no-launch-profile", + "--", + "--configuration", + "Debug", + "Build" + ], + "options": { + "cwd": "${workspaceRoot}" + }, + "group": "build", + "presentation": { + "focus": true + }, + "problemMatcher": "$msCompile" + }, + { + "label": "Pre-PR Build", + "type": "process", + "command": "dotnet", + "args": [ + "run", + "--project", + "tools/builder", + "--no-launch-profile", + "--", + "BuildAll" + ], + "options": { + "cwd": "${workspaceRoot}" + }, + "group": "build", + "presentation": { + "focus": true + }, + "problemMatcher": "$msCompile" + }, + ] +} diff --git a/Directory.build.props b/Directory.build.props index a8fa64d4..be27ab51 100644 --- a/Directory.build.props +++ b/Directory.build.props @@ -14,8 +14,6 @@ https://github.com/xunit/visualstudio.xunit embedded latest - true - $(MSBuildThisFileDirectory)src/signing.snk diff --git a/Versions.props b/Versions.props index f508f527..abd9b268 100644 --- a/Versions.props +++ b/Versions.props @@ -3,14 +3,14 @@ 2.0.13 6.0.11 - 17.7.0 + 17.7.2 1.1.1 - 17.7.0 + $(MicrosoftNetTestSdkVersion) 3.6.133 - 5.0.0 + 5.1.0 1.0.0-alpha.160 - 1.3.0 - 2.5.1 + 1.4.0 + 2.5.2 diff --git a/src/signing.snk b/src/signing.snk deleted file mode 100644 index 93641b97..00000000 Binary files a/src/signing.snk and /dev/null differ diff --git a/src/xunit.runner.visualstudio/Sinks/VsDiscoverySink.cs b/src/xunit.runner.visualstudio/Sinks/VsDiscoverySink.cs index 07de6d39..8eca04d5 100644 --- a/src/xunit.runner.visualstudio/Sinks/VsDiscoverySink.cs +++ b/src/xunit.runner.visualstudio/Sinks/VsDiscoverySink.cs @@ -86,7 +86,7 @@ public void Dispose() result.CodeFilePath = descriptor.SourceFileName; result.LineNumber = descriptor.SourceLineNumber.GetValueOrDefault(); - if (addTraitThunk != null) + if (addTraitThunk is not null) { var traits = descriptor.Traits; @@ -106,7 +106,7 @@ public void Dispose() static string Escape(string value) { - if (value == null) + if (value is null) return string.Empty; return Truncate(value.Replace("\r", "\\r").Replace("\n", "\\n").Replace("\t", "\\t")); @@ -138,7 +138,7 @@ public int Finish() #else var property = testCaseType.GetProperty("Traits"); #endif - if (property == null) + if (property is null) return null; #if NETCOREAPP @@ -146,7 +146,7 @@ public int Finish() #else var method = property.PropertyType.GetMethod("Add", new[] { typeof(string), typeof(string) }); #endif - if (method == null) + if (method is null) return null; var thisParam = Expression.Parameter(testCaseType, "this"); @@ -210,7 +210,7 @@ private void SendExistingTestCases() foreach (var descriptor in descriptors) { var vsTestCase = CreateVsTestCase(source, descriptor, logger, testPlatformContext); - if (vsTestCase != null && testCaseFilter.MatchTestCase(vsTestCase)) + if (vsTestCase is not null && testCaseFilter.MatchTestCase(vsTestCase)) { if (discoveryOptions.GetInternalDiagnosticMessagesOrDefault()) logger.LogWithSource(source, "Discovered test case '{0}' (ID = '{1}', VS FQN = '{2}')", descriptor.DisplayName, descriptor.UniqueID, vsTestCase.FullyQualifiedName); diff --git a/src/xunit.runner.visualstudio/Sinks/VsExecutionSink.cs b/src/xunit.runner.visualstudio/Sinks/VsExecutionSink.cs index ccff00f5..07324952 100644 --- a/src/xunit.runner.visualstudio/Sinks/VsExecutionSink.cs +++ b/src/xunit.runner.visualstudio/Sinks/VsExecutionSink.cs @@ -114,7 +114,7 @@ void HandleTestFailed(MessageHandlerArgs args) { var testFailed = args.Message; var result = MakeVsTestResult(TestOutcome.Failed, testFailed); - if (result != null) + if (result is not null) { result.ErrorMessage = ExceptionUtility.CombineMessages(testFailed); result.ErrorStackTrace = ExceptionUtility.CombineStackTraces(testFailed); @@ -131,7 +131,7 @@ void HandleTestPassed(MessageHandlerArgs args) { var testPassed = args.Message; var result = MakeVsTestResult(TestOutcome.Passed, testPassed); - if (result != null) + if (result is not null) TryAndReport("RecordResult (Pass)", testPassed.TestCase, () => recorder.RecordResult(result)); else logger.LogWarning(testPassed.TestCase, "(Pass) Could not find VS test case for {0} (ID = {1})", testPassed.TestCase.DisplayName, testPassed.TestCase.UniqueID); @@ -143,7 +143,7 @@ void HandleTestSkipped(MessageHandlerArgs args) { var testSkipped = args.Message; var result = MakeVsTestResult(TestOutcome.Skipped, testSkipped); - if (result != null) + if (result is not null) TryAndReport("RecordResult (Skip)", testSkipped.TestCase, () => recorder.RecordResult(result)); else logger.LogWarning(testSkipped.TestCase, "(Skip) Could not find VS test case for {0} (ID = {1})", testSkipped.TestCase.DisplayName, testSkipped.TestCase.UniqueID); @@ -155,7 +155,7 @@ void HandleTestCaseStarting(MessageHandlerArgs args) { var testCaseStarting = args.Message; var vsTestCase = FindTestCase(testCaseStarting.TestCase); - if (vsTestCase != null) + if (vsTestCase is not null) TryAndReport("RecordStart", testCaseStarting.TestCase, () => recorder.RecordStart(vsTestCase)); else logger.LogWarning(testCaseStarting.TestCase, "(Starting) Could not find VS test case for {0} (ID = {1})", testCaseStarting.TestCase.DisplayName, testCaseStarting.TestCase.UniqueID); @@ -167,7 +167,7 @@ void HandleTestCaseFinished(MessageHandlerArgs args) { var testCaseFinished = args.Message; var vsTestCase = FindTestCase(testCaseFinished.TestCase); - if (vsTestCase != null) + if (vsTestCase is not null) TryAndReport("RecordEnd", testCaseFinished.TestCase, () => recorder.RecordEnd(vsTestCase, GetAggregatedTestOutcome(testCaseFinished))); else logger.LogWarning(testCaseFinished.TestCase, "(Finished) Could not find VS test case for {0} (ID = {1})", testCaseFinished.TestCase.DisplayName, testCaseFinished.TestCase.UniqueID); @@ -243,7 +243,7 @@ void WriteError( foreach (var testCase in testCases) { var result = MakeVsTestResult(TestOutcome.Failed, testCase, testCase.DisplayName); - if (result != null) + if (result is not null) { result.ErrorMessage = $"[{failureName}]: {ExceptionUtility.CombineMessages(failureInfo)}"; result.ErrorStackTrace = ExceptionUtility.CombineStackTraces(failureInfo); @@ -275,7 +275,7 @@ void WriteError( string? errorMessage = null) { var vsTestCase = FindTestCase(testCase); - if (vsTestCase == null) + if (vsTestCase is null) return null; var result = new VsTestResult(vsTestCase) diff --git a/src/xunit.runner.visualstudio/Utility/AppDomainManager.cs b/src/xunit.runner.visualstudio/Utility/AppDomainManager.cs new file mode 100644 index 00000000..96722ca2 --- /dev/null +++ b/src/xunit.runner.visualstudio/Utility/AppDomainManager.cs @@ -0,0 +1,73 @@ +#if NETFRAMEWORK + +using System; +using System.IO; +using System.Reflection; +using System.Runtime.ExceptionServices; +using System.Security; +using System.Security.Permissions; +using Xunit.Internal; + +namespace Xunit.Runner.VisualStudio; + +class AppDomainManager +{ + readonly AppDomain appDomain; + + public AppDomainManager(string assemblyFileName) + { + Guard.ArgumentNotNullOrEmpty(assemblyFileName); + + assemblyFileName = Path.GetFullPath(assemblyFileName); + Guard.FileExists(assemblyFileName); + + var applicationBase = Path.GetDirectoryName(assemblyFileName); + var applicationName = Guid.NewGuid().ToString(); + var setup = new AppDomainSetup + { + ApplicationBase = applicationBase, + ApplicationName = applicationName, + ShadowCopyFiles = "true", + ShadowCopyDirectories = applicationBase, + CachePath = Path.Combine(Path.GetTempPath(), applicationName) + }; + + appDomain = AppDomain.CreateDomain(Path.GetFileNameWithoutExtension(assemblyFileName), AppDomain.CurrentDomain.Evidence, setup, new PermissionSet(PermissionState.Unrestricted)); + } + + public TObject? CreateObject( + AssemblyName assemblyName, + string typeName, + params object[] args) + where TObject : class + { + try + { + return appDomain.CreateInstanceAndUnwrap(assemblyName.FullName, typeName, false, BindingFlags.Default, null, args, null, null) as TObject; + } + catch (TargetInvocationException ex) + { + ExceptionDispatchInfo.Capture(ex.InnerException ?? ex).Throw(); + return default; // Will never reach here, but the compiler doesn't know that + } + } + + public virtual void Dispose() + { + if (appDomain is not null) + { + var cachePath = appDomain.SetupInformation.CachePath; + + try + { + AppDomain.Unload(appDomain); + + if (cachePath is not null) + Directory.Delete(cachePath, true); + } + catch { } + } + } +} + +#endif diff --git a/src/xunit.runner.visualstudio/Utility/AssemblyExtensions.cs b/src/xunit.runner.visualstudio/Utility/AssemblyExtensions.cs index 5cf731d4..3da97790 100644 --- a/src/xunit.runner.visualstudio/Utility/AssemblyExtensions.cs +++ b/src/xunit.runner.visualstudio/Utility/AssemblyExtensions.cs @@ -8,7 +8,7 @@ internal static class AssemblyExtensions { #if NETFRAMEWORK string? codeBase = assembly.CodeBase; - if (codeBase == null) + if (codeBase is null) return null; if (!codeBase.StartsWith("file:///")) diff --git a/src/xunit.runner.visualstudio/Utility/AssemblyResolution/AssemblyHelper_Desktop.cs b/src/xunit.runner.visualstudio/Utility/AssemblyResolution/AssemblyHelper_Desktop.cs index ad6f1c86..d3be0d94 100644 --- a/src/xunit.runner.visualstudio/Utility/AssemblyResolution/AssemblyHelper_Desktop.cs +++ b/src/xunit.runner.visualstudio/Utility/AssemblyResolution/AssemblyHelper_Desktop.cs @@ -56,9 +56,9 @@ public void Dispose() => var path = Path.Combine(directory, assemblyName); result = ResolveAndLoadAssembly(path, out var resolvedAssemblyPath); - if (internalDiagnosticsMessageSink != null) + if (internalDiagnosticsMessageSink is not null) { - if (result == null) + if (result is null) internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[AssemblyHelper_Desktop.LoadAssembly] Resolution for '{assemblyName}' failed, passed down to next resolver")); else internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[AssemblyHelper_Desktop.LoadAssembly] Resolved '{assemblyName}' to '{resolvedAssemblyPath}'")); diff --git a/src/xunit.runner.visualstudio/Utility/AssemblyResolution/AssemblyHelper_NetCoreApp.cs b/src/xunit.runner.visualstudio/Utility/AssemblyResolution/AssemblyHelper_NetCoreApp.cs index 460e8a1f..ba9dab3c 100644 --- a/src/xunit.runner.visualstudio/Utility/AssemblyResolution/AssemblyHelper_NetCoreApp.cs +++ b/src/xunit.runner.visualstudio/Utility/AssemblyResolution/AssemblyHelper_NetCoreApp.cs @@ -33,21 +33,21 @@ public AssemblyHelper( } var assembly = LoadFromAssemblyPath(assemblyFileName); - if (assembly == null) + if (assembly is null) { internalDiagnosticsMessageSink?.OnMessage(new _DiagnosticMessage($"[AssemblyHelper_NetCoreApp..ctor] Assembly file could not be loaded: '{assemblyFileName}'")); return; } var dependencyContext = DependencyContext.Load(assembly); - if (dependencyContext == null) + if (dependencyContext is null) { internalDiagnosticsMessageSink?.OnMessage(new _DiagnosticMessage($"[AssemblyHelper_NetCoreApp..ctor] Assembly file does not contain dependency manifest: '{assemblyFileName}'")); return; } var assemblyFolder = Path.GetDirectoryName(assemblyFileName); - if (assemblyFolder == null) + if (assemblyFolder is null) { internalDiagnosticsMessageSink?.OnMessage(new _DiagnosticMessage($"[AssemblyHelper_NetCoreApp..ctor] Assembly file does not have an associated folder to watch: '{assemblyFileName}'")); return; @@ -61,7 +61,7 @@ public AssemblyHelper( /// public void Dispose() { - if (assemblyCache != null) + if (assemblyCache is not null) Default.Resolving -= OnResolving; } @@ -74,7 +74,7 @@ protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) { var result = default(IntPtr); - if (assemblyCache != null) + if (assemblyCache is not null) result = assemblyCache.LoadUnmanagedLibrary(unmanagedDllName, LoadUnmanagedDllFromPath); if (result == default) @@ -87,7 +87,7 @@ protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) AssemblyLoadContext context, AssemblyName name) { - if (assemblyCache == null || name.Name == null) + if (assemblyCache is null || name.Name is null) return null; return assemblyCache.LoadManagedDll(name.Name, LoadFromAssemblyPath); diff --git a/src/xunit.runner.visualstudio/Utility/AssemblyResolution/DependencyContextAssemblyCache.cs b/src/xunit.runner.visualstudio/Utility/AssemblyResolution/DependencyContextAssemblyCache.cs index b41c9959..7398ba95 100644 --- a/src/xunit.runner.visualstudio/Utility/AssemblyResolution/DependencyContextAssemblyCache.cs +++ b/src/xunit.runner.visualstudio/Utility/AssemblyResolution/DependencyContextAssemblyCache.cs @@ -64,13 +64,13 @@ public DependencyContextAssemblyCache( .Select(lib => compatibleRuntimes .Select(runtime => Tuple.Create(lib, lib.RuntimeAssemblyGroups.FirstOrDefault(libGroup => string.Equals(libGroup.Runtime, runtime)))) - .FirstOrDefault(tuple => tuple.Item2?.AssetPaths != null) + .FirstOrDefault(tuple => tuple.Item2?.AssetPaths is not null) ) .WhereNotNull() - .Where(tuple => tuple.Item2 != null) + .Where(tuple => tuple.Item2 is not null) .SelectMany(tuple => tuple.Item2!.AssetPaths - .Where(x => x != null) + .WhereNotNull() .Select(path => Tuple.Create(Path.GetFileNameWithoutExtension(path), Tuple.Create(tuple.Item1, tuple.Item2))) ) .ToDictionaryIgnoringDuplicateKeys(tuple => tuple.Item1, tuple => tuple.Item2, StringComparer.OrdinalIgnoreCase); @@ -85,13 +85,13 @@ public DependencyContextAssemblyCache( .Select(lib => compatibleRuntimes .Select(runtime => Tuple.Create(lib, lib.NativeLibraryGroups.FirstOrDefault(libGroup => string.Equals(libGroup.Runtime, runtime)))) - .FirstOrDefault(tuple => tuple.Item2?.AssetPaths != null) + .FirstOrDefault(tuple => tuple.Item2?.AssetPaths is not null) ) .WhereNotNull() - .Where(tuple => tuple.Item2 != null) + .Where(tuple => tuple.Item2 is not null) .SelectMany(tuple => tuple.Item2!.AssetPaths - .Where(x => x != null) + .WhereNotNull() .Select(path => Tuple.Create(Path.GetFileName(path), Tuple.Create(tuple.Item1, tuple.Item2))) ) .ToDictionaryIgnoringDuplicateKeys(tuple => tuple.Item1, tuple => tuple.Item2, StringComparer.OrdinalIgnoreCase); @@ -160,9 +160,9 @@ IEnumerable GetUnmanagedDllFormats() result = tupleResult.Item2; managedAssemblyCache[assemblyName] = result; - if (internalDiagnosticsMessageSink != null) + if (internalDiagnosticsMessageSink is not null) { - if (result == null) + if (result is null) internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.LoadManagedDll] Resolution for '{assemblyName}' failed, passed down to next resolver")); else internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.LoadManagedDll] Resolved '{assemblyName}' to '{resolvedAssemblyPath}'")); @@ -186,15 +186,15 @@ public IntPtr LoadUnmanagedLibrary( needDiagnostics = true; } - if (resolvedAssemblyPath != null) + if (resolvedAssemblyPath is not null) result = unmanagedAssemblyLoader(resolvedAssemblyPath); - if (needDiagnostics && internalDiagnosticsMessageSink != null) + if (needDiagnostics && internalDiagnosticsMessageSink is not null) if (result != default) internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.LoadUnmanagedLibrary] Resolved '{unmanagedLibraryName}' to '{resolvedAssemblyPath}'")); else { - if (resolvedAssemblyPath != null) + if (resolvedAssemblyPath is not null) internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.LoadUnmanagedLibrary] Resolving '{unmanagedLibraryName}', found assembly path '{resolvedAssemblyPath}' but the assembly would not load")); internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.LoadUnmanagedLibrary] Resolution for '{unmanagedLibraryName}' failed, passed down to next resolver")); @@ -217,7 +217,7 @@ public IntPtr LoadUnmanagedLibrary( if (fileSystem.File.Exists(resolvedAssemblyPath)) { var assembly = managedAssemblyLoader(resolvedAssemblyPath); - if (assembly != null) + if (assembly is not null) return Tuple.Create(resolvedAssemblyPath, assembly); } } @@ -234,12 +234,12 @@ public IntPtr LoadUnmanagedLibrary( if (assemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies)) { var resolvedAssemblyPath = assemblies.FirstOrDefault(a => string.Equals(assemblyName, Path.GetFileNameWithoutExtension(a), StringComparison.OrdinalIgnoreCase)); - if (resolvedAssemblyPath != null) + if (resolvedAssemblyPath is not null) { resolvedAssemblyPath = Path.GetFullPath(resolvedAssemblyPath); var assembly = managedAssemblyLoader(resolvedAssemblyPath); - if (assembly != null) + if (assembly is not null) return Tuple.Create(resolvedAssemblyPath, assembly); internalDiagnosticsMessageSink?.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.ResolveManagedAssembly] Resolving '{assemblyName}', found assembly path '{resolvedAssemblyPath}' but the assembly would not load")); @@ -274,7 +274,7 @@ public IntPtr LoadUnmanagedLibrary( if (assemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies)) { var resolvedAssemblyPath = assemblies.FirstOrDefault(a => string.Equals(formattedUnmanagedDllName, Path.GetFileName(a), StringComparison.OrdinalIgnoreCase)); - if (resolvedAssemblyPath != null) + if (resolvedAssemblyPath is not null) return Path.GetFullPath(resolvedAssemblyPath); internalDiagnosticsMessageSink?.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.ResolveUnmanagedLibrary] Found a resolved path, but could not map a filename in [{string.Join(",", assemblies.OrderBy(k => k, StringComparer.OrdinalIgnoreCase).Select(k => $"'{k}'"))}]")); diff --git a/src/xunit.runner.visualstudio/Utility/AssemblyResolution/_DiagnosticMessage.cs b/src/xunit.runner.visualstudio/Utility/AssemblyResolution/_DiagnosticMessage.cs index 4885036d..d242b0df 100644 --- a/src/xunit.runner.visualstudio/Utility/AssemblyResolution/_DiagnosticMessage.cs +++ b/src/xunit.runner.visualstudio/Utility/AssemblyResolution/_DiagnosticMessage.cs @@ -1,8 +1,9 @@ using Xunit.Abstractions; +using Xunit.Sdk; namespace Xunit; -class _DiagnosticMessage : IDiagnosticMessage +class _DiagnosticMessage : LongLivedMarshalByRefObject, IDiagnosticMessage { /// /// Initializes a new instance of the class. diff --git a/src/xunit.runner.visualstudio/Utility/DiaSessionWrapper.cs b/src/xunit.runner.visualstudio/Utility/DiaSessionWrapper.cs new file mode 100644 index 00000000..461ec671 --- /dev/null +++ b/src/xunit.runner.visualstudio/Utility/DiaSessionWrapper.cs @@ -0,0 +1,81 @@ +using System; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Navigation; +using Xunit.Abstractions; +using Xunit.Internal; + +namespace Xunit.Runner.VisualStudio.Utility; + +// This class wraps DiaSession, and uses DiaSessionWrapperHelper to discover when a test is an async test +// (since that requires special handling by DIA). The wrapper helper needs to exist in a separate AppDomain +// so that we can do discovery without locking the assembly under test (for .NET Framework). +class DiaSessionWrapper : IDisposable +{ +#if NETFRAMEWORK + readonly AppDomainManager? appDomainManager; +#endif + readonly DiaSessionWrapperHelper? helper; + readonly DiaSession? session; + readonly DiagnosticMessageSink diagnosticMessageSink; + + public DiaSessionWrapper( + string assemblyFileName, + DiagnosticMessageSink diagnosticMessageSink) + { + this.diagnosticMessageSink = Guard.ArgumentNotNull(diagnosticMessageSink); + + try + { + session = new DiaSession(assemblyFileName); + } + catch (Exception ex) + { + diagnosticMessageSink.OnMessage(new DiagnosticMessage($"Exception creating DiaSession: {ex}")); + } + + try + { +#if NETFRAMEWORK + var adapterFileName = typeof(DiaSessionWrapperHelper).Assembly.GetLocalCodeBase(); + if (adapterFileName is not null) + { + appDomainManager = new AppDomainManager(assemblyFileName); + helper = appDomainManager.CreateObject(typeof(DiaSessionWrapperHelper).Assembly.GetName(), typeof(DiaSessionWrapperHelper).FullName!, adapterFileName); + } +#else + helper = new DiaSessionWrapperHelper(assemblyFileName); +#endif + } + catch (Exception ex) + { + diagnosticMessageSink.OnMessage(new DiagnosticMessage($"Exception creating DiaSessionWrapperHelper: {ex}")); + } + } + + public INavigationData? GetNavigationData( + string typeName, + string methodName) + { + if (session is null || helper is null) + return null; + + try + { + helper.Normalize(ref typeName, ref methodName); + return session.GetNavigationDataForMethod(typeName, methodName); + } + catch (Exception ex) + { + diagnosticMessageSink.OnMessage(new DiagnosticMessage($"Exception getting source mapping for {typeName}.{methodName}: {ex}")); + return null; + } + } + + public void Dispose() + { + session?.Dispose(); +#if NETFRAMEWORK + appDomainManager?.Dispose(); +#endif + } +} diff --git a/src/xunit.runner.visualstudio/Utility/DiaSessionWrapperHelper.cs b/src/xunit.runner.visualstudio/Utility/DiaSessionWrapperHelper.cs new file mode 100644 index 00000000..dfc706a6 --- /dev/null +++ b/src/xunit.runner.visualstudio/Utility/DiaSessionWrapperHelper.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using Xunit.Sdk; + +namespace Xunit.Runner.VisualStudio.Utility; + +class DiaSessionWrapperHelper : LongLivedMarshalByRefObject +{ + readonly Assembly? assembly; + readonly Dictionary typeNameMap; + + public DiaSessionWrapperHelper(string assemblyFileName) + { + try + { +#if NETFRAMEWORK + assembly = Assembly.ReflectionOnlyLoadFrom(assemblyFileName); + var assemblyDirectory = Path.GetDirectoryName(assemblyFileName); + + if (assemblyDirectory is not null) + AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += (sender, args) => + { + try + { + // Try to load it normally + var name = AppDomain.CurrentDomain.ApplyPolicy(args.Name); + return Assembly.ReflectionOnlyLoad(name); + } + catch + { + try + { + // If a normal implicit load fails, try to load it from the directory that + // the test assembly lives in + return Assembly.ReflectionOnlyLoadFrom( + Path.Combine( + assemblyDirectory, + new AssemblyName(args.Name).Name + ".dll" + ) + ); + } + catch + { + // If all else fails, say we couldn't find it + return null; + } + } + }; +#else + assembly = Assembly.Load(new AssemblyName { Name = Path.GetFileNameWithoutExtension(assemblyFileName) }); +#endif + } + catch { } + + if (assembly is not null) + { + Type?[]? types = null; + + try + { + types = assembly.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + types = ex.Types; + } + catch { } // Ignore anything other than ReflectionTypeLoadException + + if (types is not null) + typeNameMap = + types + .WhereNotNull() + .Where(t => !string.IsNullOrEmpty(t.FullName)) + .ToDictionaryIgnoringDuplicateKeys(k => k.FullName!); + } + + typeNameMap ??= new(); + } + + public void Normalize( + ref string typeName, + ref string methodName) + { + try + { + if (assembly is null) + return; + + if (typeNameMap.TryGetValue(typeName, out var type) && type is not null) + { + var method = type.GetMethod(methodName); + if (method is not null && method.DeclaringType is not null && method.DeclaringType.FullName is not null) + { + // DiaSession only ever wants you to ask for the declaring type + typeName = method.DeclaringType.FullName; + + // See if this is an async method by looking for [AsyncStateMachine] on the method, + // which means we need to pass the state machine's "MoveNext" method. + var stateMachineType = method.GetCustomAttribute()?.StateMachineType; + if (stateMachineType is not null && stateMachineType.FullName is not null) + { + typeName = stateMachineType.FullName; + methodName = "MoveNext"; + } + } + } + } + catch { } + } +} diff --git a/src/xunit.runner.visualstudio/Utility/DictionaryExtensions.cs b/src/xunit.runner.visualstudio/Utility/DictionaryExtensions.cs index f59b1265..4c442130 100644 --- a/src/xunit.runner.visualstudio/Utility/DictionaryExtensions.cs +++ b/src/xunit.runner.visualstudio/Utility/DictionaryExtensions.cs @@ -3,6 +3,13 @@ static class DictionaryExtensions { + public static Dictionary ToDictionaryIgnoringDuplicateKeys( + this IEnumerable inputValues, + Func keySelector, + IEqualityComparer? comparer = null) + where TKey : notnull => + ToDictionaryIgnoringDuplicateKeys(inputValues, keySelector, x => x, comparer); + public static Dictionary ToDictionaryIgnoringDuplicateKeys( this IEnumerable inputValues, Func keySelector, diff --git a/src/xunit.runner.visualstudio/Utility/ExceptionExtensions.cs b/src/xunit.runner.visualstudio/Utility/ExceptionExtensions.cs index f21f28a8..d444ad88 100644 --- a/src/xunit.runner.visualstudio/Utility/ExceptionExtensions.cs +++ b/src/xunit.runner.visualstudio/Utility/ExceptionExtensions.cs @@ -22,7 +22,7 @@ public static Exception Unwrap(this Exception ex) { while (true) { - if (ex is not TargetInvocationException tiex || tiex.InnerException == null) + if (ex is not TargetInvocationException tiex || tiex.InnerException is null) return ex; ex = tiex.InnerException; diff --git a/src/xunit.runner.visualstudio/Utility/Guard.cs b/src/xunit.runner.visualstudio/Utility/Guard.cs index d012898b..235e68fa 100644 --- a/src/xunit.runner.visualstudio/Utility/Guard.cs +++ b/src/xunit.runner.visualstudio/Utility/Guard.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Runtime.CompilerServices; namespace Xunit.Internal; @@ -23,7 +24,7 @@ public static T ArgumentNotNull( [CallerArgumentExpression(nameof(argValue))] string? argName = null) where T : class { - if (argValue == null) + if (argValue is null) throw new ArgumentNullException(argName?.TrimStart('@')); return argValue; @@ -49,4 +50,38 @@ public static T ArgumentNotNullOrEmpty( return argValue; } + + /// + /// Ensures that an argument is valid. + /// + /// The exception message to use when the argument is not valid + /// The validity test value + /// The name of the argument + /// The argument value as a non-null value + /// Thrown when the argument is not valid + public static void ArgumentValid( + string message, + bool test, + string? argName = null) + { + if (!test) + throw new ArgumentException(message, argName); + } + + /// + /// Ensures that a filename argument is not null or empty, and that the file exists on disk. + /// + /// The file name value + /// The name of the argument + /// The file name as a non-null value + /// Thrown when the argument is null, empty, or not on disk + public static string FileExists( + [NotNull] string? fileName, + [CallerArgumentExpression(nameof(fileName))] string? argName = null) + { + ArgumentNotNullOrEmpty(fileName, argName); + ArgumentValid($"File not found: {fileName}", File.Exists(fileName), argName?.TrimStart('@')); + + return fileName; + } } diff --git a/src/xunit.runner.visualstudio/Utility/LoggerHelper.cs b/src/xunit.runner.visualstudio/Utility/LoggerHelper.cs index 5f1e2480..ca7b5080 100644 --- a/src/xunit.runner.visualstudio/Utility/LoggerHelper.cs +++ b/src/xunit.runner.visualstudio/Utility/LoggerHelper.cs @@ -21,7 +21,7 @@ public void Log( string format, params object?[] args) { - if (InnerLogger != null) + if (InnerLogger is not null) SendMessage(InnerLogger, TestMessageLevel.Informational, null, string.Format(format, args)); } @@ -30,7 +30,7 @@ public void Log( string format, params object?[] args) { - if (InnerLogger != null) + if (InnerLogger is not null) SendMessage(InnerLogger, TestMessageLevel.Informational, testCase.TestMethod.TestClass.TestCollection.TestAssembly.Assembly.AssemblyPath, string.Format(format, args)); } @@ -39,7 +39,7 @@ public void LogWithSource( string format, params object?[] args) { - if (InnerLogger != null) + if (InnerLogger is not null) SendMessage(InnerLogger, TestMessageLevel.Informational, source, string.Format(format, args)); } @@ -47,7 +47,7 @@ public void LogError( string format, params object?[] args) { - if (InnerLogger != null) + if (InnerLogger is not null) SendMessage(InnerLogger, TestMessageLevel.Error, null, string.Format(format, args)); } @@ -56,7 +56,7 @@ public void LogError( string format, params object?[] args) { - if (InnerLogger != null) + if (InnerLogger is not null) SendMessage(InnerLogger, TestMessageLevel.Error, testCase.TestMethod.TestClass.TestCollection.TestAssembly.Assembly.AssemblyPath, string.Format(format, args)); } @@ -65,7 +65,7 @@ public void LogErrorWithSource( string format, params object?[] args) { - if (InnerLogger != null) + if (InnerLogger is not null) SendMessage(InnerLogger, TestMessageLevel.Error, source, string.Format(format, args)); } @@ -73,7 +73,7 @@ public void LogWarning( string format, params object?[] args) { - if (InnerLogger != null) + if (InnerLogger is not null) SendMessage(InnerLogger, TestMessageLevel.Warning, null, string.Format(format, args)); } @@ -82,7 +82,7 @@ public void LogWarning( string format, params object?[] args) { - if (InnerLogger != null) + if (InnerLogger is not null) SendMessage(InnerLogger, TestMessageLevel.Warning, testCase.TestMethod.TestClass.TestCollection.TestAssembly.Assembly.AssemblyPath, string.Format(format, args)); } @@ -91,7 +91,7 @@ public void LogWarningWithSource( string format, params object?[] args) { - if (InnerLogger != null) + if (InnerLogger is not null) SendMessage(InnerLogger, TestMessageLevel.Warning, source, string.Format(format, args)); } @@ -101,7 +101,7 @@ void SendMessage( string? assemblyName, string message) { - var assemblyText = assemblyName == null ? "" : $"{Path.GetFileNameWithoutExtension(assemblyName)}: "; + var assemblyText = assemblyName is null ? "" : $"{Path.GetFileNameWithoutExtension(assemblyName)}: "; logger.SendMessage(level, $"[xUnit.net {Stopwatch.Elapsed:hh\\:mm\\:ss\\.ff}] {assemblyText}{message}"); } } diff --git a/src/xunit.runner.visualstudio/Utility/RunSettings.cs b/src/xunit.runner.visualstudio/Utility/RunSettings.cs index c8a4e6d1..7776396c 100644 --- a/src/xunit.runner.visualstudio/Utility/RunSettings.cs +++ b/src/xunit.runner.visualstudio/Utility/RunSettings.cs @@ -58,17 +58,17 @@ public static RunSettings Parse(string? settingsXml) { var result = new RunSettings(); - if (settingsXml != null) + if (settingsXml is not null) { try { var runSettingsElement = System.Xml.Linq.XDocument.Parse(settingsXml)?.Element("RunSettings"); - if (runSettingsElement != null) + if (runSettingsElement is not null) { // Custom settings for xUnit.net var xunitElement = runSettingsElement.Element("xUnit"); - if (xunitElement != null) + if (xunitElement is not null) { var appDomainString = xunitElement.Element(Constants.Xunit.AppDomain)?.Value; if (Enum.TryParse(appDomainString, ignoreCase: true, out var appDomain)) @@ -119,7 +119,7 @@ public static RunSettings Parse(string? settingsXml) result.PreEnumerateTheories = preEnumerateTheories; var reporterSwitchString = xunitElement.Element(Constants.Xunit.ReporterSwitch)?.Value; - if (reporterSwitchString != null) + if (reporterSwitchString is not null) result.ReporterSwitch = reporterSwitchString; var shadowCopyString = xunitElement.Element(Constants.Xunit.ShadowCopy)?.Value; @@ -133,7 +133,7 @@ public static RunSettings Parse(string? settingsXml) // Standard settings from VSTest, which can override the user's configured values var runConfigurationElement = runSettingsElement.Element("RunConfiguration"); - if (runConfigurationElement != null) + if (runConfigurationElement is not null) { var collectSourceInformationString = runConfigurationElement.Element(Constants.RunConfiguration.CollectSourceInformation)?.Value; if (bool.TryParse(collectSourceInformationString, out var collectSourceInformation)) @@ -158,7 +158,7 @@ public static RunSettings Parse(string? settingsXml) } var targetFrameworkVersionString = runConfigurationElement.Element(Constants.RunConfiguration.TargetFrameworkVersion)?.Value; - if (targetFrameworkVersionString != null) + if (targetFrameworkVersionString is not null) result.TargetFrameworkVersion = targetFrameworkVersionString; // These values are holdovers that we inappropriately shoved into RunConfiguration. The documentation will @@ -172,7 +172,7 @@ public static RunSettings Parse(string? settingsXml) result.NoAutoReporters = noAutoReporters; var reporterSwitchString = runConfigurationElement.Element(Constants.RunConfiguration.ReporterSwitch)?.Value; - if (reporterSwitchString != null) + if (reporterSwitchString is not null) result.ReporterSwitch = reporterSwitchString; } } diff --git a/src/xunit.runner.visualstudio/Utility/TestCaseFilter.cs b/src/xunit.runner.visualstudio/Utility/TestCaseFilter.cs index dcdb0e09..d10cfa46 100644 --- a/src/xunit.runner.visualstudio/Utility/TestCaseFilter.cs +++ b/src/xunit.runner.visualstudio/Utility/TestCaseFilter.cs @@ -50,7 +50,7 @@ public bool MatchTestCase(TestCase testCase) // Had an error while getting filter, match no testcase to ensure discovered test list is empty return false; } - else if (filterExpression == null) + else if (filterExpression is null) { // No filter specified, keep every testcase return true; @@ -155,7 +155,7 @@ bool GetTestCaseFilterExpressionFromDiscoveryContext( List GetSupportedPropertyNames() { // Returns the set of well-known property names usually used with the Test Plugins (Used Test Traits + DisplayName + FullyQualifiedName) - if (supportedPropertyNames == null) + if (supportedPropertyNames is null) { supportedPropertyNames = knownTraits.ToList(); supportedPropertyNames.Add(DisplayNameString); @@ -168,7 +168,7 @@ List GetSupportedPropertyNames() static IEnumerable> GetTraits(TestCase testCase) { var traitProperty = TestProperty.Find("TestObject.Traits"); - if (traitProperty != null) + if (traitProperty is not null) return testCase.GetPropertyValue(traitProperty, Enumerable.Empty>().ToArray()); return Enumerable.Empty>(); diff --git a/src/xunit.runner.visualstudio/Utility/VisualStudioSourceInformationProvider.cs b/src/xunit.runner.visualstudio/Utility/VisualStudioSourceInformationProvider.cs new file mode 100644 index 00000000..d80e26fc --- /dev/null +++ b/src/xunit.runner.visualstudio/Utility/VisualStudioSourceInformationProvider.cs @@ -0,0 +1,48 @@ +using Xunit.Abstractions; +using Xunit.Runner.VisualStudio.Utility; +using Xunit.Sdk; + +namespace Xunit.Runner.VisualStudio; + +/// +/// An implementation of that will provide source information +/// when running inside of Visual Studio (via the DiaSession class). +/// +public class VisualStudioSourceInformationProvider : LongLivedMarshalByRefObject, ISourceInformationProvider +{ + static readonly SourceInformation EmptySourceInformation = new(); + + readonly DiaSessionWrapper session; + + /// + /// Initializes a new instance of the class. + /// + /// The assembly file name. + /// The message sink to send internal diagnostic messages to. + public VisualStudioSourceInformationProvider( + string assemblyFileName, + DiagnosticMessageSink diagnosticMessageSink) + { + session = new DiaSessionWrapper(assemblyFileName, diagnosticMessageSink); + } + + /// + public ISourceInformation GetSourceInformation(ITestCase testCase) + { + var navData = session.GetNavigationData(testCase.TestMethod.TestClass.Class.Name, testCase.TestMethod.Method.Name); + if (navData is null || navData.FileName is null) + return EmptySourceInformation; + + return new SourceInformation + { + FileName = navData.FileName, + LineNumber = navData.MinLineNumber + }; + } + + /// + public void Dispose() + { + session.Dispose(); + } +} diff --git a/src/xunit.runner.visualstudio/VsTestRunner.cs b/src/xunit.runner.visualstudio/VsTestRunner.cs index 4f94f963..4c79e6cc 100644 --- a/src/xunit.runner.visualstudio/VsTestRunner.cs +++ b/src/xunit.runner.visualstudio/VsTestRunner.cs @@ -158,7 +158,7 @@ void ITestExecutor.RunTests( IRunContext? runContext, IFrameworkHandle? frameworkHandle) { - if (sources == null) + if (sources is null) return; var stopwatch = Stopwatch.StartNew(); @@ -187,7 +187,7 @@ void ITestExecutor.RunTests( IRunContext? runContext, IFrameworkHandle? frameworkHandle) { - if (tests == null) + if (tests is null) return; var stopwatch = Stopwatch.StartNew(); @@ -240,8 +240,9 @@ void DiscoverTests( var diagnosticSink = DiagnosticMessageSink.ForDiagnostics(logger, fileName, assembly.Configuration.DiagnosticMessagesOrDefault); var appDomain = assembly.Configuration.AppDomain ?? AppDomainDefaultBehavior; - using var framework = new XunitFrontController(appDomain, assembly.AssemblyFilename, shadowCopy: shadowCopy, diagnosticMessageSink: MessageSinkAdapter.Wrap(diagnosticSink)); - if (!DiscoverTestsInSource(framework, logger, testPlatformContext, runSettings, visitorFactory, visitComplete, assembly)) + using var sourceInformationProvider = new VisualStudioSourceInformationProvider(assembly.AssemblyFilename, diagnosticSink); + using var controller = new XunitFrontController(appDomain, assembly.AssemblyFilename, shadowCopy: shadowCopy, sourceInformationProvider: sourceInformationProvider, diagnosticMessageSink: MessageSinkAdapter.Wrap(diagnosticSink)); + if (!DiscoverTestsInSource(controller, logger, testPlatformContext, runSettings, visitorFactory, visitComplete, assembly)) break; } } @@ -330,7 +331,7 @@ static bool IsXunitTestAssembly(string assemblyFileName) return IsXunitPackageReferenced(assemblyFileName); #else var assemblyFolder = Path.GetDirectoryName(assemblyFileName); - if (assemblyFolder == null) + if (assemblyFolder is null) return false; return File.Exists(Path.Combine(assemblyFolder, "xunit.dll")) @@ -350,7 +351,7 @@ static bool IsXunitPackageReferenced(string assemblyFileName) using var stream = new MemoryStream(Encoding.UTF8.GetBytes(File.ReadAllText(depsFile))); var context = new DependencyContextJsonReader().Read(stream); var xunitLibrary = context.RuntimeLibraries.Where(lib => lib.Name.Equals("xunit") || lib.Name.Equals("xunit.core")).FirstOrDefault(); - return xunitLibrary != null; + return xunitLibrary is not null; } catch { @@ -427,10 +428,11 @@ void RunTestsInAssembly( var diagnosticSink = DiagnosticMessageSink.ForDiagnostics(logger, assemblyDisplayName, runInfo.Assembly.Configuration.DiagnosticMessagesOrDefault); var diagnosticMessageSink = MessageSinkAdapter.Wrap(diagnosticSink); - using var controller = new XunitFrontController(appDomain, assemblyFileName, shadowCopy: shadowCopy, diagnosticMessageSink: diagnosticMessageSink); + using var sourceInformationProvider = new VisualStudioSourceInformationProvider(assemblyFileName, diagnosticSink); + using var controller = new XunitFrontController(appDomain, assemblyFileName, shadowCopy: shadowCopy, sourceInformationProvider: sourceInformationProvider, diagnosticMessageSink: diagnosticMessageSink); var testCasesMap = new Dictionary(); var testCases = new List(); - if (runInfo.TestCases == null || !runInfo.TestCases.Any()) + if (runInfo.TestCases is null || !runInfo.TestCases.Any()) { // Discover tests var assemblyDiscoveredInfo = default(AssemblyDiscoveredInfo); @@ -448,7 +450,7 @@ void RunTestsInAssembly( runInfo.Assembly ); - if (assemblyDiscoveredInfo == null || assemblyDiscoveredInfo.DiscoveredTestCases == null || !assemblyDiscoveredInfo.DiscoveredTestCases.Any()) + if (assemblyDiscoveredInfo is null || assemblyDiscoveredInfo.DiscoveredTestCases is null || !assemblyDiscoveredInfo.DiscoveredTestCases.Any()) { if (configuration.InternalDiagnosticMessagesOrDefault) logger.LogWarning("Skipping '{0}' since no tests were found during discovery [execution].", assemblyFileName); @@ -459,14 +461,14 @@ void RunTestsInAssembly( // Filter tests var traitNames = new HashSet(assemblyDiscoveredInfo.DiscoveredTestCases.SelectMany(testCase => testCase.TraitNames)); var filter = new TestCaseFilter(runContext, logger, assemblyDiscoveredInfo.AssemblyFileName, traitNames); - var filteredTestCases = assemblyDiscoveredInfo.DiscoveredTestCases.Where(dtc => dtc.VSTestCase != null && filter.MatchTestCase(dtc.VSTestCase)).ToList(); + var filteredTestCases = assemblyDiscoveredInfo.DiscoveredTestCases.Where(dtc => dtc.VSTestCase is not null && filter.MatchTestCase(dtc.VSTestCase)).ToList(); foreach (var filteredTestCase in filteredTestCases) { var uniqueID = filteredTestCase.UniqueID; if (testCasesMap.ContainsKey(uniqueID)) logger.LogWarning(filteredTestCase.TestCase, "Skipping test case with duplicate ID '{0}' ('{1}' and '{2}')", uniqueID, testCasesMap[uniqueID].DisplayName, filteredTestCase.VSTestCase?.DisplayName); - else if (filteredTestCase.VSTestCase != null) + else if (filteredTestCase.VSTestCase is not null) { testCasesMap.Add(uniqueID, filteredTestCase.VSTestCase); testCases.Add(filteredTestCase.TestCase); @@ -494,7 +496,7 @@ void RunTestsInAssembly( var deserializedTestCasesByUniqueId = controller.BulkDeserialize(serializations); - if (deserializedTestCasesByUniqueId == null) + if (deserializedTestCasesByUniqueId is null) logger.LogErrorWithSource(assemblyFileName, "Received null response from BulkDeserialize"); else { @@ -505,7 +507,7 @@ void RunTestsInAssembly( var kvp = deserializedTestCasesByUniqueId[idx]; var vsTestCase = runInfo.TestCases[idx]; - if (kvp.Value == null) + if (kvp.Value is null) { logger.LogErrorWithSource(assemblyFileName, "Test case {0} failed to deserialize: {1}", vsTestCase.DisplayName, kvp.Key); } @@ -617,13 +619,14 @@ public static IReadOnlyList GetAvailableRunnerReporters( IReadOnlyList sources) { var result = new List(); + var adapterAssembly = typeof(VsTestRunner).Assembly; // We need to combine the source folders with our folder to find all potential runners var folders = sources .Select(s => Path.GetDirectoryName(Path.GetFullPath(s))) .WhereNotNull() - .Concat(new[] { Path.GetDirectoryName(typeof(VsTestRunner).Assembly.GetLocalCodeBase()) }) + .Concat(new[] { Path.GetDirectoryName(adapterAssembly.GetLocalCodeBase()) }) .Distinct(); foreach (var folder in folders) @@ -653,7 +656,7 @@ static IList GetVsTestCases( for (var idx = 0; idx < descriptors.Count; ++idx) { var testCase = new DiscoveredTestCase(source, descriptors[idx], testCases[idx], logger, testPlatformContext); - if (testCase.VSTestCase != null) + if (testCase.VSTestCase is not null) results.Add(testCase); } diff --git a/src/xunit.runner.visualstudio/build/xunit.runner.visualstudio.desktop.props b/src/xunit.runner.visualstudio/build/xunit.runner.visualstudio.desktop.props index fe1ba8ed..75d06f03 100644 --- a/src/xunit.runner.visualstudio/build/xunit.runner.visualstudio.desktop.props +++ b/src/xunit.runner.visualstudio/build/xunit.runner.visualstudio.desktop.props @@ -11,6 +11,16 @@ PreserveNewest False + + xunit.runner.reporters.net452.dll + PreserveNewest + False + + + xunit.runner.utility.net452.dll + PreserveNewest + False + diff --git a/src/xunit.runner.visualstudio/build/xunit.runner.visualstudio.dotnetcore.props b/src/xunit.runner.visualstudio/build/xunit.runner.visualstudio.dotnetcore.props index 7aa92d64..410a2532 100644 --- a/src/xunit.runner.visualstudio/build/xunit.runner.visualstudio.dotnetcore.props +++ b/src/xunit.runner.visualstudio/build/xunit.runner.visualstudio.dotnetcore.props @@ -6,6 +6,16 @@ PreserveNewest False + + xunit.runner.reporters.netcoreapp10.dll + PreserveNewest + False + + + xunit.runner.utility.netcoreapp10.dll + PreserveNewest + False + diff --git a/src/xunit.runner.visualstudio/xunit.runner.visualstudio.csproj b/src/xunit.runner.visualstudio/xunit.runner.visualstudio.csproj index 7f8728b6..3b305582 100644 --- a/src/xunit.runner.visualstudio/xunit.runner.visualstudio.csproj +++ b/src/xunit.runner.visualstudio/xunit.runner.visualstudio.csproj @@ -1,25 +1,16 @@ - xUnit.net Runner for Visual Studio ($(TargetFramework)) true - Xunit.Runner.VisualStudio - net462;net6.0 - true - build + xunit.runner.visualstudio.testadapter + xunit.runner.visualstudio.dotnetcore.testadapter + xUnit.net Runner for Visual Studio ($(TargetFramework)) true $(NoWarn);CS0436 enable - - - - xunit.runner.visualstudio.testadapter - $(DefineConstants);NETFRAMEWORK - - - - xunit.runner.visualstudio.dotnetcore.testadapter - $(DefineConstants);NETCOREAPP + Xunit.Runner.VisualStudio + net462;net6.0 + true @@ -31,7 +22,7 @@ - + @@ -64,60 +55,10 @@ Configuration=$(Configuration); GitCommitId=$(GitCommitId); + MicrosoftTestPlatformObjectModelVersion=$(MicrosoftTestPlatformObjectModelVersion); PackageVersion=$(PackageVersion); - - - - $([System.IO.Path]::Combine($(TargetDir), "merged", "$(TargetFileName)")) - - - - - - - - - - - - - - - - - - - - $([System.IO.Path]::Combine($(NuGetPackageRoot), "ilrepack.msbuild.task", $(ILRepackVersion), "tools", "ilrepack.exe")) - mono $(ILRepackExe) - $([System.IO.Path]::Combine($(TargetDir), "premerge", "$(TargetFileName)")) - $([System.IO.Path]::Combine($(TargetDir), "merged", "$(TargetFileName)")) - - - - - - - - - - - - diff --git a/src/xunit.runner.visualstudio/xunit.runner.visualstudio.nuspec b/src/xunit.runner.visualstudio/xunit.runner.visualstudio.nuspec index 7f66020c..6549723c 100644 --- a/src/xunit.runner.visualstudio/xunit.runner.visualstudio.nuspec +++ b/src/xunit.runner.visualstudio/xunit.runner.visualstudio.nuspec @@ -13,10 +13,12 @@ https://xunit.net/releases/visualstudio/$PackageVersion$ Visual Studio 2022+ Test Explorer runner for the xUnit.net framework. Capable of running xUnit.net v1.9.2 and v2.0+ tests. Supports .NET 4.6.2 or later, and .NET 6 or later. Copyright (C) .NET Foundation - + true - + + + @@ -27,10 +29,10 @@ - + - + diff --git a/test/test.harness/test.harness.csproj b/test/test.harness/test.harness.csproj index 26ad2717..ce95c80b 100644 --- a/test/test.harness/test.harness.csproj +++ b/test/test.harness/test.harness.csproj @@ -4,6 +4,10 @@ net462;net472;net6.0 + + + + diff --git a/test/test.testcasefilter/test.testcasefilter.csproj b/test/test.testcasefilter/test.testcasefilter.csproj index f52d5ce4..7ba02960 100644 --- a/test/test.testcasefilter/test.testcasefilter.csproj +++ b/test/test.testcasefilter/test.testcasefilter.csproj @@ -5,6 +5,11 @@ + + + + + diff --git a/version.json b/version.json index 0ef106db..b71b6a3a 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "2.5.1", + "version": "2.5.3", "nuGetPackageVersion": { "semVer": 2.0 },