From 0a250fc23147fe9f64d6b4dc61a46685429ee1bf Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Mon, 16 Dec 2024 12:07:12 -0800 Subject: [PATCH 1/8] Bump up to 3.0.1-pre --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 4f6fd6c..eabb616 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": "3.0.0", + "version": "3.0.1-pre.{height}", "nuGetPackageVersion": { "semVer": 2.0 }, From 8f3a5861cf95b76519998d9f9703ff4ade59f092 Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Sat, 21 Dec 2024 00:50:49 -0800 Subject: [PATCH 2/8] #431: ValueTask defined in xunit.runner.visualstudio causing conflict --- src/xunit.runner.visualstudio/Constants.cs | 2 +- .../Sinks/DiagnosticMessageSink.cs | 2 +- .../Sinks/VsDiscoverySink.cs | 2 +- .../Sinks/VsExecutionSink.cs | 2 +- .../TestPlatformContext.cs | 2 +- .../Utility/AssemblyRunInfo.cs | 2 +- .../Utility/LoggerHelper.cs | 2 +- .../Utility/RunSettings.cs | 2 +- .../Utility/TestCaseFilter.cs | 2 +- .../Utility/VisualStudioRunnerLogger.cs | 2 +- src/xunit.runner.visualstudio/VsTestRunner.cs | 16 ++++++++-------- .../xunit.runner.visualstudio.csproj | 4 ++++ .../RunSettingsTests.cs | 4 +++- .../RunnerReporterTests.cs | 6 +++++- .../Sinks/VsDiscoverySinkTests.cs | 8 +++++++- .../TestCaseFilterTests.cs | 7 +++++-- .../Utility/SpyLoggerHelper.cs | 5 ++++- .../VsTestRunnerTests.cs | 4 +++- .../test.xunit.runner.visualstudio.csproj | 4 +++- 19 files changed, 52 insertions(+), 26 deletions(-) diff --git a/src/xunit.runner.visualstudio/Constants.cs b/src/xunit.runner.visualstudio/Constants.cs index 97aef60..0543da4 100644 --- a/src/xunit.runner.visualstudio/Constants.cs +++ b/src/xunit.runner.visualstudio/Constants.cs @@ -1,6 +1,6 @@ namespace Xunit.Runner.VisualStudio; -public static class Constants +internal static class Constants { #if NETFRAMEWORK public const string ExecutorUri = "executor://xunit/VsTestRunner3/netfx/"; diff --git a/src/xunit.runner.visualstudio/Sinks/DiagnosticMessageSink.cs b/src/xunit.runner.visualstudio/Sinks/DiagnosticMessageSink.cs index 49f46e4..081b4c0 100644 --- a/src/xunit.runner.visualstudio/Sinks/DiagnosticMessageSink.cs +++ b/src/xunit.runner.visualstudio/Sinks/DiagnosticMessageSink.cs @@ -2,7 +2,7 @@ namespace Xunit.Runner.VisualStudio; -public class DiagnosticMessageSink : DiagnosticEventSink +internal class DiagnosticMessageSink : DiagnosticEventSink { public DiagnosticMessageSink( LoggerHelper log, diff --git a/src/xunit.runner.visualstudio/Sinks/VsDiscoverySink.cs b/src/xunit.runner.visualstudio/Sinks/VsDiscoverySink.cs index 51d8f56..c577582 100644 --- a/src/xunit.runner.visualstudio/Sinks/VsDiscoverySink.cs +++ b/src/xunit.runner.visualstudio/Sinks/VsDiscoverySink.cs @@ -15,7 +15,7 @@ namespace Xunit.Runner.VisualStudio; -public sealed class VsDiscoverySink : IVsDiscoverySink, IDisposable +internal sealed class VsDiscoverySink : IVsDiscoverySink, IDisposable { static readonly string Ellipsis = new((char)183, 3); const int MaximumDisplayNameLength = 447; diff --git a/src/xunit.runner.visualstudio/Sinks/VsExecutionSink.cs b/src/xunit.runner.visualstudio/Sinks/VsExecutionSink.cs index 942569f..124f6b4 100644 --- a/src/xunit.runner.visualstudio/Sinks/VsExecutionSink.cs +++ b/src/xunit.runner.visualstudio/Sinks/VsExecutionSink.cs @@ -14,7 +14,7 @@ namespace Xunit.Runner.VisualStudio; -public sealed class VsExecutionSink : TestMessageSink, IDisposable +internal sealed class VsExecutionSink : TestMessageSink, IDisposable { readonly Func cancelledThunk; readonly LoggerHelper logger; diff --git a/src/xunit.runner.visualstudio/TestPlatformContext.cs b/src/xunit.runner.visualstudio/TestPlatformContext.cs index 2748ef9..b2c2fa5 100644 --- a/src/xunit.runner.visualstudio/TestPlatformContext.cs +++ b/src/xunit.runner.visualstudio/TestPlatformContext.cs @@ -4,7 +4,7 @@ namespace Xunit.Runner.VisualStudio; /// Provides contextual information on a test run/discovery based on runsettings /// or the invocation (execution, discovery). /// -public struct TestPlatformContext +internal struct TestPlatformContext { /// /// Indicates if the test runner is running in design mode (meaning, inside the Visual Studio IDE). diff --git a/src/xunit.runner.visualstudio/Utility/AssemblyRunInfo.cs b/src/xunit.runner.visualstudio/Utility/AssemblyRunInfo.cs index af02f0f..50002a5 100644 --- a/src/xunit.runner.visualstudio/Utility/AssemblyRunInfo.cs +++ b/src/xunit.runner.visualstudio/Utility/AssemblyRunInfo.cs @@ -5,7 +5,7 @@ namespace Xunit.Runner.VisualStudio; -public class AssemblyRunInfo +internal class AssemblyRunInfo { AssemblyRunInfo( LoggerHelper logger, diff --git a/src/xunit.runner.visualstudio/Utility/LoggerHelper.cs b/src/xunit.runner.visualstudio/Utility/LoggerHelper.cs index d2acdff..d11f54c 100644 --- a/src/xunit.runner.visualstudio/Utility/LoggerHelper.cs +++ b/src/xunit.runner.visualstudio/Utility/LoggerHelper.cs @@ -6,7 +6,7 @@ namespace Xunit.Runner.VisualStudio; -public class LoggerHelper(IMessageLogger? logger, Stopwatch stopwatch) +internal class LoggerHelper(IMessageLogger? logger, Stopwatch stopwatch) { public IMessageLogger? InnerLogger { get; private set; } = logger; diff --git a/src/xunit.runner.visualstudio/Utility/RunSettings.cs b/src/xunit.runner.visualstudio/Utility/RunSettings.cs index 9ff7c24..3edbd91 100644 --- a/src/xunit.runner.visualstudio/Utility/RunSettings.cs +++ b/src/xunit.runner.visualstudio/Utility/RunSettings.cs @@ -6,7 +6,7 @@ namespace Xunit.Runner.VisualStudio; -public class RunSettings +internal class RunSettings { public AppDomainSupport? AppDomain { get; set; } public string? Culture { get; set; } diff --git a/src/xunit.runner.visualstudio/Utility/TestCaseFilter.cs b/src/xunit.runner.visualstudio/Utility/TestCaseFilter.cs index 836cb3d..edcff37 100644 --- a/src/xunit.runner.visualstudio/Utility/TestCaseFilter.cs +++ b/src/xunit.runner.visualstudio/Utility/TestCaseFilter.cs @@ -7,7 +7,7 @@ namespace Xunit.Runner.VisualStudio; -public class TestCaseFilter +internal class TestCaseFilter { const string DisplayNameString = "DisplayName"; const string FullyQualifiedNameString = "FullyQualifiedName"; diff --git a/src/xunit.runner.visualstudio/Utility/VisualStudioRunnerLogger.cs b/src/xunit.runner.visualstudio/Utility/VisualStudioRunnerLogger.cs index a041494..277d2a3 100644 --- a/src/xunit.runner.visualstudio/Utility/VisualStudioRunnerLogger.cs +++ b/src/xunit.runner.visualstudio/Utility/VisualStudioRunnerLogger.cs @@ -2,7 +2,7 @@ namespace Xunit.Runner.VisualStudio; -public class VisualStudioRunnerLogger(LoggerHelper loggerHelper) : +internal class VisualStudioRunnerLogger(LoggerHelper loggerHelper) : IRunnerLogger { static readonly object lockObject = new(); diff --git a/src/xunit.runner.visualstudio/VsTestRunner.cs b/src/xunit.runner.visualstudio/VsTestRunner.cs index 0e90c05..4d85b3a 100644 --- a/src/xunit.runner.visualstudio/VsTestRunner.cs +++ b/src/xunit.runner.visualstudio/VsTestRunner.cs @@ -120,22 +120,22 @@ public class VsTestRunner : ITestDiscoverer, ITestExecutor "xunit.v3.runner.utility.netstandard20.dll", }; - public static TestProperty ManagedMethodProperty { get; } = + internal static TestProperty ManagedMethodProperty { get; } = TestProperty.Register("TestCase.ManagedMethod", "ManagedMethod", string.Empty, string.Empty, typeof(string), x => !string.IsNullOrWhiteSpace(x as string), TestPropertyAttributes.Hidden, typeof(TestCase)); - public static TestProperty ManagedTypeProperty { get; } = + internal static TestProperty ManagedTypeProperty { get; } = TestProperty.Register("TestCase.ManagedType", "ManagedType", string.Empty, string.Empty, typeof(string), x => !string.IsNullOrWhiteSpace(x as string), TestPropertyAttributes.Hidden, typeof(TestCase)); - public static TestProperty SkipReasonProperty { get; } = + internal static TestProperty SkipReasonProperty { get; } = TestProperty.Register("XunitSkipReason", "xUnit.net Skip Reason", typeof(string), typeof(VsTestRunner)); - public static TestProperty TestCaseExplicitProperty { get; } = + internal static TestProperty TestCaseExplicitProperty { get; } = TestProperty.Register("XunitTestCaseExplicit", "xUnit.net Test Case Explicit Flag", typeof(bool), typeof(VsTestRunner)); - public static TestProperty TestCaseSerializationProperty { get; } = + internal static TestProperty TestCaseSerializationProperty { get; } = TestProperty.Register("XunitTestCaseSerialization", "xUnit.net Test Case Serialization", typeof(string), typeof(VsTestRunner)); - public static TestProperty TestCaseUniqueIDProperty { get; } = + internal static TestProperty TestCaseUniqueIDProperty { get; } = TestProperty.Register("XunitTestCaseUniqueID", "xUnit.net Test Case Unique ID", typeof(string), typeof(VsTestRunner)); public void Cancel() => @@ -307,7 +307,7 @@ async Task DiscoverTestsInAssembly( return true; } - public static IReadOnlyList GetAvailableRunnerReporters(LoggerHelper? logger) + internal static IReadOnlyList GetAvailableRunnerReporters(LoggerHelper? logger) { var result = RegisteredRunnerReporters.Get(typeof(VsTestRunner).Assembly, out var messages); @@ -318,7 +318,7 @@ public static IReadOnlyList GetAvailableRunnerReporters(LoggerH return result; } - public static IRunnerReporter GetRunnerReporter( + internal static IRunnerReporter GetRunnerReporter( LoggerHelper? logger, RunSettings runSettings) { diff --git a/src/xunit.runner.visualstudio/xunit.runner.visualstudio.csproj b/src/xunit.runner.visualstudio/xunit.runner.visualstudio.csproj index 99651f3..70df5b0 100644 --- a/src/xunit.runner.visualstudio/xunit.runner.visualstudio.csproj +++ b/src/xunit.runner.visualstudio/xunit.runner.visualstudio.csproj @@ -12,6 +12,10 @@ true + + + + diff --git a/test/test.xunit.runner.visualstudio/RunSettingsTests.cs b/test/test.xunit.runner.visualstudio/RunSettingsTests.cs index 30876bf..a42e78c 100644 --- a/test/test.xunit.runner.visualstudio/RunSettingsTests.cs +++ b/test/test.xunit.runner.visualstudio/RunSettingsTests.cs @@ -1,8 +1,10 @@ +extern alias VSTestAdapter; + using System; using Xunit; using Xunit.Runner.Common; -using Xunit.Runner.VisualStudio; using Xunit.Sdk; +using RunSettings = VSTestAdapter.Xunit.Runner.VisualStudio.RunSettings; public class RunSettingsTests { diff --git a/test/test.xunit.runner.visualstudio/RunnerReporterTests.cs b/test/test.xunit.runner.visualstudio/RunnerReporterTests.cs index 04bfddb..04d3856 100644 --- a/test/test.xunit.runner.visualstudio/RunnerReporterTests.cs +++ b/test/test.xunit.runner.visualstudio/RunnerReporterTests.cs @@ -1,10 +1,14 @@ +extern alias VSTestAdapter; + using System; using System.Diagnostics; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using NSubstitute; using Xunit; using Xunit.Runner.Common; -using Xunit.Runner.VisualStudio; +using LoggerHelper = VSTestAdapter.Xunit.Runner.VisualStudio.LoggerHelper; +using RunSettings = VSTestAdapter.Xunit.Runner.VisualStudio.RunSettings; +using VsTestRunner = VSTestAdapter.Xunit.Runner.VisualStudio.VsTestRunner; public class RunnerReporterTests { diff --git a/test/test.xunit.runner.visualstudio/Sinks/VsDiscoverySinkTests.cs b/test/test.xunit.runner.visualstudio/Sinks/VsDiscoverySinkTests.cs index f32769d..d8a641b 100644 --- a/test/test.xunit.runner.visualstudio/Sinks/VsDiscoverySinkTests.cs +++ b/test/test.xunit.runner.visualstudio/Sinks/VsDiscoverySinkTests.cs @@ -1,8 +1,14 @@ +extern alias VSTestAdapter; + using System; using System.Collections.Generic; using System.Linq; using Xunit; using Xunit.Runner.VisualStudio; +using Constants = VSTestAdapter.Xunit.Runner.VisualStudio.Constants; +using TestPlatformContext = VSTestAdapter.Xunit.Runner.VisualStudio.TestPlatformContext; +using VsDiscoverySink = VSTestAdapter.Xunit.Runner.VisualStudio.VsDiscoverySink; +using VsTestRunner = VSTestAdapter.Xunit.Runner.VisualStudio.VsTestRunner; public class VsDiscoverySinkTests { @@ -45,7 +51,7 @@ public void StandardData( Assert.NotNull(vsTestCase); - // Standard VSTest properties + // Standard VSTest propertiesgi Assert.Equal("/source/file.cs", vsTestCase.CodeFilePath); Assert.Equal("test-case-display-name", vsTestCase.DisplayName); Assert.Equal(Constants.ExecutorUri, vsTestCase.ExecutorUri.OriginalString); diff --git a/test/test.xunit.runner.visualstudio/TestCaseFilterTests.cs b/test/test.xunit.runner.visualstudio/TestCaseFilterTests.cs index b7caa5f..789f35c 100644 --- a/test/test.xunit.runner.visualstudio/TestCaseFilterTests.cs +++ b/test/test.xunit.runner.visualstudio/TestCaseFilterTests.cs @@ -1,3 +1,5 @@ +extern alias VSTestAdapter; + using System; using System.Collections.Generic; using System.Diagnostics; @@ -7,8 +9,9 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using NSubstitute; using Xunit; -using Xunit.Runner.VisualStudio; -using Constants = Xunit.Runner.VisualStudio.Constants; +using Constants = VSTestAdapter.Xunit.Runner.VisualStudio.Constants; +using LoggerHelper = VSTestAdapter.Xunit.Runner.VisualStudio.LoggerHelper; +using TestCaseFilter = VSTestAdapter.Xunit.Runner.VisualStudio.TestCaseFilter; public class TestCaseFilterTests { diff --git a/test/test.xunit.runner.visualstudio/Utility/SpyLoggerHelper.cs b/test/test.xunit.runner.visualstudio/Utility/SpyLoggerHelper.cs index fe934cb..0783671 100644 --- a/test/test.xunit.runner.visualstudio/Utility/SpyLoggerHelper.cs +++ b/test/test.xunit.runner.visualstudio/Utility/SpyLoggerHelper.cs @@ -1,10 +1,13 @@ +extern alias VSTestAdapter; + using System.Collections.Generic; using System.Diagnostics; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using LoggerHelper = VSTestAdapter.Xunit.Runner.VisualStudio.LoggerHelper; namespace Xunit.Runner.VisualStudio; -public class SpyLoggerHelper(SpyMessageLogger logger, Stopwatch stopwatch) : +internal class SpyLoggerHelper(SpyMessageLogger logger, Stopwatch stopwatch) : LoggerHelper(logger, stopwatch) { public IReadOnlyCollection Messages => logger.Messages; diff --git a/test/test.xunit.runner.visualstudio/VsTestRunnerTests.cs b/test/test.xunit.runner.visualstudio/VsTestRunnerTests.cs index b144a06..e6a956e 100644 --- a/test/test.xunit.runner.visualstudio/VsTestRunnerTests.cs +++ b/test/test.xunit.runner.visualstudio/VsTestRunnerTests.cs @@ -1,7 +1,9 @@ +extern alias VSTestAdapter; + using System.ComponentModel; using System.Reflection; using Xunit; -using Xunit.Runner.VisualStudio; +using VsTestRunner = VSTestAdapter.Xunit.Runner.VisualStudio.VsTestRunner; public class VsTestRunnerTests { diff --git a/test/test.xunit.runner.visualstudio/test.xunit.runner.visualstudio.csproj b/test/test.xunit.runner.visualstudio/test.xunit.runner.visualstudio.csproj index 15b1164..9a6907c 100644 --- a/test/test.xunit.runner.visualstudio/test.xunit.runner.visualstudio.csproj +++ b/test/test.xunit.runner.visualstudio/test.xunit.runner.visualstudio.csproj @@ -15,7 +15,9 @@ - + + VSTestAdapter + From 5382a589cd50dee962e3690330dea96808c761f9 Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Sat, 21 Dec 2024 01:03:08 -0800 Subject: [PATCH 3/8] Move SpyLoggerHelper into the global namespace --- .../Sinks/VsDiscoverySinkTests.cs | 3 +-- test/test.xunit.runner.visualstudio/Utility/SpyLoggerHelper.cs | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/test/test.xunit.runner.visualstudio/Sinks/VsDiscoverySinkTests.cs b/test/test.xunit.runner.visualstudio/Sinks/VsDiscoverySinkTests.cs index d8a641b..9a6999a 100644 --- a/test/test.xunit.runner.visualstudio/Sinks/VsDiscoverySinkTests.cs +++ b/test/test.xunit.runner.visualstudio/Sinks/VsDiscoverySinkTests.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using Xunit; -using Xunit.Runner.VisualStudio; using Constants = VSTestAdapter.Xunit.Runner.VisualStudio.Constants; using TestPlatformContext = VSTestAdapter.Xunit.Runner.VisualStudio.TestPlatformContext; using VsDiscoverySink = VSTestAdapter.Xunit.Runner.VisualStudio.VsDiscoverySink; @@ -51,7 +50,7 @@ public void StandardData( Assert.NotNull(vsTestCase); - // Standard VSTest propertiesgi + // Standard VSTest properties Assert.Equal("/source/file.cs", vsTestCase.CodeFilePath); Assert.Equal("test-case-display-name", vsTestCase.DisplayName); Assert.Equal(Constants.ExecutorUri, vsTestCase.ExecutorUri.OriginalString); diff --git a/test/test.xunit.runner.visualstudio/Utility/SpyLoggerHelper.cs b/test/test.xunit.runner.visualstudio/Utility/SpyLoggerHelper.cs index 0783671..eb5286f 100644 --- a/test/test.xunit.runner.visualstudio/Utility/SpyLoggerHelper.cs +++ b/test/test.xunit.runner.visualstudio/Utility/SpyLoggerHelper.cs @@ -5,8 +5,6 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using LoggerHelper = VSTestAdapter.Xunit.Runner.VisualStudio.LoggerHelper; -namespace Xunit.Runner.VisualStudio; - internal class SpyLoggerHelper(SpyMessageLogger logger, Stopwatch stopwatch) : LoggerHelper(logger, stopwatch) { From a320aa3caf4f854fbc300a9a7cac2bf2a7d02a97 Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Sat, 21 Dec 2024 10:44:51 -0800 Subject: [PATCH 4/8] Latest dependencies (to fix #432) --- Versions.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Versions.props b/Versions.props index 409662e..f17df5e 100644 --- a/Versions.props +++ b/Versions.props @@ -10,9 +10,9 @@ 3.7.112 5.3.0 1.0.0-alpha.160 - 1.18.0 + 1.19.0-pre.3 2.9.3-pre.7 - 1.0.0 + 1.0.1-pre.6 From d71328f59375d09e5e5ced6e82909146793250a1 Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Sat, 21 Dec 2024 15:42:28 -0800 Subject: [PATCH 5/8] Updated xunit.runner.json schema URL --- test/xunit.runner.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/xunit.runner.json b/test/xunit.runner.json index 5bb2ed0..6c0d1e4 100644 --- a/test/xunit.runner.json +++ b/test/xunit.runner.json @@ -1,4 +1,4 @@ { - "$schema": "https://xunit.net/schema/v2.8.1/xunit.runner.schema.json", + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", "diagnosticMessages": true } From 8cbe1c39a625685c65f2f4bf50b406c3ee60bbf3 Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Mon, 6 Jan 2025 10:14:24 -0800 Subject: [PATCH 6/8] Restore VisualStudioSourceInformationProvider for #433 --- .../Utility/AppDomainManager.cs | 73 +++++++++++ .../Utility/AssemblyExtensions.cs | 5 +- .../Utility/DiaSessionWrapper.cs | 81 ++++++++++++ .../Utility/DiaSessionWrapperHelper.cs | 115 ++++++++++++++++++ .../VisualStudioSourceInformationProvider.cs | 44 +++++++ src/xunit.runner.visualstudio/VsTestRunner.cs | 4 +- 6 files changed, 320 insertions(+), 2 deletions(-) create mode 100644 src/xunit.runner.visualstudio/Utility/AppDomainManager.cs create mode 100644 src/xunit.runner.visualstudio/Utility/DiaSessionWrapper.cs create mode 100644 src/xunit.runner.visualstudio/Utility/DiaSessionWrapperHelper.cs create mode 100644 src/xunit.runner.visualstudio/Utility/VisualStudioSourceInformationProvider.cs diff --git a/src/xunit.runner.visualstudio/Utility/AppDomainManager.cs b/src/xunit.runner.visualstudio/Utility/AppDomainManager.cs new file mode 100644 index 0000000..96722ca --- /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 3da9779..b2ce647 100644 --- a/src/xunit.runner.visualstudio/Utility/AssemblyExtensions.cs +++ b/src/xunit.runner.visualstudio/Utility/AssemblyExtensions.cs @@ -1,6 +1,9 @@ +using System.Reflection; + +#if NETFRAMEWORK using System; using System.IO; -using System.Reflection; +#endif internal static class AssemblyExtensions { diff --git a/src/xunit.runner.visualstudio/Utility/DiaSessionWrapper.cs b/src/xunit.runner.visualstudio/Utility/DiaSessionWrapper.cs new file mode 100644 index 0000000..f83109e --- /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.Internal; +using Xunit.Runner.Common; + +namespace Xunit.Runner.VisualStudio; + +// 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 InternalDiagnosticMessage($"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 0000000..faf72e3 --- /dev/null +++ b/src/xunit.runner.visualstudio/Utility/DiaSessionWrapperHelper.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using Xunit.Internal; +using Xunit.Sdk; + +namespace Xunit.Runner.VisualStudio; + +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 ??= []; + } + + 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/VisualStudioSourceInformationProvider.cs b/src/xunit.runner.visualstudio/Utility/VisualStudioSourceInformationProvider.cs new file mode 100644 index 0000000..44461f7 --- /dev/null +++ b/src/xunit.runner.visualstudio/Utility/VisualStudioSourceInformationProvider.cs @@ -0,0 +1,44 @@ +using System.Threading.Tasks; +using Xunit.Runner.Common; +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). +/// +/// The assembly file name. +/// The message sink to send internal diagnostic messages to. +internal class VisualStudioSourceInformationProvider( + string assemblyFileName, + DiagnosticMessageSink diagnosticMessageSink) : + LongLivedMarshalByRefObject, ISourceInformationProvider +{ + static readonly SourceInformation EmptySourceInformation = new(); + + readonly DiaSessionWrapper session = new DiaSessionWrapper(assemblyFileName, diagnosticMessageSink); + + /// + public SourceInformation GetSourceInformation( + string? testClassName, + string? testMethodName) + { + if (testClassName is null || testMethodName is null) + return EmptySourceInformation; + + var navData = session.GetNavigationData(testClassName, testMethodName); + if (navData is null || navData.FileName is null) + return EmptySourceInformation; + + return new SourceInformation(navData.FileName, navData.MinLineNumber); + } + + /// + public ValueTask DisposeAsync() + { + session.Dispose(); + + return default; + } +} diff --git a/src/xunit.runner.visualstudio/VsTestRunner.cs b/src/xunit.runner.visualstudio/VsTestRunner.cs index 4d85b3a..b5d509d 100644 --- a/src/xunit.runner.visualstudio/VsTestRunner.cs +++ b/src/xunit.runner.visualstudio/VsTestRunner.cs @@ -217,7 +217,8 @@ async Task DiscoverTests( var assemblyDisplayName = Path.GetFileNameWithoutExtension(assembly.AssemblyFileName); var diagnosticMessageSink = new DiagnosticMessageSink(logger, assemblyDisplayName, assembly.Configuration.DiagnosticMessagesOrDefault, assembly.Configuration.InternalDiagnosticMessagesOrDefault); - await using var controller = XunitFrontController.Create(assembly, null, diagnosticMessageSink); + await using var sourceInformationProvider = new VisualStudioSourceInformationProvider(assemblyFileName, diagnosticMessageSink); + await using var controller = XunitFrontController.Create(assembly, sourceInformationProvider, diagnosticMessageSink); if (controller is null) return; @@ -508,6 +509,7 @@ async Task RunTestsInAssembly( if (runContext.IsBeingDebugged && frameworkHandle2 is not null) testProcessLauncher = new DebuggerProcessLauncher(frameworkHandle2); + await using var sourceInformationProvider = new VisualStudioSourceInformationProvider(assemblyFileName, diagnosticSink); await using var controller = XunitFrontController.Create(runInfo.Assembly, null, diagnosticSink, testProcessLauncher); if (controller is null) return; From b7d67bac0f6616a5ba3594f5d365f0c858424172 Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Mon, 6 Jan 2025 10:29:54 -0800 Subject: [PATCH 7/8] Missed passing an argument --- src/xunit.runner.visualstudio/VsTestRunner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xunit.runner.visualstudio/VsTestRunner.cs b/src/xunit.runner.visualstudio/VsTestRunner.cs index b5d509d..a66e58e 100644 --- a/src/xunit.runner.visualstudio/VsTestRunner.cs +++ b/src/xunit.runner.visualstudio/VsTestRunner.cs @@ -510,7 +510,7 @@ async Task RunTestsInAssembly( testProcessLauncher = new DebuggerProcessLauncher(frameworkHandle2); await using var sourceInformationProvider = new VisualStudioSourceInformationProvider(assemblyFileName, diagnosticSink); - await using var controller = XunitFrontController.Create(runInfo.Assembly, null, diagnosticSink, testProcessLauncher); + await using var controller = XunitFrontController.Create(runInfo.Assembly, sourceInformationProvider, diagnosticSink, testProcessLauncher); if (controller is null) return; From f8675c32e51818b13f1314569ec35b62246999c3 Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Thu, 9 Jan 2025 22:51:45 -0800 Subject: [PATCH 8/8] v3.0.1 --- Versions.props | 6 +++--- version.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Versions.props b/Versions.props index f17df5e..0426340 100644 --- a/Versions.props +++ b/Versions.props @@ -10,9 +10,9 @@ 3.7.112 5.3.0 1.0.0-alpha.160 - 1.19.0-pre.3 - 2.9.3-pre.7 - 1.0.1-pre.6 + 1.19.0 + 2.9.3 + 1.0.1 diff --git a/version.json b/version.json index eabb616..381127c 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": "3.0.1-pre.{height}", + "version": "3.0.1", "nuGetPackageVersion": { "semVer": 2.0 },