Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: xunit/visualstudio.xunit
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 3.0.0
Choose a base ref
...
head repository: xunit/visualstudio.xunit
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 3.0.1
Choose a head ref
  • 8 commits
  • 27 files changed
  • 1 contributor

Commits on Dec 16, 2024

  1. Bump up to 3.0.1-pre

    bradwilson committed Dec 16, 2024

    Verified

    This commit was signed with the committer’s verified signature.
    Dragory Miikka
    Copy the full SHA
    0a250fc View commit details

Commits on Dec 21, 2024

  1. #431: ValueTask defined in xunit.runner.visualstudio causing conflict

    bradwilson committed Dec 21, 2024
    Copy the full SHA
    8f3a586 View commit details
  2. Move SpyLoggerHelper into the global namespace

    bradwilson committed Dec 21, 2024
    Copy the full SHA
    5382a58 View commit details
  3. Latest dependencies (to fix #432)

    bradwilson committed Dec 21, 2024
    Copy the full SHA
    a320aa3 View commit details
  4. Updated xunit.runner.json schema URL

    bradwilson committed Dec 21, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d71328f View commit details

Commits on Jan 6, 2025

  1. Restore VisualStudioSourceInformationProvider for #433

    bradwilson committed Jan 6, 2025
    Copy the full SHA
    8cbe1c3 View commit details
  2. Missed passing an argument

    bradwilson committed Jan 6, 2025
    Copy the full SHA
    b7d67ba View commit details

Commits on Jan 10, 2025

  1. v3.0.1

    bradwilson committed Jan 10, 2025
    Copy the full SHA
    f8675c3 View commit details
Showing with 377 additions and 36 deletions.
  1. +3 −3 Versions.props
  2. +1 −1 src/xunit.runner.visualstudio/Constants.cs
  3. +1 −1 src/xunit.runner.visualstudio/Sinks/DiagnosticMessageSink.cs
  4. +1 −1 src/xunit.runner.visualstudio/Sinks/VsDiscoverySink.cs
  5. +1 −1 src/xunit.runner.visualstudio/Sinks/VsExecutionSink.cs
  6. +1 −1 src/xunit.runner.visualstudio/TestPlatformContext.cs
  7. +73 −0 src/xunit.runner.visualstudio/Utility/AppDomainManager.cs
  8. +4 −1 src/xunit.runner.visualstudio/Utility/AssemblyExtensions.cs
  9. +1 −1 src/xunit.runner.visualstudio/Utility/AssemblyRunInfo.cs
  10. +81 −0 src/xunit.runner.visualstudio/Utility/DiaSessionWrapper.cs
  11. +115 −0 src/xunit.runner.visualstudio/Utility/DiaSessionWrapperHelper.cs
  12. +1 −1 src/xunit.runner.visualstudio/Utility/LoggerHelper.cs
  13. +1 −1 src/xunit.runner.visualstudio/Utility/RunSettings.cs
  14. +1 −1 src/xunit.runner.visualstudio/Utility/TestCaseFilter.cs
  15. +1 −1 src/xunit.runner.visualstudio/Utility/VisualStudioRunnerLogger.cs
  16. +44 −0 src/xunit.runner.visualstudio/Utility/VisualStudioSourceInformationProvider.cs
  17. +12 −10 src/xunit.runner.visualstudio/VsTestRunner.cs
  18. +4 −0 src/xunit.runner.visualstudio/xunit.runner.visualstudio.csproj
  19. +3 −1 test/test.xunit.runner.visualstudio/RunSettingsTests.cs
  20. +5 −1 test/test.xunit.runner.visualstudio/RunnerReporterTests.cs
  21. +6 −1 test/test.xunit.runner.visualstudio/Sinks/VsDiscoverySinkTests.cs
  22. +5 −2 test/test.xunit.runner.visualstudio/TestCaseFilterTests.cs
  23. +4 −3 test/test.xunit.runner.visualstudio/Utility/SpyLoggerHelper.cs
  24. +3 −1 test/test.xunit.runner.visualstudio/VsTestRunnerTests.cs
  25. +3 −1 test/test.xunit.runner.visualstudio/test.xunit.runner.visualstudio.csproj
  26. +1 −1 test/xunit.runner.json
  27. +1 −1 version.json
6 changes: 3 additions & 3 deletions Versions.props
Original file line number Diff line number Diff line change
@@ -10,9 +10,9 @@
<NerdbankGitVersioningVersion>3.7.112</NerdbankGitVersioningVersion>
<NSubstituteVersion>5.3.0</NSubstituteVersion>
<TunnelVisionLabsReferenceAssemblyAnnotatorVersion>1.0.0-alpha.160</TunnelVisionLabsReferenceAssemblyAnnotatorVersion>
<XunitAnalyzersVersion>1.18.0</XunitAnalyzersVersion>
<XunitV2Version>2.9.3-pre.7</XunitV2Version>
<XunitV3Version>1.0.0</XunitV3Version>
<XunitAnalyzersVersion>1.19.0</XunitAnalyzersVersion>
<XunitV2Version>2.9.3</XunitV2Version>
<XunitV3Version>1.0.1</XunitV3Version>
</PropertyGroup>

</Project>
2 changes: 1 addition & 1 deletion src/xunit.runner.visualstudio/Constants.cs
Original file line number Diff line number Diff line change
@@ -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/";
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

namespace Xunit.Runner.VisualStudio;

public class DiagnosticMessageSink : DiagnosticEventSink
internal class DiagnosticMessageSink : DiagnosticEventSink
{
public DiagnosticMessageSink(
LoggerHelper log,
2 changes: 1 addition & 1 deletion src/xunit.runner.visualstudio/Sinks/VsDiscoverySink.cs
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 1 addition & 1 deletion src/xunit.runner.visualstudio/Sinks/VsExecutionSink.cs
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@

namespace Xunit.Runner.VisualStudio;

public sealed class VsExecutionSink : TestMessageSink, IDisposable
internal sealed class VsExecutionSink : TestMessageSink, IDisposable
{
readonly Func<bool> cancelledThunk;
readonly LoggerHelper logger;
2 changes: 1 addition & 1 deletion src/xunit.runner.visualstudio/TestPlatformContext.cs
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ namespace Xunit.Runner.VisualStudio;
/// Provides contextual information on a test run/discovery based on runsettings
/// or the invocation (execution, discovery).
/// </summary>
public struct TestPlatformContext
internal struct TestPlatformContext
{
/// <summary>
/// Indicates if the test runner is running in design mode (meaning, inside the Visual Studio IDE).
73 changes: 73 additions & 0 deletions src/xunit.runner.visualstudio/Utility/AppDomainManager.cs
Original file line number Diff line number Diff line change
@@ -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<TObject>(
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
5 changes: 4 additions & 1 deletion src/xunit.runner.visualstudio/Utility/AssemblyExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System.Reflection;

#if NETFRAMEWORK
using System;
using System.IO;
using System.Reflection;
#endif

internal static class AssemblyExtensions
{
2 changes: 1 addition & 1 deletion src/xunit.runner.visualstudio/Utility/AssemblyRunInfo.cs
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@

namespace Xunit.Runner.VisualStudio;

public class AssemblyRunInfo
internal class AssemblyRunInfo
{
AssemblyRunInfo(
LoggerHelper logger,
81 changes: 81 additions & 0 deletions src/xunit.runner.visualstudio/Utility/DiaSessionWrapper.cs
Original file line number Diff line number Diff line change
@@ -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<DiaSessionWrapperHelper>(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
}
}
115 changes: 115 additions & 0 deletions src/xunit.runner.visualstudio/Utility/DiaSessionWrapperHelper.cs
Original file line number Diff line number Diff line change
@@ -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<string, Type> 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<AsyncStateMachineAttribute>()?.StateMachineType;
if (stateMachineType is not null && stateMachineType.FullName is not null)
{
typeName = stateMachineType.FullName;
methodName = "MoveNext";
}
}
}
}
catch { }
}
}
2 changes: 1 addition & 1 deletion src/xunit.runner.visualstudio/Utility/LoggerHelper.cs
Original file line number Diff line number Diff line change
@@ -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;

2 changes: 1 addition & 1 deletion src/xunit.runner.visualstudio/Utility/RunSettings.cs
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@

namespace Xunit.Runner.VisualStudio;

public class RunSettings
internal class RunSettings
{
public AppDomainSupport? AppDomain { get; set; }
public string? Culture { get; set; }
2 changes: 1 addition & 1 deletion src/xunit.runner.visualstudio/Utility/TestCaseFilter.cs
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

namespace Xunit.Runner.VisualStudio;

public class TestCaseFilter
internal class TestCaseFilter
{
const string DisplayNameString = "DisplayName";
const string FullyQualifiedNameString = "FullyQualifiedName";
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

namespace Xunit.Runner.VisualStudio;

public class VisualStudioRunnerLogger(LoggerHelper loggerHelper) :
internal class VisualStudioRunnerLogger(LoggerHelper loggerHelper) :
IRunnerLogger
{
static readonly object lockObject = new();
Loading