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
},