+
+ altrow
+
+ ✘
+ Test Assembly Cleanup
+ Test Collection Cleanup
+ Test Class Cleanup
+ Test Method Cleanup
+ Test Case Cleanup
+ Test Cleanup
+ Fatal Error
+
+ ()
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/JUnitXml.xslt b/src/Microsoft.DotNet.XUnitConsoleRunner/src/JUnitXml.xslt
new file mode 100644
index 00000000000..ad02d9da4cd
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/JUnitXml.xslt
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/Microsoft.DotNet.XUnitConsoleRunner.csproj b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Microsoft.DotNet.XUnitConsoleRunner.csproj
new file mode 100644
index 00000000000..2f4446cfbee
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Microsoft.DotNet.XUnitConsoleRunner.csproj
@@ -0,0 +1,30 @@
+
+
+
+ Microsoft.DotNet.XUnitConsoleRunner
+ xunit.console
+ true
+ Exe
+ Xunit.ConsoleClient
+ netcoreapp2.0
+ true
+ 2.5.0
+ latest
+ $(DefaultItemExcludes);common\AssemblyResolution\*.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/NUnitXml.xslt b/src/Microsoft.DotNet.XUnitConsoleRunner/src/NUnitXml.xslt
new file mode 100644
index 00000000000..76029f47b61
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/NUnitXml.xslt
@@ -0,0 +1,166 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+ True
+
+
+ Failure
+ Success
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Failure
+ Success
+
+
+ False
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Failure
+ Success
+
+
+ False
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+ True
+
+
+ Failure
+ Success
+ Skipped
+
+
+
+ False
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/ParallelismOption.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/ParallelismOption.cs
new file mode 100644
index 00000000000..9d4147ef05f
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/ParallelismOption.cs
@@ -0,0 +1,10 @@
+namespace Xunit.ConsoleClient
+{
+ public enum ParallelismOption
+ {
+ none,
+ collections,
+ assemblies,
+ all
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/Program.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Program.cs
new file mode 100644
index 00000000000..47a1b41819b
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Program.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Linq;
+
+namespace Xunit.ConsoleClient
+{
+ public class Program
+ {
+ [STAThread]
+ public static int Main(string[] args)
+ {
+ // This code must not contain any references to code in any external assembly (or any code that references any code in any
+ // other assembly) until AFTER the creation of the AssemblyHelper.
+ var consoleLock = new object();
+ var internalDiagnosticsMessageSink = DiagnosticMessageSink.ForInternalDiagnostics(consoleLock, args.Contains("-internaldiagnostics"), args.Contains("-nocolor"));
+
+#if !WINDOWS_UWP
+ using (AssemblyHelper.SubscribeResolveForAssembly(typeof(Program), internalDiagnosticsMessageSink))
+#endif
+ return new ConsoleRunner(consoleLock).EntryPoint(args);
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/RemoteExecutor.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/RemoteExecutor.cs
similarity index 90%
rename from src/Microsoft.DotNet.XUnitRunnerUap/src/RemoteExecutor.cs
rename to src/Microsoft.DotNet.XUnitConsoleRunner/src/RemoteExecutor.cs
index 8ce6c0a1158..e47d337c157 100644
--- a/src/Microsoft.DotNet.XUnitRunnerUap/src/RemoteExecutor.cs
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/RemoteExecutor.cs
@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+#if WINDOWS_UWP
+
using System;
using System.IO;
using System.Reflection;
@@ -10,7 +12,7 @@
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
-namespace Microsoft.DotNet.XUnitRunnerUap
+namespace Xunit.ConsoleClient
{
///
/// Provides an entry point in a new process that will load a specified method and invoke it.
@@ -113,22 +115,6 @@ public static int Execute(string[] args)
return exitCode;
}
- private static MethodInfo GetMethod(this Type type, string methodName)
- {
- Type t = type;
- while (t != null)
- {
- TypeInfo ti = t.GetTypeInfo();
- MethodInfo mi = ti.GetDeclaredMethod(methodName);
- if (mi != null)
- {
- return mi;
- }
- t = ti.BaseType;
- }
- return null;
- }
-
private static T[] Subarray(this T[] arr, int offset, int count)
{
var newArr = new T[count];
@@ -137,3 +123,5 @@ private static T[] Subarray(this T[] arr, int offset, int count)
}
}
}
+
+#endif
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/Sinks/DiagnosticMessageSink.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Sinks/DiagnosticMessageSink.cs
new file mode 100644
index 00000000000..c7432260237
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Sinks/DiagnosticMessageSink.cs
@@ -0,0 +1,54 @@
+using System;
+using Xunit.Abstractions;
+
+namespace Xunit.ConsoleClient
+{
+ public class DiagnosticMessageSink : MarshalByRefObject, IMessageSink
+ {
+ readonly string assemblyDisplayName;
+ readonly object consoleLock;
+ readonly ConsoleColor displayColor;
+ readonly bool noColor;
+ readonly bool showDiagnostics;
+
+ DiagnosticMessageSink(object consoleLock, string assemblyDisplayName, bool showDiagnostics, bool noColor, ConsoleColor displayColor)
+ {
+ this.consoleLock = consoleLock;
+ this.assemblyDisplayName = assemblyDisplayName;
+ this.noColor = noColor;
+ this.displayColor = displayColor;
+ this.showDiagnostics = showDiagnostics;
+ }
+
+ public static DiagnosticMessageSink ForDiagnostics(object consoleLock, string assemblyDisplayName, bool showDiagnostics, bool noColor)
+ => new DiagnosticMessageSink(consoleLock, assemblyDisplayName, showDiagnostics, noColor, ConsoleColor.Yellow);
+
+ public static DiagnosticMessageSink ForInternalDiagnostics(object consoleLock, bool showDiagnostics, bool noColor)
+ => new DiagnosticMessageSink(consoleLock, null, showDiagnostics, noColor, ConsoleColor.DarkGray);
+
+ public static DiagnosticMessageSink ForInternalDiagnostics(object consoleLock, string assemblyDisplayName, bool showDiagnostics, bool noColor)
+ => new DiagnosticMessageSink(consoleLock, assemblyDisplayName, showDiagnostics, noColor, ConsoleColor.DarkGray);
+
+ public bool OnMessage(IMessageSinkMessage message)
+ {
+ if (showDiagnostics && message is IDiagnosticMessage diagnosticMessage)
+ {
+ lock (consoleLock)
+ {
+ if (!noColor)
+ ConsoleHelper.SetForegroundColor(displayColor);
+
+ if (assemblyDisplayName != null)
+ Console.WriteLine($" {assemblyDisplayName}: {diagnosticMessage.Message}");
+ else
+ Console.WriteLine($" {diagnosticMessage.Message}");
+
+ if (!noColor)
+ ConsoleHelper.ResetColor();
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/Utility/ConsoleRunnerLogger.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Utility/ConsoleRunnerLogger.cs
new file mode 100644
index 00000000000..935269ef8a5
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Utility/ConsoleRunnerLogger.cs
@@ -0,0 +1,84 @@
+#if WINDOWS_UWP
+
+using System;
+
+namespace Xunit.ConsoleClient
+{
+ ///
+ /// An implementation of which logs messages
+ /// to and .
+ ///
+ public class ConsoleRunnerLogger : IRunnerLogger
+ {
+ readonly object lockObject;
+ readonly bool useColors;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// A flag to indicate whether colors should be used when
+ /// logging messages.
+ public ConsoleRunnerLogger(bool useColors) : this(useColors, new object()) { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// A flag to indicate whether colors should be used when
+ /// logging messages.
+ /// The lock object used to prevent console clashes.
+ public ConsoleRunnerLogger(bool useColors, object lockObject)
+ {
+ this.useColors = useColors;
+ this.lockObject = lockObject;
+ }
+
+ ///
+ public object LockObject => lockObject;
+
+ ///
+ public void LogError(StackFrameInfo stackFrame, string message)
+ {
+ using (SetColor(ConsoleColor.Red))
+ lock (LockObject)
+ Console.Error.WriteLine(message);
+ }
+
+ ///
+ public void LogImportantMessage(StackFrameInfo stackFrame, string message)
+ {
+ using (SetColor(ConsoleColor.Gray))
+ lock (LockObject)
+ Console.WriteLine(message);
+ }
+
+ ///
+ public void LogMessage(StackFrameInfo stackFrame, string message)
+ {
+ using (SetColor(ConsoleColor.DarkGray))
+ lock (LockObject)
+ Console.WriteLine(message);
+ }
+
+ ///
+ public void LogWarning(StackFrameInfo stackFrame, string message)
+ {
+ using (SetColor(ConsoleColor.Yellow))
+ lock (LockObject)
+ Console.WriteLine(message);
+ }
+
+ IDisposable SetColor(ConsoleColor color)
+ => useColors ? new ColorRestorer(color) : null;
+
+ class ColorRestorer : IDisposable
+ {
+ public ColorRestorer(ConsoleColor color)
+ => ConsoleHelper.SetForegroundColor(color);
+
+ public void Dispose()
+ => ConsoleHelper.ResetColor();
+ }
+ }
+}
+
+#endif
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/Utility/Transform.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Utility/Transform.cs
new file mode 100644
index 00000000000..5b4541a0f8f
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Utility/Transform.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Xml.Linq;
+
+namespace Xunit.ConsoleClient
+{
+ public class Transform
+ {
+ public string CommandLine;
+ public string Description;
+ public Action OutputHandler;
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/Utility/TransformFactory.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Utility/TransformFactory.cs
new file mode 100644
index 00000000000..7a54089729f
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/Utility/TransformFactory.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Xml;
+using System.Xml.Linq;
+
+namespace Xunit.ConsoleClient
+{
+ public class TransformFactory
+ {
+ static readonly TransformFactory instance = new TransformFactory();
+
+ readonly Dictionary availableTransforms = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ public static bool NoErrorColoring = false;
+
+ protected TransformFactory()
+ {
+ availableTransforms.Add("xml", new Transform
+ {
+ CommandLine = "xml",
+ Description = "output results to xUnit.net v2 XML file",
+ OutputHandler = Handler_DirectWrite
+ });
+ availableTransforms.Add("xmlv1", new Transform
+ {
+ CommandLine = "xmlv1",
+ Description = "output results to xUnit.net v1 XML file",
+ OutputHandler = (xml, outputFileName) => Handler_XslTransform("xmlv1", "xUnit1.xslt", xml, outputFileName)
+ });
+ availableTransforms.Add("html", new Transform
+ {
+ CommandLine = "html",
+ Description = "output results to HTML file",
+ OutputHandler = (xml, outputFileName) => Handler_XslTransform("html", "HTML.xslt", xml, outputFileName)
+ });
+ availableTransforms.Add("nunit", new Transform
+ {
+ CommandLine = "nunit",
+ Description = "output results to NUnit v2.5 XML file",
+ OutputHandler = (xml, outputFileName) => Handler_XslTransform("nunit", "NUnitXml.xslt", xml, outputFileName)
+ });
+ availableTransforms.Add("junit", new Transform
+ {
+ CommandLine = "junit",
+ Description = "output results to JUnit XML file",
+ OutputHandler = (xml, outputFileName) => Handler_XslTransform("junit", "JUnitXml.xslt", xml, outputFileName)
+ });
+ }
+
+ public static List AvailableTransforms
+ => instance.availableTransforms.Values.ToList();
+
+ public static List> GetXmlTransformers(XunitProject project)
+ => project.Output
+ .Select(output => new Action(xml => instance.availableTransforms[output.Key].OutputHandler(xml, output.Value)))
+ .ToList();
+
+ static void Handler_DirectWrite(XElement xml, string outputFileName)
+ {
+ using (var stream = File.Create(outputFileName))
+ xml.Save(stream);
+ }
+
+ static void Handler_XslTransform(string key, string resourceName, XElement xml, string outputFileName)
+ {
+ var xmlTransform = new System.Xml.Xsl.XslCompiledTransform();
+
+ using (var writer = XmlWriter.Create(outputFileName, new XmlWriterSettings { Indent = true }))
+ using (var xsltStream = typeof(TransformFactory).GetTypeInfo().Assembly.GetManifestResourceStream($"Xunit.ConsoleClient.{resourceName}"))
+ using (var xsltReader = XmlReader.Create(xsltStream))
+ using (var xmlReader = xml.CreateReader())
+ {
+ xmlTransform.Load(xsltReader);
+ xmlTransform.Transform(xmlReader, writer);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/build/xunit.runner.console.props b/src/Microsoft.DotNet.XUnitConsoleRunner/src/build/xunit.runner.console.props
new file mode 100644
index 00000000000..868af20c2e4
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/build/xunit.runner.console.props
@@ -0,0 +1,7 @@
+
+
+
+ $(MSBuildThisFileDirectory)..\tools\netcoreapp2.0\xunit.console.dll
+
+
+
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/AssemblyHelper.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/AssemblyHelper.cs
new file mode 100644
index 00000000000..1c7893b3cbf
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/AssemblyHelper.cs
@@ -0,0 +1,103 @@
+using System;
+using System.IO;
+using System.Reflection;
+using System.Runtime.Loader;
+using Internal.Microsoft.Extensions.DependencyModel;
+using Xunit.Abstractions;
+
+namespace Xunit
+{
+ ///
+ /// This class provides assistance with assembly resolution for missing assemblies.
+ ///
+ class AssemblyHelper : AssemblyLoadContext, IDisposable
+ {
+ readonly DependencyContextAssemblyCache assemblyCache;
+ readonly IMessageSink internalDiagnosticsMessageSink;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The path to the assembly
+ /// An optional message sink for use with internal diagnostics messages;
+ /// may pass null for no internal diagnostics messages
+ public AssemblyHelper(string assemblyFileName, IMessageSink internalDiagnosticsMessageSink)
+ {
+ this.internalDiagnosticsMessageSink = internalDiagnosticsMessageSink;
+
+ if (!File.Exists(assemblyFileName))
+ {
+ if (internalDiagnosticsMessageSink != null)
+ internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[AssemblyHelper_NetCoreApp..ctor] Assembly file not found: '{assemblyFileName}'"));
+ return;
+ }
+
+ var assembly = LoadFromAssemblyPath(assemblyFileName);
+ if (assembly == null)
+ {
+ if (internalDiagnosticsMessageSink != 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 (internalDiagnosticsMessageSink != null)
+ internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[AssemblyHelper_NetCoreApp..ctor] Assembly file does not contain dependency manifest: '{assemblyFileName}'"));
+ return;
+ }
+
+ var assemblyFolder = Path.GetDirectoryName(assemblyFileName);
+ assemblyCache = new DependencyContextAssemblyCache(assemblyFolder, dependencyContext, internalDiagnosticsMessageSink);
+
+ Default.Resolving += OnResolving;
+ }
+
+ ///
+ public void Dispose()
+ {
+ if (assemblyCache != null)
+ Default.Resolving -= OnResolving;
+ }
+
+ ///
+ protected override Assembly Load(AssemblyName assemblyName)
+ => Default.LoadFromAssemblyName(assemblyName);
+
+ ///
+ protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
+ {
+ var result = default(IntPtr);
+
+ if (assemblyCache != null)
+ result = assemblyCache.LoadUnmanagedLibrary(unmanagedDllName, path => LoadUnmanagedDllFromPath(path));
+
+ if (result == default)
+ result = base.LoadUnmanagedDll(unmanagedDllName);
+
+ return result;
+ }
+
+ Assembly OnResolving(AssemblyLoadContext context, AssemblyName name)
+ => assemblyCache?.LoadManagedDll(name.Name, path => LoadFromAssemblyPath(path));
+
+ ///
+ /// 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(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(typeInAssembly.GetTypeInfo().Assembly.Location, internalDiagnosticsMessageSink);
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/DependencyContextAssemblyCache.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/DependencyContextAssemblyCache.cs
new file mode 100644
index 00000000000..bc6d6068c2d
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/DependencyContextAssemblyCache.cs
@@ -0,0 +1,295 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using Internal.Microsoft.DotNet.PlatformAbstractions;
+using Internal.Microsoft.Extensions.DependencyModel;
+using Xunit.Abstractions;
+using RuntimeEnvironment = Internal.Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment;
+
+namespace Xunit
+{
+ class DependencyContextAssemblyCache
+ {
+ static readonly RuntimeFallbacks AnyAndBase = new RuntimeFallbacks("unknown", "any", "base");
+ static readonly string[] ManagedAssemblyExtensions = { ".dll", ".exe" };
+ static readonly Tuple ManagedAssemblyNotFound = new Tuple(null, null);
+ static readonly Regex RuntimeIdRegex = new Regex(@"(?[A-Za-z0-9]+)(\.(?[0-9\.]+))?(?\-[A-Za-z0-9]+)?(?\-[A-Za-z0-9]+)?");
+
+ readonly string assemblyFolder;
+ readonly XunitPackageCompilationAssemblyResolver assemblyResolver;
+ readonly string currentRuntimeIdentifier;
+ readonly DependencyContext dependencyContext;
+ readonly Lazy fallbackRuntimeIdentifier;
+ readonly IFileSystem fileSystem;
+ readonly IMessageSink internalDiagnosticsMessageSink;
+ readonly Dictionary managedAssemblyCache;
+ readonly Dictionary> managedAssemblyMap;
+ readonly Platform operatingSystemPlatform;
+ readonly string[] unmanagedDllFormats;
+ readonly Dictionary unmanagedAssemblyCache;
+ readonly Dictionary> unmanagedAssemblyMap;
+
+ public DependencyContextAssemblyCache(string assemblyFolder,
+ DependencyContext dependencyContext,
+ IMessageSink internalDiagnosticsMessageSink,
+ Platform? operatingSystemPlatform = null,
+ string currentRuntimeIdentifier = null,
+ IFileSystem fileSystem = null)
+ {
+ this.assemblyFolder = assemblyFolder;
+ this.dependencyContext = dependencyContext;
+ this.internalDiagnosticsMessageSink = internalDiagnosticsMessageSink;
+ this.operatingSystemPlatform = operatingSystemPlatform ?? RuntimeEnvironment.OperatingSystemPlatform;
+ this.currentRuntimeIdentifier = currentRuntimeIdentifier ?? RuntimeEnvironment.GetRuntimeIdentifier();
+ this.fileSystem = fileSystem ?? new FileSystemWrapper();
+
+ fallbackRuntimeIdentifier = new Lazy(() => GetFallbackRuntime(this.currentRuntimeIdentifier));
+ assemblyResolver = new XunitPackageCompilationAssemblyResolver(internalDiagnosticsMessageSink, fileSystem);
+
+ if (internalDiagnosticsMessageSink != null)
+ internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache..ctor] Runtime graph: [{string.Join(",", dependencyContext.RuntimeGraph.Select(x => $"'{x.Runtime}'"))}]"));
+
+ var compatibleRuntimes = GetCompatibleRuntimes(dependencyContext);
+
+ if (internalDiagnosticsMessageSink != null)
+ internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache..ctor] Compatible runtimes: [{string.Join(",", compatibleRuntimes.Select(x => $"'{x}'"))}]"));
+
+ managedAssemblyCache = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ managedAssemblyMap =
+ dependencyContext.RuntimeLibraries
+ .Where(lib => lib.RuntimeAssemblyGroups?.Count > 0)
+ .Select(lib => compatibleRuntimes.Select(runtime => Tuple.Create(lib, lib.RuntimeAssemblyGroups.FirstOrDefault(libGroup => string.Equals(libGroup.Runtime, runtime))))
+ .FirstOrDefault(tuple => tuple.Item2?.AssetPaths != null))
+ .Where(tuple => tuple != null)
+ .SelectMany(tuple => tuple.Item2.AssetPaths.Where(x => x != null)
+ .Select(path => Tuple.Create(Path.GetFileNameWithoutExtension(path), Tuple.Create(tuple.Item1, tuple.Item2))))
+ .ToDictionaryIgnoringDuplicateKeys(tuple => tuple.Item1, tuple => tuple.Item2, StringComparer.OrdinalIgnoreCase);
+
+ if (internalDiagnosticsMessageSink != null)
+ internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache..ctor] Managed assembly map: [{string.Join(",", managedAssemblyMap.Keys.Select(k => $"'{k}'").OrderBy(k => k, StringComparer.OrdinalIgnoreCase))}]"));
+
+ unmanagedDllFormats = GetUnmanagedDllFormats().ToArray();
+ unmanagedAssemblyCache = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ unmanagedAssemblyMap =
+ dependencyContext.RuntimeLibraries
+ .Select(lib => compatibleRuntimes.Select(runtime => Tuple.Create(lib, lib.NativeLibraryGroups.FirstOrDefault(libGroup => string.Equals(libGroup.Runtime, runtime))))
+ .FirstOrDefault(tuple => tuple.Item2?.AssetPaths != null))
+ .Where(tuple => tuple != null)
+ .SelectMany(tuple => tuple.Item2.AssetPaths.Where(x => x != null)
+ .Select(path => Tuple.Create(Path.GetFileName(path), Tuple.Create(tuple.Item1, tuple.Item2))))
+ .ToDictionaryIgnoringDuplicateKeys(tuple => tuple.Item1, tuple => tuple.Item2, StringComparer.OrdinalIgnoreCase);
+
+ if (internalDiagnosticsMessageSink != null)
+ internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache..ctor] Unmanaged assembly map: [{string.Join(",", unmanagedAssemblyMap.Keys.Select(k => $"'{k}'").OrderBy(k => k, StringComparer.OrdinalIgnoreCase))}]"));
+ }
+
+ List GetCompatibleRuntimes(DependencyContext dependencyContext)
+ {
+ var result = new List(GetFallbacks(dependencyContext.RuntimeGraph).Fallbacks);
+ result.Insert(0, fallbackRuntimeIdentifier.IsValueCreated ? fallbackRuntimeIdentifier.Value : currentRuntimeIdentifier);
+ result.Add(string.Empty);
+ return result;
+ }
+
+ RuntimeFallbacks GetFallbacks(IReadOnlyList runtimeGraph)
+ => runtimeGraph.FirstOrDefault(x => string.Equals(x.Runtime, currentRuntimeIdentifier, StringComparison.OrdinalIgnoreCase))
+ ?? runtimeGraph.FirstOrDefault(x => string.Equals(x.Runtime, fallbackRuntimeIdentifier.Value, StringComparison.OrdinalIgnoreCase))
+ ?? AnyAndBase;
+
+ // This mimics the behavior of https://github.com/dotnet/core-setup/blob/863047f3ca16bada3ffc82493d1dbad6e560b80a/src/corehost/common/pal.h#L53-L73
+ string GetFallbackRuntime(string runtime)
+ {
+ var match = RuntimeIdRegex.Match(runtime);
+ var arch = match?.Groups?["arch"]?.Value;
+ var result = default(string);
+
+ switch (operatingSystemPlatform)
+ {
+ case Platform.Windows:
+ result = "win10" + arch;
+ break;
+
+ case Platform.Darwin:
+ result = "osx.10.12" + arch;
+ break;
+
+ case Platform.Linux:
+ result = "linux" + arch;
+ break;
+
+ default:
+ result = "unknown";
+ break;
+ }
+
+ if (internalDiagnosticsMessageSink != null)
+ internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.GetFallbackRuntime] Could not find runtime '{runtime}', falling back to '{result}'"));
+
+ return result;
+ }
+
+ IEnumerable GetUnmanagedDllFormats()
+ {
+ yield return "{0}";
+
+ if (operatingSystemPlatform == Platform.Windows)
+ {
+ yield return "{0}.dll";
+ }
+ else if (operatingSystemPlatform == Platform.Darwin)
+ {
+ yield return "lib{0}.dylib";
+ yield return "{0}.dylib";
+ }
+ else if (operatingSystemPlatform == Platform.Linux)
+ {
+ yield return "lib{0}.so";
+ yield return "{0}.so";
+ }
+ }
+
+ public Assembly LoadManagedDll(string assemblyName, Func managedAssemblyLoader)
+ {
+ if (!managedAssemblyCache.TryGetValue(assemblyName, out var result))
+ {
+ var tupleResult = ResolveManagedAssembly(assemblyName, managedAssemblyLoader);
+ var resolvedAssemblyPath = tupleResult.Item1;
+ result = tupleResult.Item2;
+ managedAssemblyCache[assemblyName] = result;
+
+ if (internalDiagnosticsMessageSink != null)
+ {
+ if (result == 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}'"));
+ }
+ }
+
+ return result;
+ }
+
+ public IntPtr LoadUnmanagedLibrary(string unmanagedLibraryName, Func unmanagedAssemblyLoader)
+ {
+ var result = default(IntPtr);
+ var needDiagnostics = false;
+
+ if (!unmanagedAssemblyCache.TryGetValue(unmanagedLibraryName, out var resolvedAssemblyPath))
+ {
+ resolvedAssemblyPath = ResolveUnmanagedLibrary(unmanagedLibraryName);
+ unmanagedAssemblyCache[unmanagedLibraryName] = resolvedAssemblyPath;
+ needDiagnostics = true;
+ }
+
+ if (resolvedAssemblyPath != null)
+ result = unmanagedAssemblyLoader(resolvedAssemblyPath);
+
+ if (needDiagnostics && internalDiagnosticsMessageSink != null)
+ if (result != default)
+ internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.LoadUnmanagedLibrary] Resolved '{unmanagedLibraryName}' to '{resolvedAssemblyPath}'"));
+ else
+ {
+ if (resolvedAssemblyPath != 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"));
+ }
+
+ return result;
+ }
+
+ Tuple ResolveManagedAssembly(string assemblyName, Func managedAssemblyLoader)
+ {
+ // Try to find dependency in the local folder
+ var assemblyPath = Path.Combine(Path.GetFullPath(assemblyFolder), assemblyName);
+
+ foreach (var extension in ManagedAssemblyExtensions)
+ try
+ {
+ var resolvedAssemblyPath = assemblyPath + extension;
+ if (fileSystem.File.Exists(resolvedAssemblyPath))
+ {
+ var assembly = managedAssemblyLoader(resolvedAssemblyPath);
+ if (assembly != null)
+ return Tuple.Create(resolvedAssemblyPath, assembly);
+ }
+ }
+ catch { }
+
+ // Try to find dependency from .deps.json
+ if (managedAssemblyMap.TryGetValue(assemblyName, out var libraryTuple))
+ {
+ var library = libraryTuple.Item1;
+ var assetGroup = libraryTuple.Item2;
+ var wrapper = new CompilationLibrary(library.Type, library.Name, library.Version, library.Hash,
+ assetGroup.AssetPaths, library.Dependencies, library.Serviceable,
+ library.Path, library.HashPath);
+
+ var assemblies = new List();
+ if (assemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies))
+ {
+ var resolvedAssemblyPath = assemblies.FirstOrDefault(a => string.Equals(assemblyName, Path.GetFileNameWithoutExtension(a), StringComparison.OrdinalIgnoreCase));
+ if (resolvedAssemblyPath != null)
+ {
+ resolvedAssemblyPath = Path.GetFullPath(resolvedAssemblyPath);
+
+ var assembly = managedAssemblyLoader(resolvedAssemblyPath);
+ if (assembly != null)
+ return Tuple.Create(resolvedAssemblyPath, assembly);
+
+ if (internalDiagnosticsMessageSink != null)
+ internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.ResolveManagedAssembly] Resolving '{assemblyName}', found assembly path '{resolvedAssemblyPath}' but the assembly would not load"));
+ }
+ else
+ {
+ if (internalDiagnosticsMessageSink != null)
+ internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.ResolveManagedAssembly] Resolving '{assemblyName}', found a resolved path, but could not map a filename in [{string.Join(",", assemblies.OrderBy(k => k, StringComparer.OrdinalIgnoreCase).Select(k => $"'{k}'"))}]"));
+ }
+ }
+ else
+ {
+ if (internalDiagnosticsMessageSink != null)
+ internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.ResolveManagedAssembly] Resolving '{assemblyName}', found in dependency map, but unable to resolve a path in [{string.Join(",", assetGroup.AssetPaths.OrderBy(k => k, StringComparer.OrdinalIgnoreCase).Select(k => $"'{k}'"))}]"));
+ }
+ }
+
+ return ManagedAssemblyNotFound;
+ }
+
+ public string ResolveUnmanagedLibrary(string unmanagedLibraryName)
+ {
+ foreach (var format in unmanagedDllFormats)
+ {
+ var formattedUnmanagedDllName = string.Format(format, unmanagedLibraryName);
+
+ if (unmanagedAssemblyMap.TryGetValue(formattedUnmanagedDllName, out var libraryTuple))
+ {
+ var library = libraryTuple.Item1;
+ var assetGroup = libraryTuple.Item2;
+ var wrapper = new CompilationLibrary(library.Type, library.Name, library.Version, library.Hash, assetGroup.AssetPaths, library.Dependencies, library.Serviceable);
+
+ var assemblies = new List();
+ if (assemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies))
+ {
+ var resolvedAssemblyPath = assemblies.FirstOrDefault(a => string.Equals(formattedUnmanagedDllName, Path.GetFileName(a), StringComparison.OrdinalIgnoreCase));
+ if (resolvedAssemblyPath != null)
+ return Path.GetFullPath(resolvedAssemblyPath);
+
+ if (internalDiagnosticsMessageSink != null)
+ 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}'"))}]"));
+ }
+ else
+ {
+ if (internalDiagnosticsMessageSink != null)
+ internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[DependencyContextAssemblyCache.ResolveUnmanagedLibrary] Found in dependency map, but unable to resolve a path in [{string.Join(",", assetGroup.AssetPaths.OrderBy(k => k, StringComparer.OrdinalIgnoreCase).Select(k => $"'{k}'"))}]"));
+ }
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/ApplicationEnvironment.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/ApplicationEnvironment.cs
new file mode 100644
index 00000000000..74ed0624652
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/ApplicationEnvironment.cs
@@ -0,0 +1,19 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.IO;
+
+namespace Internal.Microsoft.DotNet.PlatformAbstractions
+{
+ internal static class ApplicationEnvironment
+ {
+ public static string ApplicationBasePath { get; } = GetApplicationBasePath();
+
+ private static string GetApplicationBasePath()
+ {
+ var basePath = AppContext.BaseDirectory;
+ return Path.GetFullPath(basePath);
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/HashCodeCombiner.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/HashCodeCombiner.cs
new file mode 100644
index 00000000000..a8290e3586d
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/HashCodeCombiner.cs
@@ -0,0 +1,43 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime.CompilerServices;
+
+namespace Internal.Microsoft.DotNet.PlatformAbstractions
+{
+ internal struct HashCodeCombiner
+ {
+ private long _combinedHash64;
+
+ public int CombinedHash
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get { return _combinedHash64.GetHashCode(); }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private HashCodeCombiner(long seed)
+ {
+ _combinedHash64 = seed;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Add(int i)
+ {
+ _combinedHash64 = ((_combinedHash64 << 5) + _combinedHash64) ^ i;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Add(string s)
+ {
+ var hashCode = (s != null) ? s.GetHashCode() : 0;
+ Add(hashCode);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static HashCodeCombiner Start()
+ {
+ return new HashCodeCombiner(0x1505L);
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Native/NativeMethods.Darwin.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Native/NativeMethods.Darwin.cs
new file mode 100644
index 00000000000..532d52b4325
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Native/NativeMethods.Darwin.cs
@@ -0,0 +1,55 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Internal.Microsoft.DotNet.PlatformAbstractions.Native
+{
+ internal static partial class NativeMethods
+ {
+ public static class Darwin
+ {
+ private const int CTL_KERN = 1;
+ private const int KERN_OSRELEASE = 2;
+
+ public unsafe static string GetKernelRelease()
+ {
+ const uint BUFFER_LENGTH = 32;
+
+ var name = stackalloc int[2];
+ name[0] = CTL_KERN;
+ name[1] = KERN_OSRELEASE;
+
+ var buf = stackalloc byte[(int)BUFFER_LENGTH];
+ var len = stackalloc uint[1];
+ *len = BUFFER_LENGTH;
+
+ try
+ {
+ // If the buffer isn't big enough, it seems sysctl still returns 0 and just sets len to the
+ // necessary buffer size. This appears to be contrary to the man page, but it's easy to detect
+ // by simply checking len against the buffer length.
+ if (sysctl(name, 2, buf, len, IntPtr.Zero, 0) == 0 && *len < BUFFER_LENGTH)
+ {
+ return Marshal.PtrToStringAnsi((IntPtr)buf, (int)*len);
+ }
+ }
+ catch (Exception ex)
+ {
+ throw new PlatformNotSupportedException("Error reading Darwin Kernel Version", ex);
+ }
+ throw new PlatformNotSupportedException("Unknown error reading Darwin Kernel Version");
+ }
+
+ [DllImport("libc")]
+ private unsafe static extern int sysctl(
+ int* name,
+ uint namelen,
+ byte* oldp,
+ uint* oldlenp,
+ IntPtr newp,
+ uint newlen);
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Native/NativeMethods.Windows.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Native/NativeMethods.Windows.cs
new file mode 100644
index 00000000000..d9e26a5c129
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Native/NativeMethods.Windows.cs
@@ -0,0 +1,43 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime.InteropServices;
+
+namespace Internal.Microsoft.DotNet.PlatformAbstractions.Native
+{
+ internal static partial class NativeMethods
+ {
+ public static class Windows
+ {
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct RTL_OSVERSIONINFOEX
+ {
+ internal uint dwOSVersionInfoSize;
+ internal uint dwMajorVersion;
+ internal uint dwMinorVersion;
+ internal uint dwBuildNumber;
+ internal uint dwPlatformId;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
+ internal string szCSDVersion;
+ }
+
+ // This call avoids the shimming Windows does to report old versions
+ [DllImport("ntdll")]
+ private static extern int RtlGetVersion(out RTL_OSVERSIONINFOEX lpVersionInformation);
+
+ internal static string RtlGetVersion()
+ {
+ RTL_OSVERSIONINFOEX osvi = new RTL_OSVERSIONINFOEX();
+ osvi.dwOSVersionInfoSize = (uint)Marshal.SizeOf(osvi);
+ if (RtlGetVersion(out osvi) == 0)
+ {
+ return $"{osvi.dwMajorVersion}.{osvi.dwMinorVersion}.{osvi.dwBuildNumber}";
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Native/PlatformApis.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Native/PlatformApis.cs
new file mode 100644
index 00000000000..8fb2c3dc2c0
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Native/PlatformApis.cs
@@ -0,0 +1,159 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace Internal.Microsoft.DotNet.PlatformAbstractions.Native
+{
+ internal static class PlatformApis
+ {
+ private class DistroInfo
+ {
+ public string Id;
+ public string VersionId;
+ }
+
+ private static readonly Lazy _platform = new Lazy(DetermineOSPlatform);
+ private static readonly Lazy _distroInfo = new Lazy(LoadDistroInfo);
+
+ public static string GetOSName()
+ {
+ switch (GetOSPlatform())
+ {
+ case Platform.Windows:
+ return "Windows";
+ case Platform.Linux:
+ return GetDistroId() ?? "Linux";
+ case Platform.Darwin:
+ return "Mac OS X";
+ default:
+ return "Unknown";
+ }
+ }
+
+ public static string GetOSVersion()
+ {
+ switch (GetOSPlatform())
+ {
+ case Platform.Windows:
+ return NativeMethods.Windows.RtlGetVersion() ?? string.Empty;
+ case Platform.Linux:
+ return GetDistroVersionId() ?? string.Empty;
+ case Platform.Darwin:
+ return GetDarwinVersion() ?? string.Empty;
+ default:
+ return string.Empty;
+ }
+ }
+
+ private static string GetDarwinVersion()
+ {
+ Version version;
+ var kernelRelease = NativeMethods.Darwin.GetKernelRelease();
+ if (!Version.TryParse(kernelRelease, out version) || version.Major < 5)
+ {
+ // 10.0 covers all versions prior to Darwin 5
+ // Similarly, if the version is not a valid version number, but we have still detected that it is Darwin, we just assume
+ // it is OS X 10.0
+ return "10.0";
+ }
+ else
+ {
+ // Mac OS X 10.1 mapped to Darwin 5.x, and the mapping continues that way
+ // So just subtract 4 from the Darwin version.
+ // https://en.wikipedia.org/wiki/Darwin_%28operating_system%29
+ return $"10.{version.Major - 4}";
+ }
+ }
+
+ public static Platform GetOSPlatform()
+ {
+ return _platform.Value;
+ }
+
+ private static string GetDistroId()
+ {
+ return _distroInfo.Value?.Id;
+ }
+
+ private static string GetDistroVersionId()
+ {
+ return _distroInfo.Value?.VersionId;
+ }
+
+ private static DistroInfo LoadDistroInfo()
+ {
+ // Sample os-release file:
+ // NAME="Ubuntu"
+ // VERSION = "14.04.3 LTS, Trusty Tahr"
+ // ID = ubuntu
+ // ID_LIKE = debian
+ // PRETTY_NAME = "Ubuntu 14.04.3 LTS"
+ // VERSION_ID = "14.04"
+ // HOME_URL = "http://www.ubuntu.com/"
+ // SUPPORT_URL = "http://help.ubuntu.com/"
+ // BUG_REPORT_URL = "http://bugs.launchpad.net/ubuntu/"
+ // We use ID and VERSION_ID
+
+ if (File.Exists("/etc/os-release"))
+ {
+ var lines = File.ReadAllLines("/etc/os-release");
+ var result = new DistroInfo();
+ foreach (var line in lines)
+ {
+ if (line.StartsWith("ID=", StringComparison.Ordinal))
+ {
+ result.Id = line.Substring(3).Trim('"', '\'');
+ }
+ else if (line.StartsWith("VERSION_ID=", StringComparison.Ordinal))
+ {
+ result.VersionId = line.Substring(11).Trim('"', '\'');
+ }
+ }
+
+ return NormalizeDistroInfo(result);
+ }
+ return null;
+ }
+
+ // For some distros, we don't want to use the full version from VERSION_ID. One example is
+ // Red Hat Enterprise Linux, which includes a minor version in their VERSION_ID but minor
+ // versions are backwards compatable.
+ //
+ // In this case, we'll normalized RIDs like 'rhel.7.2' and 'rhel.7.3' to a generic
+ // 'rhel.7'. This brings RHEL in line with other distros like CentOS or Debian which
+ // don't put minor version numbers in their VERSION_ID fields because all minor versions
+ // are backwards compatible.
+ private static DistroInfo NormalizeDistroInfo(DistroInfo distroInfo)
+ {
+ // Handle if VersionId is null by just setting the index to -1.
+ int minorVersionNumberSeparatorIndex = distroInfo.VersionId?.IndexOf('.') ?? -1;
+
+ if (distroInfo.Id == "rhel" && minorVersionNumberSeparatorIndex != -1)
+ {
+ distroInfo.VersionId = distroInfo.VersionId.Substring(0, minorVersionNumberSeparatorIndex);
+ }
+
+ return distroInfo;
+ }
+
+ private static Platform DetermineOSPlatform()
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return Platform.Windows;
+ }
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ return Platform.Linux;
+ }
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ return Platform.Darwin;
+ }
+ return Platform.Unknown;
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Platform.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Platform.cs
new file mode 100644
index 00000000000..dba0bce892b
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/Platform.cs
@@ -0,0 +1,13 @@
+// Copyright(c) .NET Foundation and contributors.All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Internal.Microsoft.DotNet.PlatformAbstractions
+{
+ internal enum Platform
+ {
+ Unknown = 0,
+ Windows = 1,
+ Linux = 2,
+ Darwin = 3
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/RuntimeEnvironment.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/RuntimeEnvironment.cs
new file mode 100644
index 00000000000..a9849f5287f
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions/RuntimeEnvironment.cs
@@ -0,0 +1,105 @@
+// Copyright(c) .NET Foundation and contributors.All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using Internal.Microsoft.DotNet.PlatformAbstractions.Native;
+
+namespace Internal.Microsoft.DotNet.PlatformAbstractions
+{
+ internal static class RuntimeEnvironment
+ {
+ private static readonly string OverrideEnvironmentVariableName = "DOTNET_RUNTIME_ID";
+
+ public static Platform OperatingSystemPlatform { get; } = PlatformApis.GetOSPlatform();
+
+ public static string OperatingSystemVersion { get; } = PlatformApis.GetOSVersion();
+
+ public static string OperatingSystem { get; } = PlatformApis.GetOSName();
+
+ public static string RuntimeArchitecture { get; } = GetArch();
+
+ private static string GetArch()
+ {
+ return IntPtr.Size == 8 ? "x64" : "x86";
+ }
+
+ public static string GetRuntimeIdentifier()
+ {
+ return
+ Environment.GetEnvironmentVariable(OverrideEnvironmentVariableName) ??
+ (GetRIDOS() + GetRIDVersion() + GetRIDArch());
+ }
+
+ private static string GetRIDArch()
+ {
+ if (!string.IsNullOrEmpty(RuntimeArchitecture))
+ {
+ return $"-{RuntimeArchitecture.ToLowerInvariant()}";
+ }
+ return string.Empty;
+ }
+
+ private static string GetRIDVersion()
+ {
+ // Windows RIDs do not separate OS name and version by "." due to legacy
+ // Others do, that's why we have the "." prefix on them below
+ switch (OperatingSystemPlatform)
+ {
+ case Platform.Windows:
+ return GetWindowsProductVersion();
+ case Platform.Linux:
+ if (string.IsNullOrEmpty(OperatingSystemVersion))
+ {
+ return string.Empty;
+ }
+
+ return $".{OperatingSystemVersion}";
+ case Platform.Darwin:
+ return $".{OperatingSystemVersion}";
+ default:
+ return string.Empty; // Unknown Platform? Unknown Version!
+ }
+ }
+
+ private static string GetWindowsProductVersion()
+ {
+ var ver = Version.Parse(OperatingSystemVersion);
+ if (ver.Major == 6)
+ {
+ if (ver.Minor == 1)
+ {
+ return "7";
+ }
+ else if (ver.Minor == 2)
+ {
+ return "8";
+ }
+ else if (ver.Minor == 3)
+ {
+ return "81";
+ }
+ }
+ else if (ver.Major >= 10)
+ {
+ // Return the major version for use in RID computation without applying any cap.
+ return ver.Major.ToString();
+ }
+ return string.Empty; // Unknown version
+ }
+
+ private static string GetRIDOS()
+ {
+ switch (OperatingSystemPlatform)
+ {
+ case Platform.Windows:
+ return "win";
+ case Platform.Linux:
+ return OperatingSystem.ToLowerInvariant();
+ case Platform.Darwin:
+ return "osx";
+ default:
+ return "unknown";
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/CompilationLibrary.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/CompilationLibrary.cs
new file mode 100644
index 00000000000..815b0aecfde
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/CompilationLibrary.cs
@@ -0,0 +1,60 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Internal.Microsoft.Extensions.DependencyModel.Resolution;
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal class CompilationLibrary : Library
+ {
+ public CompilationLibrary(string type,
+ string name,
+ string version,
+ string hash,
+ IEnumerable assemblies,
+ IEnumerable dependencies,
+ bool serviceable)
+ : this(type, name, version, hash, assemblies, dependencies, serviceable, path: null, hashPath: null)
+ {
+ }
+
+ public CompilationLibrary(string type,
+ string name,
+ string version,
+ string hash,
+ IEnumerable assemblies,
+ IEnumerable dependencies,
+ bool serviceable,
+ string path,
+ string hashPath)
+ : base(type, name, version, hash, dependencies, serviceable, path, hashPath)
+ {
+ if (assemblies == null)
+ {
+ throw new ArgumentNullException(nameof(assemblies));
+ }
+ Assemblies = assemblies.ToArray();
+ }
+
+ public IReadOnlyList Assemblies { get; }
+
+ internal static ICompilationAssemblyResolver DefaultResolver { get; } = new CompositeCompilationAssemblyResolver(new ICompilationAssemblyResolver[]
+ {
+ new AppBaseCompilationAssemblyResolver(),
+ new ReferenceAssemblyPathResolver(),
+ new PackageCompilationAssemblyResolver()
+ });
+
+ private IEnumerable ResolveReferencePaths(ICompilationAssemblyResolver resolver, List assemblies)
+ {
+ if (!resolver.TryResolveAssemblyPaths(this, assemblies))
+ {
+ throw new InvalidOperationException($"Cannot find compilation library location for package '{Name}'");
+ }
+ return assemblies;
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/CompilationOptions.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/CompilationOptions.cs
new file mode 100644
index 00000000000..a517e80e2a1
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/CompilationOptions.cs
@@ -0,0 +1,81 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal class CompilationOptions
+ {
+ public IReadOnlyList Defines { get; }
+
+ public string LanguageVersion { get; }
+
+ public string Platform { get; }
+
+ public bool? AllowUnsafe { get; }
+
+ public bool? WarningsAsErrors { get; }
+
+ public bool? Optimize { get; }
+
+ public string KeyFile { get; }
+
+ public bool? DelaySign { get; }
+
+ public bool? PublicSign { get; }
+
+ public string DebugType { get; }
+
+ public bool? EmitEntryPoint { get; }
+
+ public bool? GenerateXmlDocumentation { get; }
+
+ public static CompilationOptions Default { get; } = new CompilationOptions(
+ defines: Enumerable.Empty(),
+ languageVersion: null,
+ platform: null,
+ allowUnsafe: null,
+ warningsAsErrors: null,
+ optimize: null,
+ keyFile: null,
+ delaySign: null,
+ publicSign: null,
+ debugType: null,
+ emitEntryPoint: null,
+ generateXmlDocumentation: null);
+
+ public CompilationOptions(IEnumerable defines,
+ string languageVersion,
+ string platform,
+ bool? allowUnsafe,
+ bool? warningsAsErrors,
+ bool? optimize,
+ string keyFile,
+ bool? delaySign,
+ bool? publicSign,
+ string debugType,
+ bool? emitEntryPoint,
+ bool? generateXmlDocumentation)
+ {
+ if (defines == null)
+ {
+ throw new ArgumentNullException(nameof(defines));
+ }
+ Defines = defines.ToArray();
+ LanguageVersion = languageVersion;
+ Platform = platform;
+ AllowUnsafe = allowUnsafe;
+ WarningsAsErrors = warningsAsErrors;
+ Optimize = optimize;
+ KeyFile = keyFile;
+ DelaySign = delaySign;
+ PublicSign = publicSign;
+ DebugType = debugType;
+ EmitEntryPoint = emitEntryPoint;
+ GenerateXmlDocumentation = generateXmlDocumentation;
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Dependency.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Dependency.cs
new file mode 100644
index 00000000000..795e2de48bc
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Dependency.cs
@@ -0,0 +1,47 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using Internal.Microsoft.DotNet.PlatformAbstractions;
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal struct Dependency
+ {
+ public Dependency(string name, string version)
+ {
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentException(nameof(name));
+ }
+ if (string.IsNullOrEmpty(version))
+ {
+ throw new ArgumentException(nameof(version));
+ }
+ Name = name;
+ Version = version;
+ }
+
+ public string Name { get; }
+ public string Version { get; }
+
+ public bool Equals(Dependency other)
+ {
+ return string.Equals(Name, other.Name) && string.Equals(Version, other.Version);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ return obj is Dependency && Equals((Dependency) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ var combiner = HashCodeCombiner.Start();
+ combiner.Add(Name);
+ combiner.Add(Version);
+ return combiner.CombinedHash;
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContext.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContext.cs
new file mode 100644
index 00000000000..83b34ac3da7
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContext.cs
@@ -0,0 +1,106 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal class DependencyContext
+ {
+ private static readonly Lazy _defaultContext = new Lazy(LoadDefault);
+
+ public DependencyContext(TargetInfo target,
+ CompilationOptions compilationOptions,
+ IEnumerable compileLibraries,
+ IEnumerable runtimeLibraries,
+ IEnumerable runtimeGraph)
+ {
+ if (target == null)
+ {
+ throw new ArgumentNullException(nameof(target));
+ }
+ if (compilationOptions == null)
+ {
+ throw new ArgumentNullException(nameof(compilationOptions));
+ }
+ if (compileLibraries == null)
+ {
+ throw new ArgumentNullException(nameof(compileLibraries));
+ }
+ if (runtimeLibraries == null)
+ {
+ throw new ArgumentNullException(nameof(runtimeLibraries));
+ }
+ if (runtimeGraph == null)
+ {
+ throw new ArgumentNullException(nameof(runtimeGraph));
+ }
+
+ Target = target;
+ CompilationOptions = compilationOptions;
+ CompileLibraries = compileLibraries.ToArray();
+ RuntimeLibraries = runtimeLibraries.ToArray();
+ RuntimeGraph = runtimeGraph.ToArray();
+ }
+
+ public static DependencyContext Default => _defaultContext.Value;
+
+ public TargetInfo Target { get; }
+
+ public CompilationOptions CompilationOptions { get; }
+
+ public IReadOnlyList CompileLibraries { get; }
+
+ public IReadOnlyList RuntimeLibraries { get; }
+
+ public IReadOnlyList RuntimeGraph { get; }
+
+ public DependencyContext Merge(DependencyContext other)
+ {
+ if (other == null)
+ {
+ throw new ArgumentNullException(nameof(other));
+ }
+
+ return new DependencyContext(
+ Target,
+ CompilationOptions,
+ CompileLibraries.Union(other.CompileLibraries, new LibraryMergeEqualityComparer()),
+ RuntimeLibraries.Union(other.RuntimeLibraries, new LibraryMergeEqualityComparer()),
+ RuntimeGraph.Union(other.RuntimeGraph)
+ );
+ }
+
+ private static DependencyContext LoadDefault()
+ {
+ var entryAssembly = Assembly.GetEntryAssembly();
+ if (entryAssembly == null)
+ {
+ return null;
+ }
+
+ return Load(entryAssembly);
+ }
+
+ public static DependencyContext Load(Assembly assembly)
+ {
+ return DependencyContextLoader.Default.Load(assembly);
+ }
+
+ private class LibraryMergeEqualityComparer : IEqualityComparer where T : Library
+ {
+ public bool Equals(T x, T y)
+ {
+ return StringComparer.OrdinalIgnoreCase.Equals(x.Name, y.Name);
+ }
+
+ public int GetHashCode(T obj)
+ {
+ return StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Name);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextJsonReader.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextJsonReader.cs
new file mode 100644
index 00000000000..051cedbb192
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextJsonReader.cs
@@ -0,0 +1,525 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Xunit;
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal class DependencyContextJsonReader : IDependencyContextReader
+ {
+ static readonly string[] EmptyStringArray = new string[0];
+
+ private readonly IDictionary _stringPool = new Dictionary();
+
+ public DependencyContext Read(Stream stream)
+ {
+ if (stream == null)
+ {
+ throw new ArgumentNullException(nameof(stream));
+ }
+
+ using (var streamReader = new StreamReader(stream))
+ {
+ return Read(streamReader);
+ }
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _stringPool.Clear();
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ private DependencyContext Read(TextReader reader)
+ {
+ // {
+ // "runtimeTarget": {...},
+ // "compilationOptions": {...},
+ // "targets": {...},
+ // "libraries": {...},
+ // "runtimes": {...}
+ // }
+
+ var root = JsonDeserializer.Deserialize(reader) as JsonObject;
+ if (root == null)
+ return null;
+
+ var runtime = string.Empty;
+ var framework = string.Empty;
+ var isPortable = true;
+
+ ReadRuntimeTarget(root.ValueAsJsonObject(DependencyContextStrings.RuntimeTargetPropertyName), out var runtimeTargetName, out var runtimeSignature);
+ var compilationOptions = ReadCompilationOptions(root.ValueAsJsonObject(DependencyContextStrings.CompilationOptionsPropertName));
+ var targets = ReadTargets(root.ValueAsJsonObject(DependencyContextStrings.TargetsPropertyName));
+ var libraryStubs = ReadLibraries(root.ValueAsJsonObject(DependencyContextStrings.LibrariesPropertyName));
+ var runtimeFallbacks = ReadRuntimes(root.ValueAsJsonObject(DependencyContextStrings.RuntimesPropertyName));
+
+ if (compilationOptions == null)
+ compilationOptions = CompilationOptions.Default;
+
+ Target runtimeTarget = SelectRuntimeTarget(targets, runtimeTargetName);
+ runtimeTargetName = runtimeTarget?.Name;
+
+ if (runtimeTargetName != null)
+ {
+ var seperatorIndex = runtimeTargetName.IndexOf(DependencyContextStrings.VersionSeperator);
+ if (seperatorIndex > -1 && seperatorIndex < runtimeTargetName.Length)
+ {
+ runtime = runtimeTargetName.Substring(seperatorIndex + 1);
+ framework = runtimeTargetName.Substring(0, seperatorIndex);
+ isPortable = false;
+ }
+ else
+ {
+ framework = runtimeTargetName;
+ }
+ }
+
+ Target compileTarget = null;
+
+ var ridlessTarget = targets.FirstOrDefault(t => !IsRuntimeTarget(t.Name));
+ if (ridlessTarget != null)
+ {
+ compileTarget = ridlessTarget;
+ if (runtimeTarget == null)
+ {
+ runtimeTarget = compileTarget;
+ framework = ridlessTarget.Name;
+ }
+ }
+
+ if (runtimeTarget == null)
+ throw new FormatException("No runtime target found");
+
+ return new DependencyContext(
+ new TargetInfo(framework, runtime, runtimeSignature, isPortable),
+ compilationOptions,
+ CreateLibraries(compileTarget?.Libraries, false, libraryStubs).Cast().ToArray(),
+ CreateLibraries(runtimeTarget.Libraries, true, libraryStubs).Cast().ToArray(),
+ runtimeFallbacks ?? Enumerable.Empty());
+ }
+
+ private Target SelectRuntimeTarget(List targets, string runtimeTargetName)
+ {
+ Target target;
+
+ if (targets == null || targets.Count == 0)
+ throw new FormatException("Dependency file does not have 'targets' section");
+
+ if (!string.IsNullOrEmpty(runtimeTargetName))
+ {
+ target = targets.FirstOrDefault(t => t.Name == runtimeTargetName);
+ if (target == null)
+ throw new FormatException($"Target with name {runtimeTargetName} not found");
+ }
+ else
+ {
+ target = targets.FirstOrDefault(t => IsRuntimeTarget(t.Name));
+ }
+
+ return target;
+ }
+
+ private bool IsRuntimeTarget(string name)
+ => name.Contains(DependencyContextStrings.VersionSeperator);
+
+ private void ReadRuntimeTarget(JsonObject runtimeTargetJson, out string runtimeTargetName, out string runtimeSignature)
+ {
+ // {
+ // "name": ".NETCoreApp,Version=v1.0",
+ // "signature": "35bd60f1a92c048eea72ff8160ba07b616ebd0f6"
+ // }
+
+ runtimeTargetName = runtimeTargetJson?.ValueAsString(DependencyContextStrings.RuntimeTargetNamePropertyName);
+ runtimeSignature = runtimeTargetJson?.ValueAsString(DependencyContextStrings.RuntimeTargetSignaturePropertyName);
+ }
+
+ private CompilationOptions ReadCompilationOptions(JsonObject compilationOptionsJson)
+ {
+ // {
+ // "defines": ["","",...],
+ // "languageVersion": "...",
+ // "platform": "...",
+ // "allowUnsafe: "true|false",
+ // "warningsAsErrors": "true|false",
+ // "optimize": "true|false",
+ // "keyFile": "...",
+ // "delaySign": "true|false",
+ // "publicSign": "true|false",
+ // "debugType": "...",
+ // "emitEntryPoint: "true|false",
+ // "xmlDoc": "true|false"
+ // }
+
+ if (compilationOptionsJson == null)
+ return null;
+
+ var defines = compilationOptionsJson.ValueAsStringArray(DependencyContextStrings.DefinesPropertyName) ?? EmptyStringArray;
+ var languageVersion = compilationOptionsJson.ValueAsString(DependencyContextStrings.LanguageVersionPropertyName);
+ var platform = compilationOptionsJson.ValueAsString(DependencyContextStrings.PlatformPropertyName);
+ var allowUnsafe = compilationOptionsJson.ValueAsNullableBoolean(DependencyContextStrings.AllowUnsafePropertyName);
+ var warningsAsErrors = compilationOptionsJson.ValueAsNullableBoolean(DependencyContextStrings.WarningsAsErrorsPropertyName);
+ var optimize = compilationOptionsJson.ValueAsNullableBoolean(DependencyContextStrings.OptimizePropertyName);
+ var keyFile = compilationOptionsJson.ValueAsString(DependencyContextStrings.KeyFilePropertyName);
+ var delaySign = compilationOptionsJson.ValueAsNullableBoolean(DependencyContextStrings.DelaySignPropertyName);
+ var publicSign = compilationOptionsJson.ValueAsNullableBoolean(DependencyContextStrings.PublicSignPropertyName);
+ var debugType = compilationOptionsJson.ValueAsString(DependencyContextStrings.DebugTypePropertyName);
+ var emitEntryPoint = compilationOptionsJson.ValueAsNullableBoolean(DependencyContextStrings.EmitEntryPointPropertyName);
+ var generateXmlDocumentation = compilationOptionsJson.ValueAsNullableBoolean(DependencyContextStrings.GenerateXmlDocumentationPropertyName);
+
+ return new CompilationOptions(defines, languageVersion, platform, allowUnsafe, warningsAsErrors, optimize, keyFile, delaySign, publicSign, debugType, emitEntryPoint, generateXmlDocumentation);
+ }
+
+ private List ReadTargets(JsonObject targetsJson)
+ {
+ // Object dictionary: string => object
+
+ var targets = new List();
+
+ if (targetsJson != null)
+ foreach (var key in targetsJson.Keys)
+ targets.Add(ReadTarget(key, targetsJson.ValueAsJsonObject(key)));
+
+ return targets;
+ }
+
+ private Target ReadTarget(string targetName, JsonObject targetJson)
+ {
+ // Object dictionary: string => object
+
+ var libraries = new List();
+
+ foreach (var key in targetJson.Keys)
+ libraries.Add(ReadTargetLibrary(key, targetJson.ValueAsJsonObject(key)));
+
+ return new Target { Name = targetName, Libraries = libraries };
+ }
+
+ private TargetLibrary ReadTargetLibrary(string targetLibraryName, JsonObject targetLibraryJson)
+ {
+ // {
+ // "dependencies": {...},
+ // "runtime": {...}, # Dictionary: name => {}
+ // "native": {...}, # Dictionary: name => {}
+ // "compile": {...}, # Dictionary: name => {}
+ // "runtime": {...},
+ // "resources": {...},
+ // "compileOnly": "true|false"
+ // }
+
+ var dependencies = ReadTargetLibraryDependencies(targetLibraryJson.ValueAsJsonObject(DependencyContextStrings.DependenciesPropertyName));
+ var runtimes = targetLibraryJson.ValueAsJsonObject(DependencyContextStrings.RuntimeAssembliesKey)?.Keys;
+ var natives = targetLibraryJson.ValueAsJsonObject(DependencyContextStrings.NativeLibrariesKey)?.Keys;
+ var compilations = targetLibraryJson.ValueAsJsonObject(DependencyContextStrings.CompileTimeAssembliesKey)?.Keys;
+ var runtimeTargets = ReadTargetLibraryRuntimeTargets(targetLibraryJson.ValueAsJsonObject(DependencyContextStrings.RuntimeTargetsPropertyName));
+ var resources = ReadTargetLibraryResources(targetLibraryJson.ValueAsJsonObject(DependencyContextStrings.ResourceAssembliesPropertyName));
+ var compileOnly = targetLibraryJson.ValueAsNullableBoolean(DependencyContextStrings.CompilationOnlyPropertyName);
+
+ return new TargetLibrary
+ {
+ Name = targetLibraryName,
+ Dependencies = dependencies ?? Enumerable.Empty(),
+ Runtimes = runtimes?.ToList(),
+ Natives = natives?.ToList(),
+ Compilations = compilations?.ToList(),
+ RuntimeTargets = runtimeTargets,
+ Resources = resources,
+ CompileOnly = compileOnly
+ };
+ }
+
+ public IEnumerable ReadTargetLibraryDependencies(JsonObject targetLibraryDependenciesJson)
+ {
+ // Object dictionary: string => string
+
+ var dependencies = new List();
+
+ if (targetLibraryDependenciesJson != null)
+ foreach (var key in targetLibraryDependenciesJson.Keys)
+ dependencies.Add(new Dependency(Pool(key), Pool(targetLibraryDependenciesJson.ValueAsString(key))));
+
+ return dependencies;
+ }
+
+ private List ReadTargetLibraryRuntimeTargets(JsonObject targetLibraryRuntimeTargetsJson)
+ {
+ // Object dictionary: string => { "rid": "...", "assetType": "..." }
+
+ var runtimeTargets = new List();
+
+ if (targetLibraryRuntimeTargetsJson != null)
+ {
+ foreach (var key in targetLibraryRuntimeTargetsJson.Keys)
+ {
+ var runtimeTargetJson = targetLibraryRuntimeTargetsJson.ValueAsJsonObject(key);
+
+ runtimeTargets.Add(new RuntimeTargetEntryStub
+ {
+ Path = key,
+ Rid = Pool(runtimeTargetJson?.ValueAsString(DependencyContextStrings.RidPropertyName)),
+ Type = Pool(runtimeTargetJson?.ValueAsString(DependencyContextStrings.AssetTypePropertyName))
+ });
+ }
+ }
+
+ return runtimeTargets;
+ }
+
+ private List ReadTargetLibraryResources(JsonObject targetLibraryResourcesJson)
+ {
+ // Object dictionary: string => { "locale": "..." }
+
+ var resources = new List();
+
+ if (targetLibraryResourcesJson != null)
+ {
+ foreach (var key in targetLibraryResourcesJson.Keys)
+ {
+ string locale = targetLibraryResourcesJson.ValueAsJsonObject(key)?.ValueAsString(DependencyContextStrings.LocalePropertyName);
+
+ if (locale != null)
+ resources.Add(new ResourceAssembly(key, Pool(locale)));
+ }
+ }
+
+ return resources;
+ }
+
+ private Dictionary ReadLibraries(JsonObject librariesJson)
+ {
+ // Object dictionary: string => object
+
+ var libraries = new Dictionary();
+
+ if (librariesJson != null)
+ foreach (var key in librariesJson.Keys)
+ libraries.Add(Pool(key), ReadLibrary(librariesJson.ValueAsJsonObject(key)));
+
+ return libraries;
+ }
+
+ private LibraryStub ReadLibrary(JsonObject libraryJson)
+ {
+ // {
+ // "sha512": "...",
+ // "type": "...",
+ // "serviceable: "true|false",
+ // "path": "...",
+ // "hashPath: "...",
+ // "runtimeStoreManifestName": "..."
+ // }
+
+ string hash = libraryJson.ValueAsString(DependencyContextStrings.Sha512PropertyName);
+ string type = libraryJson.ValueAsString(DependencyContextStrings.TypePropertyName);
+ bool serviceable = libraryJson.ValueAsBoolean(DependencyContextStrings.ServiceablePropertyName);
+ string path = libraryJson.ValueAsString(DependencyContextStrings.PathPropertyName);
+ string hashPath = libraryJson.ValueAsString(DependencyContextStrings.HashPathPropertyName);
+ string runtimeStoreManifestName = libraryJson.ValueAsString(DependencyContextStrings.RuntimeStoreManifestPropertyName);
+
+ return new LibraryStub()
+ {
+ Hash = hash,
+ Type = Pool(type),
+ Serviceable = serviceable,
+ Path = path,
+ HashPath = hashPath,
+ RuntimeStoreManifestName = runtimeStoreManifestName
+ };
+ }
+
+ private List ReadRuntimes(JsonObject runtimesJson)
+ {
+ // Object dictionary: string => ["...","...",...]
+
+ var runtimeFallbacks = new List();
+
+ if (runtimesJson != null)
+ foreach (var key in runtimesJson.Keys)
+ runtimeFallbacks.Add(new RuntimeFallbacks(key, runtimesJson.ValueAsStringArray(key) ?? EmptyStringArray));
+
+ return runtimeFallbacks;
+ }
+
+ private IEnumerable CreateLibraries(IEnumerable libraries, bool runtime, Dictionary libraryStubs)
+ {
+ if (libraries == null)
+ return Enumerable.Empty();
+
+ return libraries.Select(property => CreateLibrary(property, runtime, libraryStubs))
+ .Where(library => library != null);
+ }
+
+ private Library CreateLibrary(TargetLibrary targetLibrary, bool runtime, Dictionary libraryStubs)
+ {
+ var nameWithVersion = targetLibrary.Name;
+ LibraryStub stub;
+
+ if (libraryStubs == null || !libraryStubs.TryGetValue(nameWithVersion, out stub))
+ {
+ throw new InvalidOperationException($"Cannot find library information for {nameWithVersion}");
+ }
+
+ var seperatorPosition = nameWithVersion.IndexOf(DependencyContextStrings.VersionSeperator);
+
+ var name = Pool(nameWithVersion.Substring(0, seperatorPosition));
+ var version = Pool(nameWithVersion.Substring(seperatorPosition + 1));
+
+ if (runtime)
+ {
+ // Runtime section of this library was trimmed by type:platform
+ var isCompilationOnly = targetLibrary.CompileOnly;
+ if (isCompilationOnly == true)
+ {
+ return null;
+ }
+
+ var runtimeAssemblyGroups = new List();
+ var nativeLibraryGroups = new List();
+ if (targetLibrary.RuntimeTargets != null)
+ {
+ foreach (var ridGroup in targetLibrary.RuntimeTargets.GroupBy(e => e.Rid))
+ {
+ var groupRuntimeAssemblies = ridGroup
+ .Where(e => e.Type == DependencyContextStrings.RuntimeAssetType)
+ .Select(e => e.Path)
+ .ToArray();
+
+ if (groupRuntimeAssemblies.Any())
+ {
+ runtimeAssemblyGroups.Add(new RuntimeAssetGroup(
+ ridGroup.Key,
+ groupRuntimeAssemblies.Where(a => Path.GetFileName(a) != "_._")));
+ }
+
+ var groupNativeLibraries = ridGroup
+ .Where(e => e.Type == DependencyContextStrings.NativeAssetType)
+ .Select(e => e.Path)
+ .ToArray();
+
+ if (groupNativeLibraries.Any())
+ {
+ nativeLibraryGroups.Add(new RuntimeAssetGroup(
+ ridGroup.Key,
+ groupNativeLibraries.Where(a => Path.GetFileName(a) != "_._")));
+ }
+ }
+ }
+
+ if (targetLibrary.Runtimes != null && targetLibrary.Runtimes.Count > 0)
+ {
+ runtimeAssemblyGroups.Add(new RuntimeAssetGroup(string.Empty, targetLibrary.Runtimes));
+ }
+
+ if (targetLibrary.Natives != null && targetLibrary.Natives.Count > 0)
+ {
+ nativeLibraryGroups.Add(new RuntimeAssetGroup(string.Empty, targetLibrary.Natives));
+ }
+
+ return new RuntimeLibrary(
+ type: stub.Type,
+ name: name,
+ version: version,
+ hash: stub.Hash,
+ runtimeAssemblyGroups: runtimeAssemblyGroups,
+ nativeLibraryGroups: nativeLibraryGroups,
+ resourceAssemblies: targetLibrary.Resources ?? Enumerable.Empty(),
+ dependencies: targetLibrary.Dependencies,
+ serviceable: stub.Serviceable,
+ path: stub.Path,
+ hashPath: stub.HashPath,
+ runtimeStoreManifestName: stub.RuntimeStoreManifestName);
+ }
+ else
+ {
+ var assemblies = (targetLibrary.Compilations != null) ? targetLibrary.Compilations : Enumerable.Empty();
+ return new CompilationLibrary(
+ stub.Type,
+ name,
+ version,
+ stub.Hash,
+ assemblies,
+ targetLibrary.Dependencies,
+ stub.Serviceable,
+ stub.Path,
+ stub.HashPath);
+ }
+ }
+
+ private string Pool(string s)
+ {
+ if (s == null)
+ {
+ return null;
+ }
+
+ string result;
+ if (!_stringPool.TryGetValue(s, out result))
+ {
+ _stringPool[s] = s;
+ result = s;
+ }
+ return result;
+ }
+
+ private class Target
+ {
+ public string Name;
+
+ public IEnumerable Libraries;
+ }
+
+ private struct TargetLibrary
+ {
+ public string Name;
+
+ public IEnumerable Dependencies;
+
+ public List Runtimes;
+
+ public List Natives;
+
+ public List Compilations;
+
+ public List RuntimeTargets;
+
+ public List Resources;
+
+ public bool? CompileOnly;
+ }
+
+ private struct RuntimeTargetEntryStub
+ {
+ public string Type;
+
+ public string Path;
+
+ public string Rid;
+ }
+
+ private struct LibraryStub
+ {
+ public string Hash;
+
+ public string Type;
+
+ public bool Serviceable;
+
+ public string Path;
+
+ public string HashPath;
+
+ public string RuntimeStoreManifestName;
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextLoader.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextLoader.cs
new file mode 100644
index 00000000000..d2ff1cccfc7
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextLoader.cs
@@ -0,0 +1,128 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal class DependencyContextLoader
+ {
+ private const string DepsJsonExtension = ".deps.json";
+
+ private readonly string _entryPointDepsLocation;
+ private readonly IEnumerable _nonEntryPointDepsPaths;
+ private readonly IFileSystem _fileSystem;
+ private readonly Func _jsonReaderFactory;
+
+ public DependencyContextLoader() : this(
+ DependencyContextPaths.Current.Application,
+ DependencyContextPaths.Current.NonApplicationPaths,
+ FileSystemWrapper.Default,
+ () => new DependencyContextJsonReader())
+ {
+ }
+
+ internal DependencyContextLoader(
+ string entryPointDepsLocation,
+ IEnumerable nonEntryPointDepsPaths,
+ IFileSystem fileSystem,
+ Func jsonReaderFactory)
+ {
+ _entryPointDepsLocation = entryPointDepsLocation;
+ _nonEntryPointDepsPaths = nonEntryPointDepsPaths;
+ _fileSystem = fileSystem;
+ _jsonReaderFactory = jsonReaderFactory;
+ }
+
+ public static DependencyContextLoader Default { get; } = new DependencyContextLoader();
+
+ private static bool IsEntryAssembly(Assembly assembly)
+ {
+ return assembly.Equals(Assembly.GetEntryAssembly());
+ }
+
+ private static Stream GetResourceStream(Assembly assembly, string name)
+ {
+ return assembly.GetManifestResourceStream(name);
+ }
+
+ public DependencyContext Load(Assembly assembly)
+ {
+ if (assembly == null)
+ {
+ throw new ArgumentNullException(nameof(assembly));
+ }
+
+ DependencyContext context = null;
+ using (var reader = _jsonReaderFactory())
+ {
+ if (IsEntryAssembly(assembly))
+ {
+ context = LoadEntryAssemblyContext(reader);
+ }
+
+ if (context == null)
+ {
+ context = LoadAssemblyContext(assembly, reader);
+ }
+
+ if (context != null)
+ {
+ foreach (var extraPath in _nonEntryPointDepsPaths)
+ {
+ var extraContext = LoadContext(reader, extraPath);
+ if (extraContext != null)
+ {
+ context = context.Merge(extraContext);
+ }
+ }
+ }
+ }
+ return context;
+ }
+
+ private DependencyContext LoadEntryAssemblyContext(IDependencyContextReader reader)
+ {
+ return LoadContext(reader, _entryPointDepsLocation);
+ }
+
+ private DependencyContext LoadContext(IDependencyContextReader reader, string location)
+ {
+ if (!string.IsNullOrEmpty(location))
+ {
+ Debug.Assert(_fileSystem.File.Exists(location));
+ using (var stream = _fileSystem.File.OpenRead(location))
+ {
+ return reader.Read(stream);
+ }
+ }
+ return null;
+ }
+
+ private DependencyContext LoadAssemblyContext(Assembly assembly, IDependencyContextReader reader)
+ {
+ using (var stream = GetResourceStream(assembly, assembly.GetName().Name + DepsJsonExtension))
+ {
+ if (stream != null)
+ {
+ return reader.Read(stream);
+ }
+ }
+
+ var depsJsonFile = Path.ChangeExtension(assembly.Location, DepsJsonExtension);
+ if (_fileSystem.File.Exists(depsJsonFile))
+ {
+ using (var stream = _fileSystem.File.OpenRead(depsJsonFile))
+ {
+ return reader.Read(stream);
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextPaths.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextPaths.cs
new file mode 100644
index 00000000000..ec3311a68ac
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextPaths.cs
@@ -0,0 +1,60 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal class DependencyContextPaths
+ {
+ private static readonly string DepsFilesProperty = "APP_CONTEXT_DEPS_FILES";
+ private static readonly string FxDepsFileProperty = "FX_DEPS_FILE";
+
+ public static DependencyContextPaths Current { get; } = GetCurrent();
+
+ public string Application { get; }
+
+ public string SharedRuntime { get; }
+
+ public IEnumerable NonApplicationPaths { get; }
+
+ public DependencyContextPaths(
+ string application,
+ string sharedRuntime,
+ IEnumerable nonApplicationPaths)
+ {
+ Application = application;
+ SharedRuntime = sharedRuntime;
+ NonApplicationPaths = nonApplicationPaths ?? Enumerable.Empty();
+ }
+
+ private static DependencyContextPaths GetCurrent()
+ {
+#if NETCOREAPP
+ var deps = AppContext.GetData(DepsFilesProperty);
+ var fxDeps = AppContext.GetData(FxDepsFileProperty);
+#else
+ var deps = AppDomain.CurrentDomain.GetData(DepsFilesProperty);
+ var fxDeps = AppDomain.CurrentDomain.GetData(FxDepsFileProperty);
+#endif
+ return Create(deps as string, fxDeps as string);
+ }
+
+ internal static DependencyContextPaths Create(string depsFiles, string sharedRuntime)
+ {
+ var files = depsFiles?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
+ var application = files != null && files.Length > 0 ? files[0] : null;
+
+ var nonApplicationPaths = files?
+ .Skip(1) // the application path
+ .ToArray();
+
+ return new DependencyContextPaths(
+ application,
+ sharedRuntime,
+ nonApplicationPaths);
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextStrings.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextStrings.cs
new file mode 100644
index 00000000000..b5d877daae4
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DependencyContextStrings.cs
@@ -0,0 +1,86 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal class DependencyContextStrings
+ {
+ internal const char VersionSeperator = '/';
+
+ internal const string CompileTimeAssembliesKey = "compile";
+
+ internal const string RuntimeAssembliesKey = "runtime";
+
+ internal const string NativeLibrariesKey = "native";
+
+ internal const string RuntimeTargetPropertyName = "runtimeTarget";
+
+ internal const string LibrariesPropertyName = "libraries";
+
+ internal const string TargetsPropertyName = "targets";
+
+ internal const string DependenciesPropertyName = "dependencies";
+
+ internal const string Sha512PropertyName = "sha512";
+
+ internal const string PathPropertyName = "path";
+
+ internal const string HashPathPropertyName = "hashPath";
+
+ internal const string RuntimeStoreManifestPropertyName = "runtimeStoreManifestName";
+
+ internal const string TypePropertyName = "type";
+
+ internal const string ServiceablePropertyName = "serviceable";
+
+ internal const string CompilationOptionsPropertName = "compilationOptions";
+
+ internal const string DefinesPropertyName = "defines";
+
+ internal const string LanguageVersionPropertyName = "languageVersion";
+
+ internal const string PlatformPropertyName = "platform";
+
+ internal const string AllowUnsafePropertyName = "allowUnsafe";
+
+ internal const string WarningsAsErrorsPropertyName = "warningsAsErrors";
+
+ internal const string OptimizePropertyName = "optimize";
+
+ internal const string KeyFilePropertyName = "keyFile";
+
+ internal const string DelaySignPropertyName = "delaySign";
+
+ internal const string PublicSignPropertyName = "publicSign";
+
+ internal const string DebugTypePropertyName = "debugType";
+
+ internal const string EmitEntryPointPropertyName = "emitEntryPoint";
+
+ internal const string GenerateXmlDocumentationPropertyName = "xmlDoc";
+
+ internal const string PortablePropertyName = "portable";
+
+ internal const string RuntimeTargetNamePropertyName = "name";
+
+ internal const string RuntimeTargetSignaturePropertyName = "signature";
+
+ internal const string RuntimesPropertyName = "runtimes";
+
+ internal const string RuntimeTargetsPropertyName = "runtimeTargets";
+
+ internal const string RidPropertyName = "rid";
+
+ internal const string AssetTypePropertyName = "assetType";
+
+ internal const string RuntimeAssetType = "runtime";
+
+ internal const string NativeAssetType = "native";
+
+ internal const string ResourceAssembliesPropertyName = "resources";
+
+ internal const string LocalePropertyName = "locale";
+
+ internal const string CompilationOnlyPropertyName = "compileOnly";
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DirectoryWrapper.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DirectoryWrapper.cs
new file mode 100644
index 00000000000..25cc2501e71
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/DirectoryWrapper.cs
@@ -0,0 +1,15 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.IO;
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal class DirectoryWrapper: IDirectory
+ {
+ public bool Exists(string path)
+ {
+ return Directory.Exists(path);
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/EnvironmentWrapper.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/EnvironmentWrapper.cs
new file mode 100644
index 00000000000..827c6ea71f7
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/EnvironmentWrapper.cs
@@ -0,0 +1,17 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal class EnvironmentWrapper : IEnvironment
+ {
+ public static IEnvironment Default = new EnvironmentWrapper();
+
+ public string GetEnvironmentVariable(string name)
+ {
+ return Environment.GetEnvironmentVariable(name);
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/FileSystemWrapper.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/FileSystemWrapper.cs
new file mode 100644
index 00000000000..acb5055e65c
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/FileSystemWrapper.cs
@@ -0,0 +1,14 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal class FileSystemWrapper : IFileSystem
+ {
+ public static IFileSystem Default { get; } = new FileSystemWrapper();
+
+ public IFile File { get; } = new FileWrapper();
+
+ public IDirectory Directory { get; } = new DirectoryWrapper();
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/FileWrapper.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/FileWrapper.cs
new file mode 100644
index 00000000000..50015dd0dba
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/FileWrapper.cs
@@ -0,0 +1,50 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.IO;
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal class FileWrapper: IFile
+ {
+ public bool Exists(string path)
+ {
+ return File.Exists(path);
+ }
+
+ public string ReadAllText(string path)
+ {
+ return File.ReadAllText(path);
+ }
+
+ public Stream OpenRead(string path)
+ {
+ return File.OpenRead(path);
+ }
+
+ public Stream OpenFile(
+ string path,
+ FileMode fileMode,
+ FileAccess fileAccess,
+ FileShare fileShare,
+ int bufferSize,
+ FileOptions fileOptions)
+ {
+ return new FileStream(path, fileMode, fileAccess, fileShare, bufferSize, fileOptions);
+ }
+
+ public void CreateEmptyFile(string path)
+ {
+ try
+ {
+ var emptyFile = File.Create(path);
+ if (emptyFile != null)
+ {
+ emptyFile.Dispose();
+ }
+ }
+ catch { }
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IDependencyContextReader.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IDependencyContextReader.cs
new file mode 100644
index 00000000000..2d5662eb382
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IDependencyContextReader.cs
@@ -0,0 +1,10 @@
+using System;
+using System.IO;
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal interface IDependencyContextReader: IDisposable
+ {
+ DependencyContext Read(Stream stream);
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IDirectory.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IDirectory.cs
new file mode 100644
index 00000000000..7ce126e0930
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IDirectory.cs
@@ -0,0 +1,10 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal interface IDirectory
+ {
+ bool Exists(string path);
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IEnvironment.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IEnvironment.cs
new file mode 100644
index 00000000000..7ed3470fc23
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IEnvironment.cs
@@ -0,0 +1,10 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal interface IEnvironment
+ {
+ string GetEnvironmentVariable(string name);
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IFile.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IFile.cs
new file mode 100644
index 00000000000..88db2b7a49a
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IFile.cs
@@ -0,0 +1,26 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.IO;
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal interface IFile
+ {
+ bool Exists(string path);
+
+ string ReadAllText(string path);
+
+ Stream OpenRead(string path);
+
+ Stream OpenFile(
+ string path,
+ FileMode fileMode,
+ FileAccess fileAccess,
+ FileShare fileShare,
+ int bufferSize,
+ FileOptions fileOptions);
+
+ void CreateEmptyFile(string path);
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IFileSystem.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IFileSystem.cs
new file mode 100644
index 00000000000..9e01a30c59b
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/IFileSystem.cs
@@ -0,0 +1,11 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal interface IFileSystem
+ {
+ IFile File { get; }
+ IDirectory Directory { get; }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Library.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Library.cs
new file mode 100644
index 00000000000..4e182f121b0
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Library.cs
@@ -0,0 +1,78 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal class Library
+ {
+ public Library(string type,
+ string name,
+ string version,
+ string hash,
+ IEnumerable dependencies,
+ bool serviceable,
+ string path,
+ string hashPath)
+ : this(type, name, version, hash, dependencies, serviceable, path, hashPath, runtimeStoreManifestName: null)
+ {
+ }
+ public Library(string type,
+ string name,
+ string version,
+ string hash,
+ IEnumerable dependencies,
+ bool serviceable,
+ string path,
+ string hashPath,
+ string runtimeStoreManifestName = null)
+ {
+ if (string.IsNullOrEmpty(type))
+ {
+ throw new ArgumentException(nameof(type));
+ }
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentException(nameof(name));
+ }
+ if (string.IsNullOrEmpty(version))
+ {
+ throw new ArgumentException(nameof(version));
+ }
+ if (dependencies == null)
+ {
+ throw new ArgumentNullException(nameof(dependencies));
+ }
+ Type = type;
+ Name = name;
+ Version = version;
+ Hash = hash;
+ Dependencies = dependencies.ToArray();
+ Serviceable = serviceable;
+ Path = path;
+ HashPath = hashPath;
+ RuntimeStoreManifestName = runtimeStoreManifestName;
+ }
+
+ public string Type { get; }
+
+ public string Name { get; }
+
+ public string Version { get; }
+
+ public string Hash { get; }
+
+ public IReadOnlyList Dependencies { get; }
+
+ public bool Serviceable { get; }
+
+ public string Path { get; }
+
+ public string HashPath { get; }
+
+ public string RuntimeStoreManifestName {get;}
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/AppBaseCompilationAssemblyResolver.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/AppBaseCompilationAssemblyResolver.cs
new file mode 100644
index 00000000000..72c6c0955ac
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/AppBaseCompilationAssemblyResolver.cs
@@ -0,0 +1,110 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Internal.Microsoft.DotNet.PlatformAbstractions;
+
+namespace Internal.Microsoft.Extensions.DependencyModel.Resolution
+{
+ internal class AppBaseCompilationAssemblyResolver : ICompilationAssemblyResolver
+ {
+ private static string RefsDirectoryName = "refs";
+ private readonly IFileSystem _fileSystem;
+ private readonly string _basePath;
+ private readonly DependencyContextPaths _dependencyContextPaths;
+
+ public AppBaseCompilationAssemblyResolver()
+ : this(FileSystemWrapper.Default)
+ {
+ }
+
+ internal AppBaseCompilationAssemblyResolver(IFileSystem fileSystem)
+ : this(fileSystem, ApplicationEnvironment.ApplicationBasePath, DependencyContextPaths.Current)
+ {
+ }
+
+ internal AppBaseCompilationAssemblyResolver(IFileSystem fileSystem, string basePath, DependencyContextPaths dependencyContextPaths)
+ {
+ _fileSystem = fileSystem;
+ _basePath = basePath;
+ _dependencyContextPaths = dependencyContextPaths;
+ }
+
+ public bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies)
+ {
+ var isProject = string.Equals(library.Type, "project", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(library.Type, "msbuildproject", StringComparison.OrdinalIgnoreCase);
+
+ var isPackage = string.Equals(library.Type, "package", StringComparison.OrdinalIgnoreCase);
+ var isReferenceAssembly = string.Equals(library.Type, "referenceassembly", StringComparison.OrdinalIgnoreCase);
+ if (!isProject &&
+ !isPackage &&
+ !isReferenceAssembly &&
+ !string.Equals(library.Type, "reference", StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ var refsPath = Path.Combine(_basePath, RefsDirectoryName);
+ var isPublished = _fileSystem.Directory.Exists(refsPath);
+
+ // Resolving reference assemblies requires refs folder to exist
+ if (isReferenceAssembly && !isPublished)
+ {
+ return false;
+ }
+
+ var directories = new List()
+ {
+ _basePath
+ };
+
+ if (isPublished)
+ {
+ directories.Insert(0, refsPath);
+ }
+
+ // Only packages can come from shared runtime
+ var sharedPath = _dependencyContextPaths.SharedRuntime;
+ if (isPublished && isPackage && !string.IsNullOrEmpty(sharedPath))
+ {
+ var sharedDirectory = Path.GetDirectoryName(sharedPath);
+ var sharedRefs = Path.Combine(sharedDirectory, RefsDirectoryName);
+ if (_fileSystem.Directory.Exists(sharedRefs))
+ {
+ directories.Add(sharedRefs);
+ }
+ directories.Add(sharedDirectory);
+ }
+
+ var paths = new List();
+
+ foreach (var assembly in library.Assemblies)
+ {
+ bool resolved = false;
+ var assemblyFile = Path.GetFileName(assembly);
+ foreach (var directory in directories)
+ {
+ string fullName;
+ if (ResolverUtils.TryResolveAssemblyFile(_fileSystem, directory, assemblyFile, out fullName))
+ {
+ paths.Add(fullName);
+ resolved = true;
+ break;
+ }
+ }
+
+ if (!resolved)
+ {
+ return false;
+ }
+ }
+
+ // only modify the assemblies parameter if we've resolved all files
+ assemblies?.AddRange(paths);
+ return true;
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/CompositeCompilationAssemblyResolver.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/CompositeCompilationAssemblyResolver.cs
new file mode 100644
index 00000000000..80011c384cc
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/CompositeCompilationAssemblyResolver.cs
@@ -0,0 +1,29 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Generic;
+
+namespace Internal.Microsoft.Extensions.DependencyModel.Resolution
+{
+ internal class CompositeCompilationAssemblyResolver: ICompilationAssemblyResolver
+ {
+ private readonly ICompilationAssemblyResolver[] _resolvers;
+
+ public CompositeCompilationAssemblyResolver(ICompilationAssemblyResolver[] resolvers)
+ {
+ _resolvers = resolvers;
+ }
+
+ public bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies)
+ {
+ foreach (var resolver in _resolvers)
+ {
+ if (resolver.TryResolveAssemblyPaths(library, assemblies))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/DotNetReferenceAssembliesPathResolver.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/DotNetReferenceAssembliesPathResolver.cs
new file mode 100644
index 00000000000..488e8aad9c4
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/DotNetReferenceAssembliesPathResolver.cs
@@ -0,0 +1,51 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Internal.Microsoft.DotNet.PlatformAbstractions;
+
+namespace Internal.Microsoft.Extensions.DependencyModel.Resolution
+{
+ internal class DotNetReferenceAssembliesPathResolver
+ {
+ public static readonly string DotNetReferenceAssembliesPathEnv = "DOTNET_REFERENCE_ASSEMBLIES_PATH";
+
+ internal static string Resolve(IEnvironment envirnment, IFileSystem fileSystem)
+ {
+ var path = envirnment.GetEnvironmentVariable(DotNetReferenceAssembliesPathEnv);
+ if (!string.IsNullOrEmpty(path))
+ {
+ return path;
+ }
+
+ return GetDefaultDotNetReferenceAssembliesPath(fileSystem);
+ }
+
+ private static string GetDefaultDotNetReferenceAssembliesPath(IFileSystem fileSystem)
+ {
+ var os = RuntimeEnvironment.OperatingSystemPlatform;
+
+ if (os == Platform.Windows)
+ {
+ return null;
+ }
+
+ if (os == Platform.Darwin &&
+ fileSystem.Directory.Exists("/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/xbuild-frameworks"))
+ {
+ return "/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/xbuild-frameworks";
+ }
+
+ if (fileSystem.Directory.Exists("/usr/local/lib/mono/xbuild-frameworks"))
+ {
+ return "/usr/local/lib/mono/xbuild-frameworks";
+ }
+
+ if (fileSystem.Directory.Exists("/usr/lib/mono/xbuild-frameworks"))
+ {
+ return "/usr/lib/mono/xbuild-frameworks";
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/ICompilationAssemblyResolver.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/ICompilationAssemblyResolver.cs
new file mode 100644
index 00000000000..d035cf300c5
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/ICompilationAssemblyResolver.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Generic;
+
+namespace Internal.Microsoft.Extensions.DependencyModel.Resolution
+{
+ internal interface ICompilationAssemblyResolver
+ {
+ bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies);
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/PackageCompilationAssemblyResolver.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/PackageCompilationAssemblyResolver.cs
new file mode 100644
index 00000000000..e268fbee18f
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/PackageCompilationAssemblyResolver.cs
@@ -0,0 +1,126 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Internal.Microsoft.DotNet.PlatformAbstractions;
+
+namespace Internal.Microsoft.Extensions.DependencyModel.Resolution
+{
+ internal class PackageCompilationAssemblyResolver: ICompilationAssemblyResolver
+ {
+ private readonly IFileSystem _fileSystem;
+ private readonly string[] _nugetPackageDirectories;
+
+ public PackageCompilationAssemblyResolver()
+ : this(EnvironmentWrapper.Default, FileSystemWrapper.Default)
+ {
+ }
+
+ public PackageCompilationAssemblyResolver(string nugetPackageDirectory)
+ : this(FileSystemWrapper.Default, new string[] { nugetPackageDirectory })
+ {
+ }
+
+ internal PackageCompilationAssemblyResolver(IEnvironment environment,
+ IFileSystem fileSystem)
+ : this(fileSystem, GetDefaultProbeDirectories(environment))
+ {
+ }
+
+ internal PackageCompilationAssemblyResolver(IFileSystem fileSystem, string[] nugetPackageDirectories)
+ {
+ _fileSystem = fileSystem;
+ _nugetPackageDirectories = nugetPackageDirectories;
+ }
+
+ private static string[] GetDefaultProbeDirectories(IEnvironment environment) =>
+ GetDefaultProbeDirectories(RuntimeEnvironment.OperatingSystemPlatform, environment);
+
+ internal static string[] GetDefaultProbeDirectories(Platform osPlatform, IEnvironment environment)
+ {
+ var probeDirectories = AppContext.GetData("PROBING_DIRECTORIES");
+
+ var listOfDirectories = probeDirectories as string;
+
+ if (!string.IsNullOrEmpty(listOfDirectories))
+ {
+ return listOfDirectories.Split(new char [] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries );
+ }
+
+ var packageDirectory = environment.GetEnvironmentVariable("NUGET_PACKAGES");
+
+ if (!string.IsNullOrEmpty(packageDirectory))
+ {
+ return new string[] { packageDirectory };
+ }
+
+ string basePath;
+ if (osPlatform == Platform.Windows)
+ {
+ basePath = environment.GetEnvironmentVariable("USERPROFILE");
+ }
+ else
+ {
+ basePath = environment.GetEnvironmentVariable("HOME");
+ }
+
+ if (string.IsNullOrEmpty(basePath))
+ {
+ return new string[] { string.Empty };
+ }
+
+ return new string[] { Path.Combine(basePath, ".nuget", "packages") };
+
+ }
+
+ public bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies)
+ {
+ if (_nugetPackageDirectories == null || _nugetPackageDirectories.Length == 0 ||
+ !string.Equals(library.Type, "package", StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ foreach (var directory in _nugetPackageDirectories)
+ {
+ string packagePath;
+
+ if (ResolverUtils.TryResolvePackagePath(_fileSystem, library, directory, out packagePath))
+ {
+ IEnumerable fullPathsFromPackage;
+ if (TryResolveFromPackagePath(_fileSystem, library, packagePath, out fullPathsFromPackage))
+ {
+ assemblies.AddRange(fullPathsFromPackage);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static bool TryResolveFromPackagePath(IFileSystem fileSystem, CompilationLibrary library, string basePath, out IEnumerable results)
+ {
+ var paths = new List();
+
+ foreach (var assembly in library.Assemblies)
+ {
+ string fullName;
+ if (!ResolverUtils.TryResolveAssemblyFile(fileSystem, basePath, assembly, out fullName))
+ {
+ // if one of the files can't be found, skip this package path completely.
+ // there are package paths that don't include all of the "ref" assemblies
+ // (ex. ones created by 'dotnet store')
+ results = null;
+ return false;
+ }
+
+ paths.Add(fullName);
+ }
+
+ results = paths;
+ return true;
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/ReferenceAssemblyPathResolver.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/ReferenceAssemblyPathResolver.cs
new file mode 100644
index 00000000000..6aa6c1fa589
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/ReferenceAssemblyPathResolver.cs
@@ -0,0 +1,140 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Internal.Microsoft.DotNet.PlatformAbstractions;
+
+namespace Internal.Microsoft.Extensions.DependencyModel.Resolution
+{
+ internal class ReferenceAssemblyPathResolver: ICompilationAssemblyResolver
+ {
+ private readonly IFileSystem _fileSystem;
+ private readonly string _defaultReferenceAssembliesPath;
+ private readonly string[] _fallbackSearchPaths;
+
+ public ReferenceAssemblyPathResolver()
+ : this(FileSystemWrapper.Default, EnvironmentWrapper.Default)
+ {
+ }
+
+ public ReferenceAssemblyPathResolver(string defaultReferenceAssembliesPath, string[] fallbackSearchPaths)
+ : this(FileSystemWrapper.Default, defaultReferenceAssembliesPath, fallbackSearchPaths)
+ {
+ }
+
+ internal ReferenceAssemblyPathResolver(IFileSystem fileSystem, IEnvironment environment)
+ : this(fileSystem,
+ GetDefaultReferenceAssembliesPath(fileSystem, RuntimeEnvironment.OperatingSystemPlatform, environment),
+ GetFallbackSearchPaths(fileSystem, RuntimeEnvironment.OperatingSystemPlatform, environment))
+ {
+ }
+
+ internal ReferenceAssemblyPathResolver(IFileSystem fileSystem, string defaultReferenceAssembliesPath, string[] fallbackSearchPaths)
+ {
+ _fileSystem = fileSystem;
+ _defaultReferenceAssembliesPath = defaultReferenceAssembliesPath;
+ _fallbackSearchPaths = fallbackSearchPaths;
+ }
+
+ public bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies)
+ {
+ if (!string.Equals(library.Type, "referenceassembly", StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+ foreach (var assembly in library.Assemblies)
+ {
+ string fullName;
+ if (!TryResolveReferenceAssembly(assembly, out fullName))
+ {
+ throw new InvalidOperationException($"Cannot find reference assembly '{assembly}' file for package {library.Name}");
+ }
+ assemblies.Add(fullName);
+ }
+ return true;
+ }
+
+ private bool TryResolveReferenceAssembly(string path, out string fullPath)
+ {
+ fullPath = null;
+
+ if (_defaultReferenceAssembliesPath != null)
+ {
+ var relativeToReferenceAssemblies = Path.Combine(_defaultReferenceAssembliesPath, path);
+ if (_fileSystem.File.Exists(relativeToReferenceAssemblies))
+ {
+ fullPath = relativeToReferenceAssemblies;
+ return true;
+ }
+ }
+
+ var name = Path.GetFileName(path);
+ foreach (var fallbackPath in _fallbackSearchPaths)
+ {
+ var fallbackFile = Path.Combine(fallbackPath, name);
+ if (_fileSystem.File.Exists(fallbackFile))
+ {
+ fullPath = fallbackFile;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ internal static string[] GetFallbackSearchPaths(IFileSystem fileSystem, Platform platform, IEnvironment environment)
+ {
+ if (platform != Platform.Windows)
+ {
+ return new string[0];
+ }
+
+ var net20Dir = Path.Combine(environment.GetEnvironmentVariable("WINDIR"), "Microsoft.NET", "Framework", "v2.0.50727");
+
+ if (!fileSystem.Directory.Exists(net20Dir))
+ {
+ return new string[0];
+ }
+ return new[] { net20Dir };
+ }
+
+ internal static string GetDefaultReferenceAssembliesPath(IFileSystem fileSystem, Platform platform, IEnvironment environment)
+ {
+ // Allow setting the reference assemblies path via an environment variable
+ var referenceAssembliesPath = DotNetReferenceAssembliesPathResolver.Resolve(environment, fileSystem);
+ if (!string.IsNullOrEmpty(referenceAssembliesPath))
+ {
+ return referenceAssembliesPath;
+ }
+
+ if (platform != Platform.Windows)
+ {
+ // There is no reference assemblies path outside of windows
+ // The environment variable can be used to specify one
+ return null;
+ }
+
+ // References assemblies are in %ProgramFiles(x86)% on
+ // 64 bit machines
+ var programFiles = environment.GetEnvironmentVariable("ProgramFiles(x86)");
+
+ if (string.IsNullOrEmpty(programFiles))
+ {
+ // On 32 bit machines they are in %ProgramFiles%
+ programFiles = environment.GetEnvironmentVariable("ProgramFiles");
+ }
+
+ if (string.IsNullOrEmpty(programFiles))
+ {
+ // Reference assemblies aren't installed
+ return null;
+ }
+
+ return Path.Combine(
+ programFiles,
+ "Reference Assemblies", "Microsoft", "Framework");
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/ResolverUtils.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/ResolverUtils.cs
new file mode 100644
index 00000000000..e1002b4b64c
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/Resolution/ResolverUtils.cs
@@ -0,0 +1,37 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.IO;
+
+namespace Internal.Microsoft.Extensions.DependencyModel.Resolution
+{
+ internal static class ResolverUtils
+ {
+ internal static bool TryResolvePackagePath(IFileSystem fileSystem, CompilationLibrary library, string basePath, out string packagePath)
+ {
+ var path = library.Path;
+ if (string.IsNullOrEmpty(path))
+ {
+ path = Path.Combine(library.Name.ToLowerInvariant(), library.Version.ToLowerInvariant());
+ }
+
+ packagePath = Path.Combine(basePath, path);
+
+ if (fileSystem.Directory.Exists(packagePath))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ internal static bool TryResolveAssemblyFile(IFileSystem fileSystem, string basePath, string assemblyPath, out string fullName)
+ {
+ fullName = Path.GetFullPath(Path.Combine(basePath, assemblyPath));
+ if (fileSystem.File.Exists(fullName))
+ {
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/ResourceAssembly.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/ResourceAssembly.cs
new file mode 100644
index 00000000000..427f9670b57
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/ResourceAssembly.cs
@@ -0,0 +1,29 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal class ResourceAssembly
+ {
+ public ResourceAssembly(string path, string locale)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ throw new ArgumentException(nameof(path));
+ }
+ if (string.IsNullOrEmpty(locale))
+ {
+ throw new ArgumentException(nameof(locale));
+ }
+ Locale = locale;
+ Path = path;
+ }
+
+ public string Locale { get; set; }
+
+ public string Path { get; set; }
+
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/RuntimeAssetGroup.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/RuntimeAssetGroup.cs
new file mode 100644
index 00000000000..ba3ad90bb18
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/RuntimeAssetGroup.cs
@@ -0,0 +1,29 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Linq;
+using System.Collections.Generic;
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal class RuntimeAssetGroup
+ {
+ public RuntimeAssetGroup(string runtime, params string[] assetPaths) : this(runtime, (IEnumerable)assetPaths) { }
+
+ public RuntimeAssetGroup(string runtime, IEnumerable assetPaths)
+ {
+ Runtime = runtime;
+ AssetPaths = assetPaths.ToArray();
+ }
+
+ ///
+ /// The runtime ID associated with this group (may be empty if the group is runtime-agnostic)
+ ///
+ public string Runtime { get; }
+
+ ///
+ /// Gets a list of assets provided in this runtime group
+ ///
+ public IReadOnlyList AssetPaths { get; }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/RuntimeFallbacks.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/RuntimeFallbacks.cs
new file mode 100644
index 00000000000..acdee225b44
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/RuntimeFallbacks.cs
@@ -0,0 +1,30 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal class RuntimeFallbacks
+ {
+ public string Runtime { get; set; }
+ public IReadOnlyList Fallbacks { get; set; }
+
+ public RuntimeFallbacks(string runtime, params string[] fallbacks) : this(runtime, (IEnumerable)fallbacks) { }
+ public RuntimeFallbacks(string runtime, IEnumerable fallbacks)
+ {
+ if (string.IsNullOrEmpty(runtime))
+ {
+ throw new ArgumentException(nameof(runtime));
+ }
+ if (fallbacks == null)
+ {
+ throw new ArgumentNullException(nameof(fallbacks));
+ }
+ Runtime = runtime;
+ Fallbacks = fallbacks.ToArray();
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/RuntimeLibrary.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/RuntimeLibrary.cs
new file mode 100644
index 00000000000..a875f871863
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/RuntimeLibrary.cs
@@ -0,0 +1,83 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal class RuntimeLibrary : Library
+ {
+ public RuntimeLibrary(string type,
+ string name,
+ string version,
+ string hash,
+ IReadOnlyList runtimeAssemblyGroups,
+ IReadOnlyList nativeLibraryGroups,
+ IEnumerable resourceAssemblies,
+ IEnumerable dependencies,
+ bool serviceable,
+ string path,
+ string hashPath)
+ : this(type,
+ name,
+ version,
+ hash,
+ runtimeAssemblyGroups,
+ nativeLibraryGroups,
+ resourceAssemblies,
+ dependencies,
+ serviceable,
+ path,
+ hashPath,
+ runtimeStoreManifestName : null)
+ {
+ }
+
+ public RuntimeLibrary(string type,
+ string name,
+ string version,
+ string hash,
+ IReadOnlyList runtimeAssemblyGroups,
+ IReadOnlyList nativeLibraryGroups,
+ IEnumerable resourceAssemblies,
+ IEnumerable dependencies,
+ bool serviceable,
+ string path,
+ string hashPath,
+ string runtimeStoreManifestName)
+ : base(type,
+ name,
+ version,
+ hash,
+ dependencies,
+ serviceable,
+ path,
+ hashPath,
+ runtimeStoreManifestName)
+ {
+ if (runtimeAssemblyGroups == null)
+ {
+ throw new ArgumentNullException(nameof(runtimeAssemblyGroups));
+ }
+ if (nativeLibraryGroups == null)
+ {
+ throw new ArgumentNullException(nameof(nativeLibraryGroups));
+ }
+ if (resourceAssemblies == null)
+ {
+ throw new ArgumentNullException(nameof(resourceAssemblies));
+ }
+ RuntimeAssemblyGroups = runtimeAssemblyGroups;
+ ResourceAssemblies = resourceAssemblies.ToArray();
+ NativeLibraryGroups = nativeLibraryGroups;
+ }
+
+ public IReadOnlyList RuntimeAssemblyGroups { get; }
+
+ public IReadOnlyList NativeLibraryGroups { get; }
+
+ public IReadOnlyList ResourceAssemblies { get; }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/TargetInfo.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/TargetInfo.cs
new file mode 100644
index 00000000000..980b5a2529c
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/Microsoft.Extensions.DependencyModel/TargetInfo.cs
@@ -0,0 +1,34 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+
+namespace Internal.Microsoft.Extensions.DependencyModel
+{
+ internal class TargetInfo
+ {
+ public TargetInfo(string framework,
+ string runtime,
+ string runtimeSignature,
+ bool isPortable)
+ {
+ if (string.IsNullOrEmpty(framework))
+ {
+ throw new ArgumentException(nameof(framework));
+ }
+
+ Framework = framework;
+ Runtime = runtime;
+ RuntimeSignature = runtimeSignature;
+ IsPortable = isPortable;
+ }
+
+ public string Framework { get; }
+
+ public string Runtime { get; }
+
+ public string RuntimeSignature { get; }
+
+ public bool IsPortable { get; }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/XunitPackageCompilationAssemblyResolver.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/XunitPackageCompilationAssemblyResolver.cs
new file mode 100644
index 00000000000..32974a0c190
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/XunitPackageCompilationAssemblyResolver.cs
@@ -0,0 +1,101 @@
+// Adapted from https://github.com/dotnet/core-setup/blob/652b680dff6b1afb9cd26cc3c2e883a664c209fd/src/managed/Microsoft.Extensions.DependencyModel/Resolution/PackageCompilationAssemblyResolver.cs
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Internal.Microsoft.DotNet.PlatformAbstractions;
+using Internal.Microsoft.Extensions.DependencyModel;
+using Internal.Microsoft.Extensions.DependencyModel.Resolution;
+using Xunit.Abstractions;
+
+namespace Xunit
+{
+ class XunitPackageCompilationAssemblyResolver : ICompilationAssemblyResolver
+ {
+ readonly IFileSystem fileSystem;
+ readonly List nugetPackageDirectories;
+
+ public XunitPackageCompilationAssemblyResolver(IMessageSink internalDiagnosticsMessageSink,
+ IFileSystem fileSystem = null)
+ {
+ nugetPackageDirectories = GetDefaultProbeDirectories(internalDiagnosticsMessageSink);
+ this.fileSystem = fileSystem ?? new FileSystemWrapper();
+ }
+
+ static List GetDefaultProbeDirectories(IMessageSink internalDiagnosticsMessageSink) =>
+ GetDefaultProbeDirectories(RuntimeEnvironment.OperatingSystemPlatform, internalDiagnosticsMessageSink);
+
+ static List GetDefaultProbeDirectories(Platform osPlatform, IMessageSink internalDiagnosticsMessageSink)
+ {
+ var results = default(List);
+
+ var probeDirectories = AppContext.GetData("PROBING_DIRECTORIES") as string;
+ if (!string.IsNullOrEmpty(probeDirectories))
+ results = probeDirectories.Split(new char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries).ToList();
+ else
+ {
+ results = new List();
+
+ // Allow the user to override the default location of NuGet packages
+ var packageDirectory = Environment.GetEnvironmentVariable("NUGET_PACKAGES");
+ if (!string.IsNullOrEmpty(packageDirectory))
+ results.Add(packageDirectory);
+ else
+ {
+ string basePath;
+ if (osPlatform == Platform.Windows)
+ basePath = Environment.GetEnvironmentVariable("USERPROFILE");
+ else
+ basePath = Environment.GetEnvironmentVariable("HOME");
+
+ if (!string.IsNullOrEmpty(basePath))
+ results.Add(Path.Combine(basePath, ".nuget", "packages"));
+ }
+ }
+
+ if (internalDiagnosticsMessageSink != null)
+ internalDiagnosticsMessageSink.OnMessage(new _DiagnosticMessage($"[XunitPackageCompilationAssemblyResolver.GetDefaultProbeDirectories] returns: [{string.Join(",", results.Select(x => $"'{x}'"))}]"));
+
+ return results;
+ }
+
+ public bool TryResolveAssemblyPaths(CompilationLibrary library, List assemblies)
+ {
+ if (nugetPackageDirectories.Count == 0 || !string.Equals(library.Type, "package", StringComparison.OrdinalIgnoreCase))
+ return false;
+
+ foreach (var directory in nugetPackageDirectories)
+ if (ResolverUtils.TryResolvePackagePath(fileSystem, library, directory, out var packagePath))
+ if (TryResolveFromPackagePath(library, packagePath, out var fullPathsFromPackage))
+ {
+ assemblies.AddRange(fullPathsFromPackage);
+ return true;
+ }
+
+ return false;
+ }
+
+ bool TryResolveFromPackagePath(CompilationLibrary library, string basePath, out IEnumerable results)
+ {
+ var paths = new List();
+
+ foreach (var assembly in library.Assemblies)
+ {
+ if (!ResolverUtils.TryResolveAssemblyFile(fileSystem, basePath, assembly, out var fullName))
+ {
+ // if one of the files can't be found, skip this package path completely.
+ // there are package paths that don't include all of the "ref" assemblies
+ // (ex. ones created by 'dotnet store')
+ results = null;
+ return false;
+ }
+
+ paths.Add(fullName);
+ }
+
+ results = paths;
+ return true;
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/_DiagnosticMessage.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/_DiagnosticMessage.cs
new file mode 100644
index 00000000000..693c93f99d9
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/AssemblyResolution/_DiagnosticMessage.cs
@@ -0,0 +1,19 @@
+using Xunit.Abstractions;
+
+namespace Xunit
+{
+ class _DiagnosticMessage : IDiagnosticMessage
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message to send
+ public _DiagnosticMessage(string message)
+ {
+ Message = message;
+ }
+
+ ///
+ public string Message { get; set; }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/ConsoleHelper.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/ConsoleHelper.cs
new file mode 100644
index 00000000000..6a47a2a2255
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/ConsoleHelper.cs
@@ -0,0 +1,66 @@
+// This code was adapted from https://github.com/Microsoft/msbuild/blob/ab090d1255caa87e742cbdbc6d7fe904ecebd975/src/Build/Logging/BaseConsoleLogger.cs#L361-L401
+// Under the MIT license https://github.com/Microsoft/msbuild/blob/ab090d1255caa87e742cbdbc6d7fe904ecebd975/LICENSE
+
+using System;
+
+namespace Xunit
+{
+ internal static class ConsoleHelper
+ {
+ internal static Action ResetColor;
+ internal static Action SetForegroundColor;
+
+ static ConsoleHelper()
+ {
+ if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows))
+ {
+ ResetColor = ResetColorConsole;
+ SetForegroundColor = SetForegroundColorConsole;
+ }
+ else
+ {
+ ResetColor = ResetColorANSI;
+ SetForegroundColor = SetForegroundColorANSI;
+ }
+ }
+
+ static void SetForegroundColorANSI(ConsoleColor c)
+ {
+ string colorString = "\x1b[";
+ switch (c)
+ {
+ case ConsoleColor.Black: colorString += "30"; break;
+ case ConsoleColor.DarkBlue: colorString += "34"; break;
+ case ConsoleColor.DarkGreen: colorString += "32"; break;
+ case ConsoleColor.DarkCyan: colorString += "36"; break;
+ case ConsoleColor.DarkRed: colorString += "31"; break;
+ case ConsoleColor.DarkMagenta: colorString += "35"; break;
+ case ConsoleColor.DarkYellow: colorString += "33"; break;
+ case ConsoleColor.Gray: colorString += "37"; break;
+ case ConsoleColor.DarkGray: colorString += "30;1"; break;
+ case ConsoleColor.Blue: colorString += "34;1"; break;
+ case ConsoleColor.Green: colorString += "32;1"; break;
+ case ConsoleColor.Cyan: colorString += "36;1"; break;
+ case ConsoleColor.Red: colorString += "31;1"; break;
+ case ConsoleColor.Magenta: colorString += "35;1"; break;
+ case ConsoleColor.Yellow: colorString += "33;1"; break;
+ case ConsoleColor.White: colorString += "37;1"; break;
+ default: colorString = ""; break;
+ }
+ if ("" != colorString)
+ {
+ colorString += "m";
+ Console.Out.Write(colorString);
+ }
+ }
+
+ static void SetForegroundColorConsole(ConsoleColor c)
+ => Console.ForegroundColor = c;
+
+ static void ResetColorANSI()
+ => Console.Out.Write("\x1b[m");
+
+ static void ResetColorConsole()
+ => Console.ResetColor();
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/DictionaryExtensions.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/DictionaryExtensions.cs
new file mode 100644
index 00000000000..2dd72676b3f
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/DictionaryExtensions.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+static class DictionaryExtensions
+{
+ public static void Add(this IDictionary> dictionary, TKey key, TValue value)
+ {
+ dictionary.GetOrAdd(key).Add(value);
+ }
+
+ public static TValue GetOrAdd(this IDictionary dictionary, TKey key)
+ where TValue : new()
+ {
+ return dictionary.GetOrAdd(key, () => new TValue());
+ }
+
+ public static TValue GetOrAdd(this IDictionary dictionary, TKey key, Func newValue)
+ {
+ TValue result;
+
+ if (!dictionary.TryGetValue(key, out result))
+ {
+ result = newValue();
+ dictionary[key] = result;
+ }
+
+ return result;
+ }
+
+ public static Dictionary ToDictionaryIgnoringDuplicateKeys(this IEnumerable inputValues,
+ Func keySelector,
+ Func valueSelector,
+ IEqualityComparer comparer = null)
+ {
+ var result = new Dictionary(comparer);
+
+ foreach (var inputValue in inputValues)
+ {
+ var key = keySelector(inputValue);
+ if (!result.ContainsKey(key))
+ result.Add(key, valueSelector(inputValue));
+ }
+
+ return result;
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/Json.cs b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/Json.cs
new file mode 100644
index 00000000000..c31f0aac213
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/common/Json.cs
@@ -0,0 +1,879 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Text;
+
+namespace Xunit
+{
+ class JsonArray : JsonValue
+ {
+ readonly JsonValue[] _array;
+
+ public JsonArray(JsonValue[] array, int line, int column)
+ : base(line, column)
+ {
+ if (array == null)
+ {
+ throw new ArgumentNullException(nameof(array));
+ }
+
+ _array = array;
+ }
+
+ public int Length
+ {
+ get { return _array.Length; }
+ }
+
+ public JsonValue this[int index]
+ {
+ get { return _array[index]; }
+ }
+ }
+
+ class JsonBoolean : JsonValue
+ {
+ public JsonBoolean(JsonToken token)
+ : base(token.Line, token.Column)
+ {
+ if (token.Type == JsonTokenType.True)
+ {
+ Value = true;
+ }
+ else if (token.Type == JsonTokenType.False)
+ {
+ Value = false;
+ }
+ else
+ {
+ throw new ArgumentException("Token value should be either True or False.", nameof(token));
+ }
+ }
+
+ public bool Value { get; private set; }
+
+ public static implicit operator bool (JsonBoolean jsonBoolean)
+ {
+ return jsonBoolean.Value;
+ }
+ }
+
+ class JsonBuffer
+ {
+ public const string ValueNull = "null";
+ public const string ValueTrue = "true";
+ public const string ValueFalse = "false";
+
+ StringBuilder _buffer = new StringBuilder();
+ StringBuilder _codePointBuffer = new StringBuilder(4);
+ readonly TextReader _reader;
+ JsonToken _token;
+ int _line;
+ int _column;
+
+ public JsonBuffer(TextReader reader)
+ {
+ _reader = reader;
+ _line = 1;
+ }
+
+ public JsonToken Read()
+ {
+ int first;
+ while (true)
+ {
+ first = ReadNextChar();
+
+ if (first == -1)
+ {
+ _token.Type = JsonTokenType.EOF;
+ return _token;
+ }
+ else if (!IsWhitespace(first))
+ {
+ break;
+ }
+ }
+
+ _token.Value = ((char)first).ToString();
+ _token.Line = _line;
+ _token.Column = _column;
+
+ if (first == '{')
+ {
+ _token.Type = JsonTokenType.LeftCurlyBracket;
+ }
+ else if (first == '}')
+ {
+ _token.Type = JsonTokenType.RightCurlyBracket;
+ }
+ else if (first == '[')
+ {
+ _token.Type = JsonTokenType.LeftSquareBracket;
+ }
+ else if (first == ']')
+ {
+ _token.Type = JsonTokenType.RightSquareBracket;
+ }
+ else if (first == ':')
+ {
+ _token.Type = JsonTokenType.Colon;
+ }
+ else if (first == ',')
+ {
+ _token.Type = JsonTokenType.Comma;
+ }
+ else if (first == '"')
+ {
+ _token.Type = JsonTokenType.String;
+ _token.Value = ReadString();
+ }
+ else if (first == 't')
+ {
+ ReadLiteral(ValueTrue);
+ _token.Type = JsonTokenType.True;
+ }
+ else if (first == 'f')
+ {
+ ReadLiteral(ValueFalse);
+ _token.Type = JsonTokenType.False;
+ }
+ else if (first == 'n')
+ {
+ ReadLiteral(ValueNull);
+ _token.Type = JsonTokenType.Null;
+ }
+ else if ((first >= '0' && first <= '9') || first == '-')
+ {
+ _token.Type = JsonTokenType.Number;
+ _token.Value = ReadNumber(first);
+ }
+ else
+ {
+ throw new JsonDeserializerException(
+ JsonDeserializerResource.Format_IllegalCharacter(first),
+ _token);
+ }
+
+ // JsonToken is a value type
+ return _token;
+ }
+
+ int ReadNextChar()
+ {
+ while (true)
+ {
+ var value = _reader.Read();
+ _column++;
+ switch (value)
+ {
+ case -1:
+ // This is the end of file
+ return -1;
+ case '\n':
+ // This is a new line. Let the next loop read the first character of the following line.
+ // Set position ahead of next line
+ _column = 0;
+ _line++;
+
+ continue;
+ case '\r':
+ break;
+ default:
+ // Returns the normal value
+ return value;
+ }
+ }
+ }
+
+ string ReadNumber(int firstRead)
+ {
+#if NET35
+ _buffer = new StringBuilder();
+#else
+ _buffer.Clear();
+#endif
+ _buffer.Append((char)firstRead);
+
+ while (true)
+ {
+ var next = _reader.Peek();
+
+ if ((next >= '0' && next <= '9') ||
+ next == '.' ||
+ next == 'e' ||
+ next == 'E')
+ {
+ _buffer.Append((char)ReadNextChar());
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return _buffer.ToString();
+ }
+
+ void ReadLiteral(string literal)
+ {
+ for (int i = 1; i < literal.Length; ++i)
+ {
+ var next = _reader.Peek();
+ if (next != literal[i])
+ {
+ throw new JsonDeserializerException(
+ JsonDeserializerResource.Format_UnrecognizedLiteral(literal),
+ _line, _column);
+ }
+ else
+ {
+ ReadNextChar();
+ }
+ }
+
+ var tail = _reader.Peek();
+ if (tail != '}' &&
+ tail != ']' &&
+ tail != ',' &&
+ tail != '\n' &&
+ tail != -1 &&
+ !IsWhitespace(tail))
+ {
+ throw new JsonDeserializerException(
+ JsonDeserializerResource.Format_IllegalTrailingCharacterAfterLiteral(tail, literal),
+ _line, _column);
+ }
+ }
+
+ string ReadString()
+ {
+#if NET35
+ _buffer = new StringBuilder();
+#else
+ _buffer.Clear();
+#endif
+ var escaped = false;
+
+ while (true)
+ {
+ var next = ReadNextChar();
+
+ if (next == -1 || next == '\n')
+ {
+ throw new JsonDeserializerException(
+ JsonDeserializerResource.JSON_OpenString,
+ _line, _column);
+ }
+ else if (escaped)
+ {
+ if ((next == '"') || (next == '\\') || (next == '/'))
+ {
+ _buffer.Append((char)next);
+ }
+ else if (next == 'b')
+ {
+ // '\b' backspace
+ _buffer.Append('\b');
+ }
+ else if (next == 'f')
+ {
+ // '\f' form feed
+ _buffer.Append('\f');
+ }
+ else if (next == 'n')
+ {
+ // '\n' line feed
+ _buffer.Append('\n');
+ }
+ else if (next == 'r')
+ {
+ // '\r' carriage return
+ _buffer.Append('\r');
+ }
+ else if (next == 't')
+ {
+ // '\t' tab
+ _buffer.Append('\t');
+ }
+ else if (next == 'u')
+ {
+ // '\uXXXX' unicode
+ var unicodeLine = _line;
+ var unicodeColumn = _column;
+
+#if NET35
+ _codePointBuffer = new StringBuilder(4);
+#else
+ _codePointBuffer.Clear();
+#endif
+ for (int i = 0; i < 4; ++i)
+ {
+ next = ReadNextChar();
+ if (next == -1)
+ {
+ throw new JsonDeserializerException(
+ JsonDeserializerResource.JSON_InvalidEnd,
+ unicodeLine,
+ unicodeColumn);
+ }
+ else
+ {
+ _codePointBuffer[i] = (char)next;
+ }
+ }
+
+ try
+ {
+ var unicodeValue = int.Parse(_codePointBuffer.ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
+ _buffer.Append((char)unicodeValue);
+ }
+ catch (FormatException ex)
+ {
+ throw new JsonDeserializerException(
+ JsonDeserializerResource.Format_InvalidUnicode(_codePointBuffer.ToString()),
+ ex,
+ unicodeLine,
+ unicodeColumn);
+ }
+ }
+ else
+ {
+ throw new JsonDeserializerException(
+ JsonDeserializerResource.Format_InvalidSyntaxNotExpected("character escape", "\\" + next),
+ _line,
+ _column);
+ }
+
+ escaped = false;
+ }
+ else if (next == '\\')
+ {
+ escaped = true;
+ }
+ else if (next == '"')
+ {
+ break;
+ }
+ else
+ {
+ _buffer.Append((char)next);
+ }
+ }
+
+ return _buffer.ToString();
+ }
+
+ static bool IsWhitespace(int value)
+ {
+ return value == ' ' || value == '\t' || value == '\r';
+ }
+ }
+
+ static class JsonDeserializer
+ {
+ public static JsonValue Deserialize(TextReader reader)
+ {
+ if (reader == null)
+ {
+ throw new ArgumentNullException(nameof(reader));
+ }
+
+ var buffer = new JsonBuffer(reader);
+
+ var result = DeserializeInternal(buffer.Read(), buffer);
+
+ // There are still unprocessed char. The parsing is not finished. Error happened.
+ var nextToken = buffer.Read();
+ if (nextToken.Type != JsonTokenType.EOF)
+ {
+ throw new JsonDeserializerException(
+ JsonDeserializerResource.Format_UnfinishedJSON(nextToken.Value),
+ nextToken);
+ }
+
+ return result;
+ }
+
+ static JsonValue DeserializeInternal(JsonToken next, JsonBuffer buffer)
+ {
+ if (next.Type == JsonTokenType.EOF)
+ {
+ return null;
+ }
+
+ if (next.Type == JsonTokenType.LeftSquareBracket)
+ {
+ return DeserializeArray(next, buffer);
+ }
+
+ if (next.Type == JsonTokenType.LeftCurlyBracket)
+ {
+ return DeserializeObject(next, buffer);
+ }
+
+ if (next.Type == JsonTokenType.String)
+ {
+ return new JsonString(next.Value, next.Line, next.Column);
+ }
+
+ if (next.Type == JsonTokenType.True || next.Type == JsonTokenType.False)
+ {
+ return new JsonBoolean(next);
+ }
+
+ if (next.Type == JsonTokenType.Null)
+ {
+ return new JsonNull(next.Line, next.Column);
+ }
+
+ if (next.Type == JsonTokenType.Number)
+ {
+ return new JsonNumber(next);
+ }
+
+ throw new JsonDeserializerException(JsonDeserializerResource.Format_InvalidTokenExpectation(
+ next.Value, "'{', '[', true, false, null, JSON string, JSON number, or the end of the file"),
+ next);
+ }
+
+ static JsonArray DeserializeArray(JsonToken head, JsonBuffer buffer)
+ {
+ var list = new List();
+ while (true)
+ {
+ var next = buffer.Read();
+ if (next.Type == JsonTokenType.RightSquareBracket)
+ {
+ break;
+ }
+
+ list.Add(DeserializeInternal(next, buffer));
+
+ next = buffer.Read();
+ if (next.Type == JsonTokenType.EOF)
+ {
+ throw new JsonDeserializerException(
+ JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON array", ']', ','),
+ next);
+ }
+ else if (next.Type == JsonTokenType.RightSquareBracket)
+ {
+ break;
+ }
+ else if (next.Type != JsonTokenType.Comma)
+ {
+ throw new JsonDeserializerException(
+ JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON array", ','),
+ next);
+ }
+ }
+
+ return new JsonArray(list.ToArray(), head.Line, head.Column);
+ }
+
+ static JsonObject DeserializeObject(JsonToken head, JsonBuffer buffer)
+ {
+ var dictionary = new Dictionary();
+
+ // Loop through each JSON entry in the input object
+ while (true)
+ {
+ var next = buffer.Read();
+ if (next.Type == JsonTokenType.EOF)
+ {
+ throw new JsonDeserializerException(
+ JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON object", '}'),
+ next);
+ }
+
+ if (next.Type == JsonTokenType.Colon)
+ {
+ throw new JsonDeserializerException(
+ JsonDeserializerResource.Format_InvalidSyntaxNotExpected("JSON object", ':'),
+ next);
+ }
+ else if (next.Type == JsonTokenType.RightCurlyBracket)
+ {
+ break;
+ }
+ else
+ {
+ if (next.Type != JsonTokenType.String)
+ {
+ throw new JsonDeserializerException(
+ JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON object member name", "JSON string"),
+ next);
+ }
+
+ var memberName = next.Value;
+ if (dictionary.ContainsKey(memberName))
+ {
+ throw new JsonDeserializerException(
+ JsonDeserializerResource.Format_DuplicateObjectMemberName(memberName),
+ next);
+ }
+
+ next = buffer.Read();
+ if (next.Type != JsonTokenType.Colon)
+ {
+ throw new JsonDeserializerException(
+ JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON object", ':'),
+ next);
+ }
+
+ dictionary[memberName] = DeserializeInternal(buffer.Read(), buffer);
+
+ next = buffer.Read();
+ if (next.Type == JsonTokenType.RightCurlyBracket)
+ {
+ break;
+ }
+ else if (next.Type != JsonTokenType.Comma)
+ {
+ throw new JsonDeserializerException(
+ JsonDeserializerResource.Format_InvalidSyntaxExpectation("JSON object", ',', '}'),
+ next);
+ }
+ }
+ }
+
+ return new JsonObject(dictionary, head.Line, head.Column);
+ }
+ }
+
+ class JsonDeserializerException : Exception
+ {
+ public JsonDeserializerException(string message, Exception innerException, int line, int column)
+ : base(message, innerException)
+ {
+ Line = line;
+ Column = column;
+ }
+
+ public JsonDeserializerException(string message, int line, int column)
+ : base(message)
+ {
+ Line = line;
+ Column = column;
+ }
+
+ public JsonDeserializerException(string message, JsonToken nextToken)
+ : base(message)
+ {
+ Line = nextToken.Line;
+ Column = nextToken.Column;
+ }
+
+ public int Line { get; }
+
+ public int Column { get; }
+ }
+
+ static class JsonDeserializerResource
+ {
+ internal static string Format_IllegalCharacter(int value)
+ {
+ return $"Illegal character '{(char)value}' (Unicode hexadecimal {value:X4}).";
+ }
+
+ internal static string Format_IllegalTrailingCharacterAfterLiteral(int value, string literal)
+ {
+ return $"Illegal character '{(char)value}' (Unicode hexadecimal {value:X4}) after the literal name '{literal}'.";
+ }
+
+ internal static string Format_UnrecognizedLiteral(string literal)
+ {
+ return $"Invalid JSON literal. Expected literal '{literal}'.";
+ }
+
+ internal static string Format_DuplicateObjectMemberName(string memberName)
+ {
+ return Format_InvalidSyntax("JSON object", $"Duplicate member name '{memberName}'");
+ }
+
+ internal static string Format_InvalidFloatNumberFormat(string raw)
+ {
+ return $"Invalid float number format: {raw}";
+ }
+
+ internal static string Format_FloatNumberOverflow(string raw)
+ {
+ return $"Float number overflow: {raw}";
+ }
+
+ internal static string Format_InvalidSyntax(string syntaxName, string issue)
+ {
+ return $"Invalid {syntaxName} syntax. {issue}.";
+ }
+
+ internal static string Format_InvalidSyntaxNotExpected(string syntaxName, char unexpected)
+ {
+ return $"Invalid {syntaxName} syntax. Unexpected '{unexpected}'.";
+ }
+
+ internal static string Format_InvalidSyntaxNotExpected(string syntaxName, string unexpected)
+ {
+ return $"Invalid {syntaxName} syntax. Unexpected {unexpected}.";
+ }
+
+ internal static string Format_InvalidSyntaxExpectation(string syntaxName, char expectation)
+ {
+ return $"Invalid {syntaxName} syntax. Expected '{expectation}'.";
+ }
+
+ internal static string Format_InvalidSyntaxExpectation(string syntaxName, string expectation)
+ {
+ return $"Invalid {syntaxName} syntax. Expected {expectation}.";
+ }
+
+ internal static string Format_InvalidSyntaxExpectation(string syntaxName, char expectation1, char expectation2)
+ {
+ return $"Invalid {syntaxName} syntax. Expected '{expectation1}' or '{expectation2}'.";
+ }
+
+ internal static string Format_InvalidTokenExpectation(string tokenValue, string expectation)
+ {
+ return $"Unexpected token '{tokenValue}'. Expected {expectation}.";
+ }
+
+ internal static string Format_InvalidUnicode(string unicode)
+ {
+ return $"Invalid Unicode [{unicode}]";
+ }
+
+ internal static string Format_UnfinishedJSON(string nextTokenValue)
+ {
+ return $"Invalid JSON end. Unprocessed token {nextTokenValue}.";
+ }
+
+ internal static string JSON_OpenString
+ {
+ get { return Format_InvalidSyntaxExpectation("JSON string", '\"'); }
+ }
+
+ internal static string JSON_InvalidEnd
+ {
+ get { return "Invalid JSON. Unexpected end of file."; }
+ }
+ }
+
+ class JsonNull : JsonValue
+ {
+ public JsonNull(int line, int column)
+ : base(line, column)
+ {
+ }
+ }
+
+ class JsonNumber : JsonValue
+ {
+ readonly string _raw;
+ readonly double _double;
+
+ public JsonNumber(JsonToken token)
+ : base(token.Line, token.Column)
+ {
+ try
+ {
+ _raw = token.Value;
+ _double = double.Parse(_raw, NumberStyles.Float);
+ }
+ catch (FormatException ex)
+ {
+ throw new JsonDeserializerException(
+ JsonDeserializerResource.Format_InvalidFloatNumberFormat(_raw),
+ ex,
+ token.Line,
+ token.Column);
+ }
+ catch (OverflowException ex)
+ {
+ throw new JsonDeserializerException(
+ JsonDeserializerResource.Format_FloatNumberOverflow(_raw),
+ ex,
+ token.Line,
+ token.Column);
+ }
+ }
+
+ public double Double
+ {
+ get { return _double; }
+ }
+
+ public string Raw
+ {
+ get { return _raw; }
+ }
+ }
+
+ class JsonObject : JsonValue
+ {
+ readonly IDictionary _data;
+
+ public JsonObject(IDictionary data, int line, int column)
+ : base(line, column)
+ {
+ if (data == null)
+ {
+ throw new ArgumentNullException(nameof(data));
+ }
+
+ _data = data;
+ }
+
+ public ICollection Keys
+ {
+ get { return _data.Keys; }
+ }
+
+ public JsonValue Value(string key)
+ {
+ JsonValue result;
+ if (!_data.TryGetValue(key, out result))
+ {
+ result = null;
+ }
+
+ return result;
+ }
+
+ public JsonObject ValueAsJsonObject(string key)
+ {
+ return Value(key) as JsonObject;
+ }
+
+ public JsonString ValueAsString(string key)
+ {
+ return Value(key) as JsonString;
+ }
+
+ public int ValueAsInt(string key)
+ {
+ var number = Value(key) as JsonNumber;
+ if (number == null)
+ {
+ throw new FormatException();
+ }
+ return Convert.ToInt32(number.Raw);
+ }
+
+ public bool ValueAsBoolean(string key, bool defaultValue = false)
+ {
+ var boolVal = Value(key) as JsonBoolean;
+ if (boolVal != null)
+ {
+ return boolVal.Value;
+ }
+
+ return defaultValue;
+ }
+
+ public bool? ValueAsNullableBoolean(string key)
+ {
+ var boolVal = Value(key) as JsonBoolean;
+ if (boolVal != null)
+ {
+ return boolVal.Value;
+ }
+
+ return null;
+ }
+
+ public string[] ValueAsStringArray(string key)
+ {
+ var list = Value(key) as JsonArray;
+ if (list == null)
+ {
+ return null;
+ }
+
+ var result = new string[list.Length];
+
+ for (int i = 0; i < list.Length; ++i)
+ {
+ var jsonString = list[i] as JsonString;
+ result[i] = jsonString?.ToString();
+ }
+
+ return result;
+ }
+ }
+
+ class JsonString : JsonValue
+ {
+ readonly string _value;
+
+ public JsonString(string value, int line, int column)
+ : base(line, column)
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ _value = value;
+ }
+
+ public string Value
+ {
+ get { return _value; }
+ }
+
+ public override string ToString()
+ {
+ return _value;
+ }
+
+ public static implicit operator string (JsonString instance)
+ {
+ if (instance == null)
+ {
+ return null;
+ }
+ else
+ {
+ return instance.Value;
+ }
+ }
+ }
+
+ struct JsonToken
+ {
+ public JsonTokenType Type;
+ public string Value;
+ public int Line;
+ public int Column;
+ }
+
+ enum JsonTokenType
+ {
+ LeftCurlyBracket, // [
+ LeftSquareBracket, // {
+ RightCurlyBracket, // ]
+ RightSquareBracket, // }
+ Colon, // :
+ Comma, // ,
+ Null,
+ True,
+ False,
+ Number,
+ String,
+ EOF
+ }
+
+ class JsonValue
+ {
+ public JsonValue(int line, int column)
+ {
+ Line = line;
+ Column = column;
+ }
+
+ public int Line { get; }
+
+ public int Column { get; }
+ }
+}
diff --git a/src/Microsoft.DotNet.XUnitConsoleRunner/src/xUnit1.xslt b/src/Microsoft.DotNet.XUnitConsoleRunner/src/xUnit1.xslt
new file mode 100644
index 00000000000..a8425d36efb
--- /dev/null
+++ b/src/Microsoft.DotNet.XUnitConsoleRunner/src/xUnit1.xslt
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Directory.Build.props b/src/Microsoft.DotNet.XUnitRunnerUap/Directory.Build.props
deleted file mode 100644
index 0c98d167d19..00000000000
--- a/src/Microsoft.DotNet.XUnitRunnerUap/Directory.Build.props
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Directory.Build.targets b/src/Microsoft.DotNet.XUnitRunnerUap/Directory.Build.targets
deleted file mode 100644
index 0c98d167d19..00000000000
--- a/src/Microsoft.DotNet.XUnitRunnerUap/Directory.Build.targets
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/Microsoft.DotNet.Uap.TestTools.sln b/src/Microsoft.DotNet.XUnitRunnerUap/Microsoft.DotNet.Uap.TestTools.sln
deleted file mode 100644
index 8e39506cab5..00000000000
--- a/src/Microsoft.DotNet.XUnitRunnerUap/Microsoft.DotNet.Uap.TestTools.sln
+++ /dev/null
@@ -1,65 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.28119.50
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowsStoreAppLauncher", "Launcher\WindowsStoreAppLauncher.vcxproj", "{177AA879-D233-4A16-8398-8BDF5D5E1ED8}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.XUnitRunnerUap", "src\Microsoft.DotNet.XUnitRunnerUap.csproj", "{552DECA2-68D7-4506-86DE-E808D1E60FB6}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|ARM = Debug|ARM
- Debug|ARM64 = Debug|ARM64
- Debug|x64 = Debug|x64
- Debug|x86 = Debug|x86
- Release|ARM = Release|ARM
- Release|ARM64 = Release|ARM64
- Release|x64 = Release|x64
- Release|x86 = Release|x86
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Debug|ARM.ActiveCfg = Release|x64
- {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Debug|ARM.Build.0 = Release|x64
- {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Debug|ARM64.ActiveCfg = Release|ARM64
- {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Debug|ARM64.Build.0 = Release|ARM64
- {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Debug|x64.ActiveCfg = Release|x64
- {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Debug|x64.Build.0 = Release|x64
- {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Debug|x86.ActiveCfg = Release|Win32
- {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Debug|x86.Build.0 = Release|Win32
- {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Release|ARM.ActiveCfg = Release|Win32
- {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Release|ARM64.ActiveCfg = Release|ARM64
- {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Release|ARM64.Build.0 = Release|ARM64
- {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Release|x64.ActiveCfg = Release|x64
- {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Release|x64.Build.0 = Release|x64
- {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Release|x86.ActiveCfg = Release|Win32
- {177AA879-D233-4A16-8398-8BDF5D5E1ED8}.Release|x86.Build.0 = Release|Win32
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|ARM.ActiveCfg = Debug|ARM
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|ARM.Build.0 = Debug|ARM
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|ARM.Deploy.0 = Debug|ARM
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|ARM64.Build.0 = Debug|ARM64
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|ARM64.Deploy.0 = Debug|ARM64
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|x64.ActiveCfg = Debug|x64
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|x64.Build.0 = Debug|x64
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|x64.Deploy.0 = Debug|x64
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|x86.ActiveCfg = Debug|x86
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|x86.Build.0 = Debug|x86
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Debug|x86.Deploy.0 = Debug|x86
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|ARM.ActiveCfg = Release|ARM
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|ARM.Build.0 = Release|ARM
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|ARM.Deploy.0 = Release|ARM
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|ARM64.ActiveCfg = Release|ARM64
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|ARM64.Build.0 = Release|ARM64
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|ARM64.Deploy.0 = Release|ARM64
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|x64.ActiveCfg = Release|x64
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|x64.Build.0 = Release|x64
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|x64.Deploy.0 = Release|x64
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|x86.ActiveCfg = Release|x86
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|x86.Build.0 = Release|x86
- {552DECA2-68D7-4506-86DE-E808D1E60FB6}.Release|x86.Deploy.0 = Release|x86
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
-EndGlobal
diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/DictionaryExtensions.cs b/src/Microsoft.DotNet.XUnitRunnerUap/src/DictionaryExtensions.cs
deleted file mode 100644
index ebfdd06f581..00000000000
--- a/src/Microsoft.DotNet.XUnitRunnerUap/src/DictionaryExtensions.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Collections.Generic;
-
-namespace Microsoft.DotNet.XUnitRunnerUap
-{
- internal static class DictionaryExtensions
- {
- public static void Add(this IDictionary> dictionary, TKey key, TValue value)
- {
- dictionary.GetOrAdd(key).Add(value);
- }
-
- public static TValue GetOrAdd(this IDictionary dictionary, TKey key)
- where TValue : new()
- {
- return dictionary.GetOrAdd(key, () => new TValue());
- }
-
- public static TValue GetOrAdd(this IDictionary dictionary, TKey key, Func newValue)
- {
- if (!dictionary.TryGetValue(key, out TValue result))
- {
- result = newValue();
- dictionary[key] = result;
- }
-
- return result;
- }
- }
-}
diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/Program.cs b/src/Microsoft.DotNet.XUnitRunnerUap/src/Program.cs
deleted file mode 100644
index 6c0ada7b0af..00000000000
--- a/src/Microsoft.DotNet.XUnitRunnerUap/src/Program.cs
+++ /dev/null
@@ -1,240 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Collections.Concurrent;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Threading.Tasks;
-using System.Xml.Linq;
-using Windows.Storage;
-using Xunit;
-
-namespace Microsoft.DotNet.XUnitRunnerUap
-{
- class Program
- {
- volatile static bool cancel = false;
-
- static int Main(string[] args)
- {
- // Handle RemoteExecutor
- if (args.Length > 0 && args[0] == "remote")
- {
- return RemoteExecutor.Execute(args.Skip(1).ToArray());
- }
-
- if (args.Length == 0 || args[0] == "-?" || args[0] == "/?" || args[0] == "-h" || args[0] == "--help")
- {
- PrintHeader();
- PrintUsage();
- return 2;
- }
-
- var commandLine = CommandLine.Parse(args);
-
- Console.CancelKeyPress += (sender, e) =>
- {
- if (!cancel)
- {
- Console.WriteLine("Canceling... (Press Ctrl+C again to terminate)");
- cancel = true;
- e.Cancel = true;
- }
- };
-
- if (commandLine.Debug)
- {
- Debugger.Launch();
- }
-
- if (!commandLine.NoLogo)
- PrintHeader();
-
- var completionMessages = new ConcurrentDictionary();
- var assembliesElement = new XElement("assemblies");
-
- int errorCount = 0;
- int failCount = 0;
-
- foreach (var assembly in commandLine.Project.Assemblies)
- {
- if (cancel)
- {
- break;
- }
-
- assembly.Configuration.PreEnumerateTheories = false;
- assembly.Configuration.DiagnosticMessages |= commandLine.DiagnosticMessages;
- assembly.Configuration.AppDomain = AppDomainSupport.Denied;
- var discoveryOptions = TestFrameworkOptions.ForDiscovery(assembly.Configuration);
- var executionOptions = TestFrameworkOptions.ForExecution(assembly.Configuration);
- executionOptions.SetDisableParallelization(true);
-
- try
- {
- using (var xunit = new XunitFrontController(AppDomainSupport.Denied, assembly.AssemblyFilename, assembly.ConfigFilename, assembly.Configuration.ShadowCopyOrDefault))
- using (var discoveryVisitor = new TestDiscoveryVisitor())
- {
- string assemblyName = Path.GetFileNameWithoutExtension(assembly.AssemblyFilename);
- // Discover & filter the tests
- Console.WriteLine($"Discovering: {assemblyName}");
- xunit.Find(false, discoveryVisitor, discoveryOptions);
- discoveryVisitor.Finished.WaitOne();
-
- var testCasesDiscovered = discoveryVisitor.TestCases.Count;
- var filteredTestCases = discoveryVisitor.TestCases.Where(commandLine.Project.Filters.Filter).ToList();
- var testCasesToRun = filteredTestCases.Count;
-
- Console.WriteLine($"Discovered: {assemblyName}");
-
- // Run the filtered tests
- if (testCasesToRun == 0)
- {
- Console.WriteLine($"Info: {assemblyName} has no tests to run");
- }
- else
- {
- if (commandLine.Serialize)
- {
- filteredTestCases = filteredTestCases.Select(xunit.Serialize).Select(xunit.Deserialize).ToList();
- }
-
- var assemblyElement = new XElement("assembly");
-
- StandardUapVisitor resultsVisitor = new StandardUapVisitor(assemblyElement, () => cancel, completionMessages, commandLine.Verbose, commandLine.FailSkips);
-
- xunit.RunTests(filteredTestCases, resultsVisitor, executionOptions);
-
- resultsVisitor.Finished.WaitOne();
-
- assembliesElement.Add(assemblyElement);
-
- // Set counters to determine the error code later.
- errorCount += resultsVisitor.ExecutionSummary.Errors;
- failCount += resultsVisitor.ExecutionSummary.Failed;
-
- Console.WriteLine($"{Path.GetFileNameWithoutExtension(assembly.AssemblyFilename)} Total: {resultsVisitor.ExecutionSummary.Total}, Errors: {resultsVisitor.ExecutionSummary.Errors}, Failed: {resultsVisitor.ExecutionSummary.Failed}, Skipped: {resultsVisitor.ExecutionSummary.Skipped}, Time: {resultsVisitor.ExecutionSummary.Time}");
- }
- }
- }
- catch (Exception ex)
- {
- assembliesElement = new XElement("error");
- assembliesElement.Add(ex);
-
- if (!commandLine.NoColor)
- Console.ForegroundColor = ConsoleColor.Red;
-
- Console.WriteLine($"error: {ex.Message}");
-
- if (commandLine.DiagnosticMessages)
- {
- if (!commandLine.NoColor)
- Console.ForegroundColor = ConsoleColor.DarkGray;
-
- Console.WriteLine(ex.StackTrace);
- }
- }
- finally
- {
- if (!commandLine.NoColor)
- Console.ResetColor();
-
- WriteResults(Path.GetFileName(assembly.AssemblyFilename), assembliesElement).GetAwaiter().GetResult();
- }
- }
-
- if (cancel)
- return -1073741510; // 0xC000013A: The application terminated as a result of a CTRL+C
-
- if (commandLine.Wait)
- {
- Console.WriteLine();
- Console.Write("Press any key to continue...");
- Console.ReadKey();
- Console.WriteLine();
- }
-
- if (errorCount > 0 || failCount > 0)
- return 1;
-
- return 0;
- }
-
- static async Task WriteResults(string test, XElement data)
- {
- StorageFolder folder = await KnownFolders.DocumentsLibrary.CreateFolderAsync("TestResults", CreationCollisionOption.OpenIfExists);
- StorageFile file = await folder.CreateFileAsync(test + ".xml", CreationCollisionOption.ReplaceExisting);
-
- using (var stream = await file.OpenStreamForWriteAsync())
- {
- data.Save(stream);
- stream.Flush();
- }
- }
-
- private static void PrintHeader()
- {
- var platform = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription;
- var versionAttribute = typeof(Program).Assembly.GetCustomAttribute();
-
- Console.WriteLine($"xUnit.net Console Runner v1.0.25 ({IntPtr.Size * 8}-bit {platform})");
- }
-
- private static void PrintUsage()
- {
- var executableName = "XUnitRunnerUap";
-
- Console.WriteLine("Copyright (C) .NET Foundation.");
- Console.WriteLine();
- Console.WriteLine($"usage: {executableName} [options]");
- Console.WriteLine();
- Console.WriteLine("Valid options:");
- Console.WriteLine(" -nologo : do not show the copyright message");
- Console.WriteLine(" -nocolor : do not output results with colors");
- Console.WriteLine(" -failskips : convert skipped tests into failures");
- Console.WriteLine(" -parallel option : set parallelization based on option");
- Console.WriteLine(" : none - turn off all parallelization");
- Console.WriteLine(" : collections - only parallelize collections");
- Console.WriteLine(" : assemblies - only parallelize assemblies");
- Console.WriteLine(" : all - parallelize assemblies & collections");
- Console.WriteLine(" -maxthreads count : maximum thread count for collection parallelization");
- Console.WriteLine(" : default - run with default (1 thread per CPU thread)");
- Console.WriteLine(" : unlimited - run with unbounded thread count");
- Console.WriteLine(" : (number) - limit task thread pool size to 'count'");
- Console.WriteLine(" -wait : wait for input after completion");
- Console.WriteLine(" -diagnostics : enable diagnostics messages for all test assemblies");
- Console.WriteLine(" -debug : launch the debugger to debug the tests");
- Console.WriteLine(" -serialize : serialize all test cases (for diagnostic purposes only)");
- Console.WriteLine(" -verbose : enable verbose messages (track start and finish time)");
- Console.WriteLine(" -trait \"name=value\" : only run tests with matching name/value traits");
- Console.WriteLine(" : if specified more than once, acts as an OR operation");
- Console.WriteLine(" -notrait \"name=value\" : do not run tests with matching name/value traits");
- Console.WriteLine(" : if specified more than once, acts as an AND operation");
- Console.WriteLine(" -method \"name\" : run a given test method (can be fully specified or use a wildcard;");
- Console.WriteLine(" : i.e., 'MyNamespace.MyClass.MyTestMethod' or '*.MyTestMethod')");
- Console.WriteLine(" : if specified more than once, acts as an OR operation");
- Console.WriteLine(" -nomethod \"name\" : do not run a given test method (can be fully specified or use a wildcard;");
- Console.WriteLine(" : i.e., 'MyNamespace.MyClass.MyTestMethod' or '*.MyTestMethod')");
- Console.WriteLine(" : if specified more than once, acts as an AND operation");
- Console.WriteLine(" -class \"name\" : run all methods in a given test class (should be fully");
- Console.WriteLine(" : specified; i.e., 'MyNamespace.MyClass')");
- Console.WriteLine(" : if specified more than once, acts as an OR operation");
- Console.WriteLine(" -noclass \"name\" : do not run any methods in a given test class (should be fully");
- Console.WriteLine(" : specified; i.e., 'MyNamespace.MyClass')");
- Console.WriteLine(" : if specified more than once, acts as an AND operation");
- Console.WriteLine(" -namespace \"name\" : run all methods in a given namespace (i.e.,");
- Console.WriteLine(" : 'MyNamespace.MySubNamespace')");
- Console.WriteLine(" : if specified more than once, acts as an OR operation");
- Console.WriteLine(" -nonamespace \"name\" : do not run any methods in a given namespace (i.e.,");
- Console.WriteLine(" : 'MyNamespace.MySubNamespace')");
- Console.WriteLine(" : if specified more than once, acts as an AND operation");
- Console.WriteLine(" -xml \"name\" : The xml test results file name");
- Console.WriteLine();
- }
- }
-}
diff --git a/src/Microsoft.DotNet.XUnitRunnerUap/src/StandardUapVisitor.cs b/src/Microsoft.DotNet.XUnitRunnerUap/src/StandardUapVisitor.cs
deleted file mode 100644
index 5af2dd709eb..00000000000
--- a/src/Microsoft.DotNet.XUnitRunnerUap/src/StandardUapVisitor.cs
+++ /dev/null
@@ -1,187 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Collections.Concurrent;
-using System.IO;
-using System.Xml.Linq;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Microsoft.DotNet.XUnitRunnerUap
-{
- internal class StandardUapVisitor : XmlTestExecutionVisitor
- {
- private string _assemblyName;
- private readonly ConcurrentDictionary _completionMessages;
- private readonly bool _verbose;
- private readonly bool _failSkips;
-
- public StandardUapVisitor(XElement assemblyElement, Func cancelThunk,
- ConcurrentDictionary completionMessages, bool verbose, bool failSkips)
- : base(assemblyElement, cancelThunk)
- {
- _completionMessages = completionMessages;
- _verbose = verbose;
- _failSkips = failSkips;
- }
-
- public ExecutionSummary ExecutionSummary
- {
- get
- {
- if (_completionMessages.TryGetValue(_assemblyName, out ExecutionSummary summary))
- {
- return summary;
- }
-
- return new ExecutionSummary();
- }
- }
-
- protected override bool Visit(ITestAssemblyStarting assemblyStarting)
- {
- _assemblyName = Path.GetFileNameWithoutExtension(assemblyStarting.TestAssembly.Assembly.AssemblyPath);
-
- Console.WriteLine($"Starting: {_assemblyName}");
-
- return base.Visit(assemblyStarting);
- }
-
- protected override bool Visit(ITestAssemblyFinished assemblyFinished)
- {
- // Base class does computation of results, so call it first.
- var result = base.Visit(assemblyFinished);
-
- Console.WriteLine($"Finished: {_assemblyName}");
-
- _completionMessages.TryAdd(_assemblyName, new ExecutionSummary
- {
- Total = assemblyFinished.TestsRun,
- Failed = !_failSkips ? assemblyFinished.TestsFailed : assemblyFinished.TestsFailed + assemblyFinished.TestsSkipped,
- Skipped = !_failSkips ? assemblyFinished.TestsSkipped : 0,
- Time = assemblyFinished.ExecutionTime,
- Errors = Errors
- });
-
- return result;
- }
-
- protected override bool Visit(ITestFailed testFailed)
- {
- Console.WriteLine($" {XmlEscape(testFailed.Test.DisplayName)} [FAIL]");
- Console.WriteLine($" {ExceptionUtility.CombineMessages(testFailed).Replace(Environment.NewLine, Environment.NewLine + " ")}");
-
- WriteStackTrace(ExceptionUtility.CombineStackTraces(testFailed));
-
- return base.Visit(testFailed);
- }
-
- protected override bool Visit(ITestPassed testPassed)
- {
- return base.Visit(testPassed);
- }
-
- protected override bool Visit(ITestSkipped testSkipped)
- {
- if (_failSkips)
- {
- return Visit(new TestFailed(testSkipped.Test, 0M, "", new[] { "FAIL_SKIP" }, new[] { testSkipped.Reason }, new[] { "" }, new[] { -1 }));
- }
-
- Console.WriteLine($" {XmlEscape(testSkipped.Test.DisplayName)} [SKIP]");
- Console.WriteLine($" {XmlEscape(testSkipped.Reason)}");
-
- return base.Visit(testSkipped);
- }
-
- protected override bool Visit(ITestStarting testStarting)
- {
- if (_verbose)
- {
- Console.WriteLine($" {XmlEscape(testStarting.Test.DisplayName)} [STARTING]");
- }
- return base.Visit(testStarting);
- }
-
- protected override bool Visit(ITestFinished testFinished)
- {
- if (_verbose)
- {
- Console.WriteLine($" {XmlEscape(testFinished.Test.DisplayName)} [FINISHED] Time: {testFinished.ExecutionTime}s");
- }
- return base.Visit(testFinished);
- }
-
- protected override bool Visit(IErrorMessage error)
- {
- WriteError("FATAL", error);
-
- return base.Visit(error);
- }
-
- protected override bool Visit(ITestAssemblyCleanupFailure cleanupFailure)
- {
- WriteError($"Test Assembly Cleanup Failure ({cleanupFailure.TestAssembly.Assembly.AssemblyPath})", cleanupFailure);
-
- return base.Visit(cleanupFailure);
- }
-
- protected override bool Visit(ITestCaseCleanupFailure cleanupFailure)
- {
- WriteError($"Test Case Cleanup Failure ({cleanupFailure.TestCase.DisplayName})", cleanupFailure);
-
- return base.Visit(cleanupFailure);
- }
-
- protected override bool Visit(ITestClassCleanupFailure cleanupFailure)
- {
- WriteError($"Test Class Cleanup Failure ({cleanupFailure.TestClass.Class.Name})", cleanupFailure);
-
- return base.Visit(cleanupFailure);
- }
-
- protected override bool Visit(ITestCollectionCleanupFailure cleanupFailure)
- {
- WriteError($"Test Collection Cleanup Failure ({cleanupFailure.TestCollection.DisplayName})", cleanupFailure);
-
- return base.Visit(cleanupFailure);
- }
-
- protected override bool Visit(ITestCleanupFailure cleanupFailure)
- {
- WriteError($"Test Cleanup Failure ({cleanupFailure.Test.DisplayName})", cleanupFailure);
-
- return base.Visit(cleanupFailure);
- }
-
- protected override bool Visit(ITestMethodCleanupFailure cleanupFailure)
- {
- WriteError($"Test Method Cleanup Failure ({cleanupFailure.TestMethod.Method.Name})", cleanupFailure);
-
- return base.Visit(cleanupFailure);
- }
-
- protected void WriteError(string failureName, IFailureInformation failureInfo)
- {
- Console.WriteLine($" [{failureName}] {XmlEscape(failureInfo.ExceptionTypes[0])}");
- Console.WriteLine($" {XmlEscape(ExceptionUtility.CombineMessages(failureInfo))}");
-
- WriteStackTrace(ExceptionUtility.CombineStackTraces(failureInfo));
- }
-
- void WriteStackTrace(string stackTrace)
- {
- if (string.IsNullOrWhiteSpace(stackTrace))
- return;
-
- Console.WriteLine(" Stack Trace:");
-
- foreach (var stackFrame in stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.None))
- {
- Console.WriteLine($" {StackFrameTransformer.TransformFrame(stackFrame, Directory.GetCurrentDirectory())}");
- }
- }
- }
-}