diff --git a/SampleTestAssembly/SampleTestAssembly.csproj b/SampleTestAssembly/SampleTestAssembly.csproj index b8b1d3e..497d7de 100644 --- a/SampleTestAssembly/SampleTestAssembly.csproj +++ b/SampleTestAssembly/SampleTestAssembly.csproj @@ -1,7 +1,7 @@  - net461 + netstandard2.0 diff --git a/xunit.runner.data/xunit.runner.data.csproj b/xunit.runner.data/xunit.runner.data.csproj index d22e7a7..d69f99b 100644 --- a/xunit.runner.data/xunit.runner.data.csproj +++ b/xunit.runner.data/xunit.runner.data.csproj @@ -2,7 +2,7 @@ Xunit.Runner.Data - net452 + netstandard2.0 \ No newline at end of file diff --git a/xunit.runner.worker/AssemblyHelper.cs b/xunit.runner.worker/AssemblyHelper.cs new file mode 100644 index 0000000..d8283c4 --- /dev/null +++ b/xunit.runner.worker/AssemblyHelper.cs @@ -0,0 +1,110 @@ +#if NETFRAMEWORK + +// Taken from https://github.com/xunit/xunit/blob/master/src/common/AssemblyResolution/AssemblyHelper_Desktop.cs + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Reflection; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Xunit +{ + /// + /// This class provides assistance with assembly resolution for missing assemblies. + /// + class AssemblyHelper : LongLivedMarshalByRefObject, IDisposable + { + static readonly string[] Extensions = { ".dll", ".exe" }; + + readonly string directory; + readonly IMessageSink internalDiagnosticsMessageSink; + readonly Dictionary lookupCache = new Dictionary(); + + /// + /// Constructs an instance using the given for resolution. + /// + /// The directory to use for resolving assemblies. + public AssemblyHelper(string directory) : this(directory, null) { } + + /// + /// Constructs an instance using the given for resolution. + /// + /// The directory to use for resolving assemblies. + /// The message sink to send internal diagnostics messages to + public AssemblyHelper(string directory, IMessageSink internalDiagnosticsMessageSink) + { + this.directory = directory; + this.internalDiagnosticsMessageSink = internalDiagnosticsMessageSink; + + AppDomain.CurrentDomain.AssemblyResolve += Resolve; + } + + /// + public void Dispose() + => AppDomain.CurrentDomain.AssemblyResolve -= Resolve; + + Assembly LoadAssembly(AssemblyName assemblyName) + { + if (lookupCache.TryGetValue(assemblyName.Name, out var result)) + return result; + + var path = Path.Combine(directory, assemblyName.Name); + result = ResolveAndLoadAssembly(path, out var resolvedAssemblyPath); + + if (internalDiagnosticsMessageSink != null) + { + if (result == null) + internalDiagnosticsMessageSink.OnMessage(new DiagnosticMessage($"[AssemblyHelper_Desktop.LoadAssembly] Resolution for '{assemblyName.Name}' failed, passed down to next resolver")); + else + internalDiagnosticsMessageSink.OnMessage(new DiagnosticMessage($"[AssemblyHelper_Desktop.LoadAssembly] Resolved '{assemblyName.Name}' to '{resolvedAssemblyPath}'")); + } + + lookupCache[assemblyName.Name] = result; + return result; + } + + Assembly Resolve(object sender, ResolveEventArgs args) + => LoadAssembly(new AssemblyName(args.Name)); + + Assembly ResolveAndLoadAssembly(string pathWithoutExtension, out string resolvedAssemblyPath) + { + foreach (var extension in Extensions) + { + resolvedAssemblyPath = pathWithoutExtension + extension; + + try + { + if (File.Exists(resolvedAssemblyPath)) + return Assembly.LoadFrom(resolvedAssemblyPath); + } + catch { } + } + + resolvedAssemblyPath = null; + return null; + } + + /// + /// Subscribes to the appropriate assembly resolution event, to provide automatic assembly resolution for + /// an assembly and any of its dependencies. Depending on the target platform, this may include the use + /// of the .deps.json file generated during the build process. + /// + /// An object which, when disposed, un-subscribes. + public static IDisposable SubscribeResolveForAssembly(string assemblyFileName, IMessageSink internalDiagnosticsMessageSink = null) + => new AssemblyHelper(Path.GetDirectoryName(Path.GetFullPath(assemblyFileName)), internalDiagnosticsMessageSink); + + /// + /// Subscribes to the appropriate assembly resolution event, to provide automatic assembly resolution for + /// an assembly and any of its dependencies. Depending on the target platform, this may include the use + /// of the .deps.json file generated during the build process. + /// + /// An object which, when disposed, un-subscribes. + public static IDisposable SubscribeResolveForAssembly(Type typeInAssembly, IMessageSink internalDiagnosticsMessageSink = null) + => new AssemblyHelper(Path.GetDirectoryName(typeInAssembly.Assembly.Location), internalDiagnosticsMessageSink); + } +} + +#endif \ No newline at end of file diff --git a/xunit.runner.worker/XunitUtil.cs b/xunit.runner.worker/XunitUtil.cs index f3e8e74..7a5f6a4 100644 --- a/xunit.runner.worker/XunitUtil.cs +++ b/xunit.runner.worker/XunitUtil.cs @@ -9,7 +9,7 @@ namespace Xunit.Runner.Worker protected static void Go(string assemblyFileName, Stream stream, AppDomainSupport appDomainSupport, Action action) { - using (AssemblyHelper.SubscribeResolve()) + using (AssemblyHelper.SubscribeResolveForAssembly(assemblyFileName)) using (var xunit = new XunitFrontController(appDomainSupport, assemblyFileName, shadowCopy: false)) using (var writer = new ClientWriter(stream)) { diff --git a/xunit.runner.worker/xunit.runner.worker.csproj b/xunit.runner.worker/xunit.runner.worker.csproj index e1d06a2..fec894e 100644 --- a/xunit.runner.worker/xunit.runner.worker.csproj +++ b/xunit.runner.worker/xunit.runner.worker.csproj @@ -3,11 +3,11 @@ Exe Xunit.Runner.Worker - net452 + net472 - + diff --git a/xunit.runner.wpf/xunit.runner.wpf.csproj b/xunit.runner.wpf/xunit.runner.wpf.csproj index 6505610..0c94732 100644 --- a/xunit.runner.wpf/xunit.runner.wpf.csproj +++ b/xunit.runner.wpf/xunit.runner.wpf.csproj @@ -9,6 +9,7 @@ Pilchie true true + net472 XUnit Gui written in WPF https://github.com/Pilchie/xunit.runner.wpf https://github.com/Pilchie/xunit.runner.wpf @@ -27,7 +28,6 @@ -