diff --git a/SampleTestAssembly/Class1.cs b/SampleTestAssembly/Class1.cs index 0581428..c7187f5 100644 --- a/SampleTestAssembly/Class1.cs +++ b/SampleTestAssembly/Class1.cs @@ -21,8 +21,8 @@ namespace SampleTestAssembly //[Trait("TraitName1", "TraitValue2")] public void Fail() { - Thread.Sleep(TimeSpan.FromSeconds(1)); - Assert.True(false); + Thread.Sleep(TimeSpan.FromSeconds(2)); + Assert.True(true); } [Fact(Skip = "Testing")] diff --git a/SampleTestAssembly/SampleTestAssembly.csproj b/SampleTestAssembly/SampleTestAssembly.csproj index 88b95d7..090170b 100644 --- a/SampleTestAssembly/SampleTestAssembly.csproj +++ b/SampleTestAssembly/SampleTestAssembly.csproj @@ -1,5 +1,6 @@  + Debug @@ -41,20 +42,16 @@ - ..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll - True + ..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll - - ..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll - True + + ..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll - - ..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll - True + + ..\packages\xunit.extensibility.core.2.4.1\lib\net452\xunit.core.dll - - ..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll - True + + ..\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll @@ -64,7 +61,18 @@ + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + - \ No newline at end of file diff --git a/xunit.runner.worker/App.config b/xunit.runner.worker/App.config deleted file mode 100644 index 2d2a12d..0000000 --- a/xunit.runner.worker/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/xunit.runner.worker/Connection.cs b/xunit.runner.worker/Connection.cs deleted file mode 100644 index 3902e99..0000000 --- a/xunit.runner.worker/Connection.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.IO; -using System.IO.Pipes; - -namespace Xunit.Runner.Worker -{ - internal abstract class Connection : IDisposable - { - private bool _closed; - - internal abstract Stream Stream { get; } - - internal abstract void WaitForClientConnect(); - internal abstract void WaitForClientDone(); - - protected virtual void DisposeCore() - { - - } - - internal void Dispose() - { - if (_closed) - { - return; - } - - _closed = true; - DisposeCore(); - } - - #region IDisposable - - void IDisposable.Dispose() - { - Dispose(); - } - - #endregion - } - - internal sealed class NamedPipeConnection : Connection - { - private readonly NamedPipeServerStream _stream; - - internal override Stream Stream => _stream; - - internal NamedPipeConnection(string pipeName) - { - _stream = new NamedPipeServerStream(pipeName, PipeDirection.InOut); - } - - protected override void DisposeCore() - { - _stream.Dispose(); - } - - internal override void WaitForClientConnect() - { - _stream.WaitForConnection(); - } - - internal override void WaitForClientDone() - { - try - { - _stream.ReadByte(); - } - catch (Exception ex) - { - // If there is an error reading from the client then clearly they are done - Console.WriteLine($"Error reading client done byte {ex.Message}"); - } - } - } - - internal sealed class TestConnection : Connection - { - private readonly MemoryStream _stream = new MemoryStream(); - - internal override Stream Stream => _stream; - - internal override void WaitForClientConnect() - { - - } - - internal override void WaitForClientDone() - { - - } - } -} diff --git a/xunit.runner.worker/DiscoverUtil.cs b/xunit.runner.worker/DiscoverUtil.cs deleted file mode 100644 index 4ba4bac..0000000 --- a/xunit.runner.worker/DiscoverUtil.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.IO; -using Xunit.Abstractions; -using Xunit.Runner.Data; -using Xunit.Runner.Worker.MessageSinks; - -namespace Xunit.Runner.Worker -{ - internal sealed class DiscoverUtil : XunitUtil - { - private sealed class TestDiscoverySink : BaseTestDiscoverySink - { - private readonly ClientWriter _writer; - - internal TestDiscoverySink(ClientWriter writer) - { - _writer = writer; - } - - protected override bool ShouldContinue => _writer.IsConnected; - - protected override void OnTestDiscovered(ITestCaseDiscoveryMessage testCaseDiscovered) - { - var testCase = testCaseDiscovered.TestCase; - var testCaseData = new TestCaseData( - testCase.DisplayName, - testCase.UniqueID, - testCase.SkipReason, - testCaseDiscovered.TestAssembly.Assembly.AssemblyPath, - testCase.Traits); - - _writer.Write(TestDataKind.Value); - _writer.Write(testCaseData); - } - } - - internal static void Go(string assemblyFileName, Stream stream) - { - Go(assemblyFileName, stream, AppDomainSupport.IfAvailable, - (xunit, configuration, writer) => - { - using (var sink = new TestDiscoverySink(writer)) - { - xunit.Find(includeSourceInformation: false, messageSink: sink, - discoveryOptions: TestFrameworkOptions.ForDiscovery(configuration)); - - sink.Finished.WaitOne(); - - writer.Write(TestDataKind.EndOfData); - } - }); - } - } -} diff --git a/xunit.runner.worker/Listener.cs b/xunit.runner.worker/Listener.cs deleted file mode 100644 index 0ec1c00..0000000 --- a/xunit.runner.worker/Listener.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.IO.Pipes; -using System.Threading.Tasks; -using Xunit.Runner.Data; - -namespace Xunit.Runner.Worker -{ - internal sealed class Listener - { - private readonly string _pipeName; - private readonly List _taskList = new List(); - - internal Listener(string pipeName) - { - _pipeName = pipeName; - } - - internal void Go() - { - bool success; - do - { - _taskList.RemoveAll(x => x.IsCompleted); - success = GoOne(); - } - while (success); - - // Wait for the existing tasks to complete before stopping the listener - Task.WaitAll(_taskList.ToArray()); - } - - private bool GoOne() - { - try - { - var namedPipe = new NamedPipeServerStream(_pipeName, PipeDirection.InOut, maxNumberOfServerInstances: -1); - namedPipe.WaitForConnection(); - _taskList.Add(Task.Run(() => ProcessConnection(namedPipe))); - - return true; - } - catch (Exception ex) - { - Console.WriteLine($"Error creating named pipe {ex.Message}"); - return false; - } - } - - private static void ProcessConnection(NamedPipeServerStream stream) - { - Console.WriteLine("Connection established processing"); - ProcessConnectionCore(stream); - Console.WriteLine("Connection completed"); - } - - private static void ProcessConnectionCore(NamedPipeServerStream stream) - { - Debug.Assert(stream.IsConnected); - - try - { - var reader = new ClientReader(stream); - var action = reader.ReadString(); - var assemblyFileName = reader.ReadString(); - - switch (action) - { - case Constants.ActionDiscover: - Discover(stream, assemblyFileName); - break; - case Constants.ActionRunAll: - RunAll(stream, assemblyFileName); - break; - case Constants.ActionRunSpecific: - RunSpecific(stream, assemblyFileName); - break; - default: - Debug.Fail($"Invalid action {action}"); - break; - } - } - catch (Exception ex) - { - // Happens during a rude disconnect by the client - Console.WriteLine(ex.Message); - } - finally - { - stream.Dispose(); - } - } - - private static void Discover(Stream stream, string assemblyFileName) - { - Console.WriteLine($"discover started: {assemblyFileName}"); - DiscoverUtil.Go(assemblyFileName, stream); - Console.WriteLine("discover ended"); - } - - private static void RunAll(Stream stream, string assemblyFileName) - { - Console.WriteLine($"run all started: {assemblyFileName}"); - RunUtil.RunAll(assemblyFileName, stream); - Console.WriteLine("run all ended"); - } - - private static void RunSpecific(Stream stream, string assemblyFileName) - { - Console.WriteLine($"run specific started: {assemblyFileName}"); - RunUtil.RunSpecific(assemblyFileName, stream); - Console.WriteLine("run specific ended"); - } - } -} diff --git a/xunit.runner.worker/MessageSinks/BaseMessageSink.cs b/xunit.runner.worker/MessageSinks/BaseMessageSink.cs deleted file mode 100644 index 47d17e9..0000000 --- a/xunit.runner.worker/MessageSinks/BaseMessageSink.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using Xunit.Abstractions; - -namespace Xunit.Runner.Worker.MessageSinks -{ - /// - /// An Xunit implementation without the dispatch overhead of - /// and . - /// - internal abstract class BaseMessageSink : LongLivedMarshalByRefObject, IMessageSink, IDisposable - { - private bool _disposed; - - protected BaseMessageSink() - { - } - - ~BaseMessageSink() - { - Dispose(false); - } - - protected virtual void DisposeCore(bool disposing) - { - } - - private void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - DisposeCore(disposing); - - _disposed = true; - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(true); - } - - protected abstract bool OnMessage(IMessageSinkMessage message); - - bool IMessageSink.OnMessage(IMessageSinkMessage message) - { - return OnMessage(message); - } - } -} diff --git a/xunit.runner.worker/MessageSinks/BaseTestDiscoverySink.cs b/xunit.runner.worker/MessageSinks/BaseTestDiscoverySink.cs deleted file mode 100644 index 4a21c39..0000000 --- a/xunit.runner.worker/MessageSinks/BaseTestDiscoverySink.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Threading; -using Xunit.Abstractions; - -namespace Xunit.Runner.Worker.MessageSinks -{ - internal abstract class BaseTestDiscoverySink : BaseMessageSink - { - public ManualResetEvent Finished { get; } - - protected BaseTestDiscoverySink() - { - Finished = new ManualResetEvent(false); - } - - protected override void DisposeCore(bool disposing) - { - Finished.Dispose(); - } - - protected override bool OnMessage(IMessageSinkMessage message) - { - var discoveryMessage = message as ITestCaseDiscoveryMessage; - if (discoveryMessage != null) - { - OnTestDiscovered(discoveryMessage); - } - - if (message is IDiscoveryCompleteMessage) - { - Finished.Set(); - } - - return ShouldContinue; - } - - protected virtual bool ShouldContinue => true; - - protected abstract void OnTestDiscovered(ITestCaseDiscoveryMessage testCaseDiscovered); - } -} diff --git a/xunit.runner.worker/MessageSinks/BaseTestRunSink.cs b/xunit.runner.worker/MessageSinks/BaseTestRunSink.cs deleted file mode 100644 index 3f3d5a6..0000000 --- a/xunit.runner.worker/MessageSinks/BaseTestRunSink.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Threading; -using Xunit.Abstractions; - -namespace Xunit.Runner.Worker.MessageSinks -{ - internal abstract class BaseTestRunSink : BaseMessageSink - { - public ManualResetEvent Finished { get; } - - protected BaseTestRunSink() - { - Finished = new ManualResetEvent(false); - } - - protected override void DisposeCore(bool disposing) - { - Finished.Dispose(); - } - - protected override bool OnMessage(IMessageSinkMessage message) - { - var testStarted = message as ITestStarting; - if (testStarted != null) - { - OnTestStarted(testStarted); - } - - var testFailed = message as ITestFailed; - if (testFailed != null) - { - OnTestFailed(testFailed); - } - - var testPassed = message as ITestPassed; - if (testPassed != null) - { - OnTestPassed(testPassed); - } - - var testSkipped = message as ITestSkipped; - if (testSkipped != null) - { - OnTestSkipped(testSkipped); - } - - if (message is ITestAssemblyFinished) - { - Finished.Set(); - } - - return ShouldContinue; - } - - protected virtual bool ShouldContinue => true; - - protected abstract void OnTestStarted(ITestStarting testStarted); - protected abstract void OnTestFailed(ITestFailed testFailed); - protected abstract void OnTestPassed(ITestPassed testPassed); - protected abstract void OnTestSkipped(ITestSkipped testSkipped); - } -} diff --git a/xunit.runner.worker/MessageSinks/DiagnosticSink.cs b/xunit.runner.worker/MessageSinks/DiagnosticSink.cs deleted file mode 100644 index a34958d..0000000 --- a/xunit.runner.worker/MessageSinks/DiagnosticSink.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Xunit.Abstractions; - -namespace Xunit.Runner.Worker.MessageSinks -{ - internal class DiagnosticSink : BaseMessageSink - { - protected override bool OnMessage(IMessageSinkMessage message) - { - return true; - } - } -} diff --git a/xunit.runner.worker/Program.cs b/xunit.runner.worker/Program.cs deleted file mode 100644 index dd3c290..0000000 --- a/xunit.runner.worker/Program.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading.Tasks; - -namespace Xunit.Runner.Worker -{ - public static class Program - { - private const int ExitSuccess = 0; - private const int ExitError = 1; - - public static int Main(string[] args) - { - if (args.Length < 2) - { - Usage(); - return ExitError; - } - - var pipeName = args[0]; - var parentPid = Int32.Parse(args[1]); - var process = Process.GetProcessById(parentPid); - if (process == null) - { - Console.WriteLine($"Invalid parent pid {parentPid}"); - return ExitError; - } - - Task.WaitAny( - Task.Run(() => process.WaitForExit()), - Task.Run(() => new Listener(pipeName).Go())); - - return ExitSuccess; - } - - private static void Usage() - { - Console.WriteLine("xunit.runner.worker [pipe name] [action] [assembly path]"); - Console.WriteLine("\tpipe name: Name of the pipe this worker should communicate on"); - Console.WriteLine("\taction: Action performed by the worker (run or discover tests)"); - Console.WriteLine("\tassembly path: Path of assembly to perform the action against"); - } - } -} diff --git a/xunit.runner.worker/Properties/AssemblyInfo.cs b/xunit.runner.worker/Properties/AssemblyInfo.cs deleted file mode 100644 index 5aa7b35..0000000 --- a/xunit.runner.worker/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("xunit.runner.worker")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("xunit.runner.worker")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("9df97a2b-0eb5-4b12-9f81-69dfac979814")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/xunit.runner.worker/RunUtil.cs b/xunit.runner.worker/RunUtil.cs deleted file mode 100644 index aab3b60..0000000 --- a/xunit.runner.worker/RunUtil.cs +++ /dev/null @@ -1,160 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using Xunit.Runner.Data; -using Xunit.Abstractions; -using Xunit.Runner.Worker.MessageSinks; - -namespace Xunit.Runner.Worker -{ - internal sealed class RunUtil : XunitUtil - { - private sealed class TestRunSink : BaseTestRunSink - { - private readonly ClientWriter _writer; - - public TestRunSink(ClientWriter writer) - { - _writer = writer; - } - - protected override bool ShouldContinue => _writer.IsConnected; - - private void Process(string displayName, string uniqueID, TestState state, string output = "") - { - System.Diagnostics.Trace.WriteLine($"{state} - {displayName}"); - var result = new TestResultData(displayName, uniqueID, state, output); - - _writer.Write(TestDataKind.Value); - _writer.Write(result); - } - - protected override void OnTestStarted(ITestStarting testStarted) - { - Process(testStarted.TestCase.DisplayName, testStarted.TestCase.UniqueID, TestState.Running); - } - - protected override void OnTestFailed(ITestFailed testFailed) - { - var displayName = testFailed.TestCase.DisplayName; - var builder = new StringBuilder(); - builder.AppendLine($"{displayName} FAILED:"); - for (int i = 0; i < testFailed.ExceptionTypes.Length; i++) - { - builder.AppendLine($"\tException type: '{testFailed.ExceptionTypes[i]}', number: '{i}', parent: '{testFailed.ExceptionParentIndices[i]}'"); - builder.AppendLine($"\tException message:"); - builder.AppendLine(testFailed.Messages[i]); - builder.AppendLine($"\tException stacktrace"); - builder.AppendLine(testFailed.StackTraces[i]); - } - - builder.AppendLine(); - - Process(testFailed.TestCase.DisplayName, testFailed.TestCase.UniqueID, TestState.Failed, builder.ToString()); - } - - protected override void OnTestPassed(ITestPassed testPassed) - { - Process(testPassed.TestCase.DisplayName, testPassed.TestCase.UniqueID, TestState.Passed); - } - - protected override void OnTestSkipped(ITestSkipped testSkipped) - { - Process(testSkipped.TestCase.DisplayName, testSkipped.TestCase.UniqueID, TestState.Skipped); - } - } - - private sealed class TestDiscoverySink : BaseTestDiscoverySink - { - private readonly HashSet _testCaseUniqueIDSet; - private readonly List _testCaseList; - - internal TestDiscoverySink(HashSet testCaseUniqueIDSet, List testCaseList) - { - _testCaseUniqueIDSet = testCaseUniqueIDSet; - _testCaseList = testCaseList; - } - - protected override void OnTestDiscovered(ITestCaseDiscoveryMessage testCaseDiscovered) - { - var testCase = testCaseDiscovered.TestCase; - if (_testCaseUniqueIDSet.Contains(testCase.UniqueID)) - { - _testCaseList.Add(testCaseDiscovered.TestCase); - } - } - } - - /// - /// Read out the set of test case unique IDs to run. - /// - private static List ReadTestCaseUniqueIDs(Stream stream) - { - using (var reader = new ClientReader(stream)) - { - var list = new List(); - while (reader.ReadKind() == TestDataKind.Value) - { - list.Add(reader.ReadString()); - } - - return list; - } - } - - private static List GetTestCaseList(XunitFrontController xunit, TestAssemblyConfiguration configuration, HashSet testCaseNameSet) - { - var testCaseList = new List(); - - using (var sink = new TestDiscoverySink(testCaseNameSet, testCaseList)) - { - xunit.Find(includeSourceInformation: false, messageSink: sink, - discoveryOptions: TestFrameworkOptions.ForDiscovery(configuration)); - - sink.Finished.WaitOne(); - } - - return testCaseList; - } - - internal static void RunAll(string assemblyFileName, Stream stream) - { - Go(assemblyFileName, stream, AppDomainSupport.IfAvailable, - (xunit, configuration, writer) => - { - using (var sink = new TestRunSink(writer)) - { - xunit.RunAll(sink, - discoveryOptions: TestFrameworkOptions.ForDiscovery(configuration), - executionOptions: TestFrameworkOptions.ForExecution(configuration)); - - sink.Finished.WaitOne(); - - writer.Write(TestDataKind.EndOfData); - } - }); - } - - internal static void RunSpecific(string assemblyFileName, Stream stream) - { - var testCaseUniqueIDSet = new HashSet(ReadTestCaseUniqueIDs(stream), StringComparer.Ordinal); - - Go(assemblyFileName, stream, AppDomainSupport.IfAvailable, - (xunit, configuration, writer) => - { - using (var sink = new TestRunSink(writer)) - { - var testCaseList = GetTestCaseList(xunit, configuration, testCaseUniqueIDSet); - - xunit.RunTests(testCaseList, sink, - executionOptions: TestFrameworkOptions.ForExecution(configuration)); - - sink.Finished.WaitOne(); - - writer.Write(TestDataKind.EndOfData); - } - }); - } - } -} diff --git a/xunit.runner.worker/XunitUtil.cs b/xunit.runner.worker/XunitUtil.cs deleted file mode 100644 index f3e8e74..0000000 --- a/xunit.runner.worker/XunitUtil.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.IO; -using Xunit.Runner.Data; - -namespace Xunit.Runner.Worker -{ - internal abstract class XunitUtil - { - protected static void Go(string assemblyFileName, Stream stream, AppDomainSupport appDomainSupport, - Action action) - { - using (AssemblyHelper.SubscribeResolve()) - using (var xunit = new XunitFrontController(appDomainSupport, assemblyFileName, shadowCopy: false)) - using (var writer = new ClientWriter(stream)) - { - var configuration = ConfigReader.Load(assemblyFileName); - action(xunit, configuration, writer); - } - } - } -} diff --git a/xunit.runner.worker/packages.config b/xunit.runner.worker/packages.config deleted file mode 100644 index f3e206d..0000000 --- a/xunit.runner.worker/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/xunit.runner.worker/xunit.runner.worker.csproj b/xunit.runner.worker/xunit.runner.worker.csproj deleted file mode 100644 index 5f90231..0000000 --- a/xunit.runner.worker/xunit.runner.worker.csproj +++ /dev/null @@ -1,86 +0,0 @@ - - - - - Debug - AnyCPU - {9DF97A2B-0EB5-4B12-9F81-69DFAC979814} - Exe - Properties - Xunit.Runner.Worker - xunit.runner.worker - v4.6 - 512 - true - - - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - ..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll - - - ..\packages\xunit.runner.utility.2.1.0\lib\net35\xunit.runner.utility.desktop.dll - True - - - - - - - - - - - - - - - - - - - - - - {a1f579f4-443e-4f64-bc55-998ab86ff293} - xunit.runner.data - - - - - \ No newline at end of file diff --git a/xunit.runner.wpf.sln b/xunit.runner.wpf.sln index 294990a..181b20f 100644 --- a/xunit.runner.wpf.sln +++ b/xunit.runner.wpf.sln @@ -1,40 +1,31 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26430.4 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30011.22 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xunit.runner.wpf", "xunit.runner.wpf\xunit.runner.wpf.csproj", "{34FB519C-FB49-4B31-ACA2-7F7879311BCF}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xunit.runner.wpf", "xunit.runner.wpf\xunit.runner.wpf.csproj", "{4120F4AC-1B24-4B7C-86A9-0A3C75E526CD}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleTestAssembly", "SampleTestAssembly\SampleTestAssembly.csproj", "{BDAFB5DD-FFB3-4A94-A312-DFB080010846}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xunit.runner.worker", "xunit.runner.worker\xunit.runner.worker.csproj", "{9DF97A2B-0EB5-4B12-9F81-69DFAC979814}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xunit.runner.data", "xunit.runner.data\xunit.runner.data.csproj", "{A1F579F4-443E-4F64-BC55-998AB86FF293}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {34FB519C-FB49-4B31-ACA2-7F7879311BCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {34FB519C-FB49-4B31-ACA2-7F7879311BCF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {34FB519C-FB49-4B31-ACA2-7F7879311BCF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {34FB519C-FB49-4B31-ACA2-7F7879311BCF}.Release|Any CPU.Build.0 = Release|Any CPU + {4120F4AC-1B24-4B7C-86A9-0A3C75E526CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4120F4AC-1B24-4B7C-86A9-0A3C75E526CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4120F4AC-1B24-4B7C-86A9-0A3C75E526CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4120F4AC-1B24-4B7C-86A9-0A3C75E526CD}.Release|Any CPU.Build.0 = Release|Any CPU {BDAFB5DD-FFB3-4A94-A312-DFB080010846}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BDAFB5DD-FFB3-4A94-A312-DFB080010846}.Debug|Any CPU.Build.0 = Debug|Any CPU {BDAFB5DD-FFB3-4A94-A312-DFB080010846}.Release|Any CPU.ActiveCfg = Release|Any CPU {BDAFB5DD-FFB3-4A94-A312-DFB080010846}.Release|Any CPU.Build.0 = Release|Any CPU - {9DF97A2B-0EB5-4B12-9F81-69DFAC979814}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9DF97A2B-0EB5-4B12-9F81-69DFAC979814}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9DF97A2B-0EB5-4B12-9F81-69DFAC979814}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9DF97A2B-0EB5-4B12-9F81-69DFAC979814}.Release|Any CPU.Build.0 = Release|Any CPU - {A1F579F4-443E-4F64-BC55-998AB86FF293}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A1F579F4-443E-4F64-BC55-998AB86FF293}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A1F579F4-443E-4F64-BC55-998AB86FF293}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A1F579F4-443E-4F64-BC55-998AB86FF293}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F8EBD009-FF10-49B8-91DB-F90A51C529BA} + EndGlobalSection EndGlobal diff --git a/xunit.runner.wpf/App.config b/xunit.runner.wpf/App.config index 2d2a12d..193aecc 100644 --- a/xunit.runner.wpf/App.config +++ b/xunit.runner.wpf/App.config @@ -1,6 +1,6 @@ - + - + - + \ No newline at end of file diff --git a/xunit.runner.wpf/App.xaml b/xunit.runner.wpf/App.xaml index dc724ff..8a42ee1 100644 --- a/xunit.runner.wpf/App.xaml +++ b/xunit.runner.wpf/App.xaml @@ -1,14 +1,7 @@ - - - - + + + + + + \ No newline at end of file diff --git a/xunit.runner.wpf/App.xaml.cs b/xunit.runner.wpf/App.xaml.cs index 5d8df6e..ba26f22 100644 --- a/xunit.runner.wpf/App.xaml.cs +++ b/xunit.runner.wpf/App.xaml.cs @@ -1,8 +1,17 @@ -using System.Windows; +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; namespace Xunit.Runner.Wpf { - public partial class App : Application - { - } + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } } diff --git a/xunit.runner.wpf/AssemblyRunner2.cs b/xunit.runner.wpf/AssemblyRunner2.cs new file mode 100644 index 0000000..30a1d39 --- /dev/null +++ b/xunit.runner.wpf/AssemblyRunner2.cs @@ -0,0 +1,384 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit.Abstractions; +using Xunit.Runner.Wpf; +using Xunit.Sdk; + +namespace Xunit.Runners +{ + /// + /// A class which makes it simpler for casual runner authors to find and run tests and get results. + /// Adapted from: https://github.com/xunit/xunit/blob/main/src/xunit.v3.runner.utility/Runners/AssemblyRunner.cs + /// + public class AssemblyRunner2 : LongLivedMarshalByRefObject, IDisposable, IMessageSinkWithTypes + { + static readonly Dictionary MessageTypeNames; + + private TaskCompletionSource tcs { get; set; } + private CancellationToken cancellationToken { get; set; } + + volatile bool cancelled; + bool disposed; + readonly TestAssemblyConfiguration configuration; + readonly IFrontController controller; + + string assemblyFileName { get; set; } + int testCasesDiscovered; + readonly List testCasesToRun = new List(); + + static AssemblyRunner2() + { + MessageTypeNames = new Dictionary(); + + AddMessageTypeName(); + AddMessageTypeName(); + AddMessageTypeName(); + AddMessageTypeName(); + AddMessageTypeName(); + AddMessageTypeName(); + AddMessageTypeName(); + AddMessageTypeName(); + AddMessageTypeName(); + AddMessageTypeName(); + AddMessageTypeName(); + AddMessageTypeName(); + AddMessageTypeName(); + AddMessageTypeName(); + AddMessageTypeName(); + AddMessageTypeName(); + AddMessageTypeName(); + } + + AssemblyRunner2(AppDomainSupport appDomainSupport, + string assemblyFileName, + TaskCompletionSource tcs, + CancellationToken cancellationToken, + string configFileName = null, + bool shadowCopy = true, + string shadowCopyFolder = null) + { + this.tcs = tcs; + this.cancellationToken = cancellationToken; + this.assemblyFileName = assemblyFileName; + controller = new XunitFrontController(appDomainSupport, assemblyFileName, configFileName, shadowCopy, shadowCopyFolder, diagnosticMessageSink: MessageSinkAdapter.Wrap(this)); + configuration = ConfigReader.Load(assemblyFileName, configFileName); + } + + /// + /// Set to get notification of diagnostic messages. + /// + public Action OnDiagnosticMessage { get; set; } + + /// + /// Set to get notification of when test discovery is complete. + /// + public Action OnDiscoveryComplete { get; set; } + + /// + /// Set to get notification of error messages (unhandled exceptions outside of tests). + /// + public Action OnErrorMessage { get; set; } + + /// + /// Set to get notification of when test execution is complete. + /// + public Action OnExecutionComplete { get; set; } + + /// + /// Set to get notification of failed tests. + /// + public Action OnTestFailed { get; set; } + + /// + /// Set to get notification of finished tests (regardless of outcome). + /// + public Action OnTestFinished { get; set; } + + /// + /// Set to get real-time notification of test output (for xUnit.net v2 tests only). + /// Note that output is captured and reported back to all the test completion Info>s + /// in addition to being sent to this Info>. + /// + public Action OnTestOutput { get; set; } + + /// + /// Set to get notification of passing tests. + /// + public Action OnTestPassed { get; set; } + + /// + /// Set to get notification of skipped tests. + /// + public Action OnTestSkipped { get; set; } + + /// + /// Set to get notification of when tests start running. + /// + public Action OnTestStarting { get; set; } + + /// + /// Set to be able to filter the test cases to decide which ones to run. If this is not set, + /// then all test cases will be run. + /// + public Func TestCaseFilter { get; set; } + + static void AddMessageTypeName() => MessageTypeNames.Add(typeof(T), typeof(T).FullName); + + /// + /// Call to request that the current run be cancelled. Note that cancellation may not be + /// instantaneous, and even after cancellation has been acknowledged, you can expect to + /// receive all the cleanup-related messages. + /// + public void Cancel() + { + cancelled = true; + } + + /// + public void Dispose() + { + if (disposed) + return; + + disposed = true; + + controller?.Dispose(); + } + + ITestFrameworkDiscoveryOptions GetDiscoveryOptions(bool? diagnosticMessages, TestMethodDisplay? methodDisplay, TestMethodDisplayOptions? methodDisplayOptions, bool? preEnumerateTheories, bool? internalDiagnosticMessages) + { + var discoveryOptions = TestFrameworkOptions.ForDiscovery(configuration); + discoveryOptions.SetSynchronousMessageReporting(true); + + if (diagnosticMessages.HasValue) + discoveryOptions.SetDiagnosticMessages(diagnosticMessages); + if (internalDiagnosticMessages.HasValue) + discoveryOptions.SetDiagnosticMessages(internalDiagnosticMessages); + if (methodDisplay.HasValue) + discoveryOptions.SetMethodDisplay(methodDisplay); + if (methodDisplayOptions.HasValue) + discoveryOptions.SetMethodDisplayOptions(methodDisplayOptions); + if (preEnumerateTheories.HasValue) + discoveryOptions.SetPreEnumerateTheories(preEnumerateTheories); + + return discoveryOptions; + } + + ITestFrameworkExecutionOptions GetExecutionOptions(bool? diagnosticMessages, bool? parallel, int? maxParallelThreads, bool? internalDiagnosticMessages) + { + var executionOptions = TestFrameworkOptions.ForExecution(configuration); + executionOptions.SetSynchronousMessageReporting(true); + + if (diagnosticMessages.HasValue) + executionOptions.SetDiagnosticMessages(diagnosticMessages); + if (internalDiagnosticMessages.HasValue) + executionOptions.SetDiagnosticMessages(internalDiagnosticMessages); + if (parallel.HasValue) + executionOptions.SetDisableParallelization(!parallel.GetValueOrDefault()); + if (maxParallelThreads.HasValue) + executionOptions.SetMaxParallelThreads(maxParallelThreads); + + return executionOptions; + } + + /// + /// Starts running tests from a single type (if provided) or the whole assembly (if not). This call returns + /// immediately, and status results are dispatched to the Info>s on this class. Callers can check + /// to find out the current status. + /// + /// The (optional) type name of the single test class to run + /// Set to true to enable diagnostic messages; set to false to disable them. + /// By default, uses the value from the assembly configuration file. + /// Set to choose the default display name style for test methods. + /// By default, uses the value from the assembly configuration file. (This parameter is ignored for xUnit.net v1 tests.) + /// Set to choose the default display name style options for test methods. + /// By default, uses the value from the assembly configuration file. (This parameter is ignored for xUnit.net v1 tests.) + /// Set to true to pre-enumerate individual theory tests; set to false to use + /// a single test case for the theory. By default, uses the value from the assembly configuration file. (This parameter is ignored + /// for xUnit.net v1 tests.) + /// Set to true to run test collections in parallel; set to false to run them sequentially. + /// By default, uses the value from the assembly configuration file. (This parameter is ignored for xUnit.net v1 tests.) + /// Set to 0 to use unlimited threads; set to any other positive integer to limit to an exact number + /// of threads. By default, uses the value from the assembly configuration file. (This parameter is ignored for xUnit.net v1 tests.) + /// Set to true to enable internal diagnostic messages; set to false to disable them. + /// By default, uses the value from the assembly configuration file. + public void Discover( + string typeName = null, + bool? diagnosticMessages = null, + TestMethodDisplay? methodDisplay = null, + TestMethodDisplayOptions? methodDisplayOptions = null, + bool? preEnumerateTheories = null, + bool? internalDiagnosticMessages = null) + { + + + cancelled = false; + testCasesDiscovered = 0; + testCasesToRun.Clear(); + + XunitWorkerThread.QueueUserWorkItem(() => + { + var discoveryOptions = GetDiscoveryOptions(diagnosticMessages, methodDisplay, methodDisplayOptions, preEnumerateTheories, internalDiagnosticMessages); + if (typeName != null) + controller.Find(typeName, false, this, discoveryOptions); + else + controller.Find(false, this, discoveryOptions); + + if (cancelled) + { + // Synthesize the execution complete message, since we're not going to run at all + if (OnExecutionComplete != null) + OnExecutionComplete(ExecutionCompleteInfo.Empty); + return; + } + + }); + } + + public void Run(List cases, + bool? diagnosticMessages = null, + bool? parallel = null, + int? maxParallelThreads = null, + bool? internalDiagnosticMessages = null) + { + cancelled = false; + testCasesDiscovered = cases.Count(); + testCasesToRun.Clear(); + testCasesToRun.AddRange(cases); + + XunitWorkerThread.QueueUserWorkItem(() => + { + + if (cancelled) + { + // Synthesize the execution complete message, since we're not going to run at all + if (OnExecutionComplete != null) + OnExecutionComplete(ExecutionCompleteInfo.Empty); + return; + } + + var executionOptions = GetExecutionOptions(diagnosticMessages, parallel, maxParallelThreads, internalDiagnosticMessages); + + controller.RunTests(testCasesToRun, this, executionOptions); + }); + } + + + /// + /// Creates an assembly runner that discovers and run tests in a separate app domain. + /// + /// The test assembly. + /// The test assembly configuration file. + /// If set to true, runs tests in a shadow copied app domain, which allows + /// tests to be discovered and run without locking assembly files on disk. + /// The path on disk to use for shadow copying; if null, a folder + /// will be automatically (randomly) generated + //public static AssemblyRunner2 WithAppDomain(string assemblyFileName, + // string configFileName = null, + // bool shadowCopy = true, + // string shadowCopyFolder = null) + //{ + // //Guard.ArgumentValid(nameof(shadowCopyFolder), "Cannot set shadowCopyFolder if shadowCopy is false", shadowCopy == true || shadowCopyFolder == null); + + // return new AssemblyRunner2(AppDomainSupport.Required, assemblyFileName, configFileName, shadowCopy, shadowCopyFolder); + //} + + + /// + /// Creates an assembly runner that discovers and runs tests without a separate app domain. + /// + /// The test assembly. + public static AssemblyRunner2 WithoutAppDomain(string assemblyFileName, TaskCompletionSource tcs, CancellationToken cancellationToken) + { + return new AssemblyRunner2(AppDomainSupport.Denied, assemblyFileName, tcs, cancellationToken); + } + + bool DispatchMessage(IMessageSinkMessage message, HashSet messageTypes, Action handler) + where TMessage : class + { + + if (messageTypes == null || !MessageTypeNames.TryGetValue(typeof(TMessage), out var typeName) || !messageTypes.Contains(typeName)) + return false; + + handler((TMessage)message); + return true; + } + + bool IMessageSinkWithTypes.OnMessageWithTypes(IMessageSinkMessage message, HashSet messageTypes) + { + if (cancellationToken.IsCancellationRequested) + { + cancelled = true; + } + + if (DispatchMessage(message, messageTypes, testDiscovered => + { + ++testCasesDiscovered; + if (TestCaseFilter == null || TestCaseFilter(testDiscovered.TestCase)) + testCasesToRun.Add(testDiscovered.TestCase); + })) + return !cancelled; + + if (DispatchMessage(message, messageTypes, discoveryComplete => + { + OnDiscoveryComplete?.Invoke(new TestDiscoveryInfo(testCasesToRun, assemblyFileName)); + tcs.TrySetResult(""); + })) + return !cancelled; + + if (DispatchMessage(message, messageTypes, assemblyFinished => + { + + OnExecutionComplete?.Invoke(new ExecutionCompleteInfo(assemblyFinished.TestsRun, assemblyFinished.TestsFailed, assemblyFinished.TestsSkipped, assemblyFinished.ExecutionTime)); + tcs.TrySetResult(""); + })) + return !cancelled; + + if (OnDiagnosticMessage != null) + if (DispatchMessage(message, messageTypes, m => OnDiagnosticMessage(new DiagnosticMessageInfo(m.Message)))) + return !cancelled; + if (OnTestFailed != null) + if (DispatchMessage(message, messageTypes, m => OnTestFailed(new TestFailedInfo(m.TestClass.Class.Name, m.TestMethod.Method.Name, m.TestCase.Traits, m.Test.DisplayName, m.TestCollection.DisplayName, m.ExecutionTime, m.Output, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault())))) + return !cancelled; + if (OnTestFinished != null) + if (DispatchMessage(message, messageTypes, m => OnTestFinished(new TestFinishedInfo(m.TestClass.Class.Name, m.TestMethod.Method.Name, m.TestCase.Traits, m.Test.DisplayName, m.TestCollection.DisplayName, m.ExecutionTime, m.Output)))) + return !cancelled; + if (OnTestOutput != null) + if (DispatchMessage(message, messageTypes, m => OnTestOutput(new TestOutputInfo(m.TestClass.Class.Name, m.TestMethod.Method.Name, m.TestCase.Traits, m.Test.DisplayName, m.TestCollection.DisplayName, m.Output)))) + return !cancelled; + if (OnTestPassed != null) + if (DispatchMessage(message, messageTypes, m => OnTestPassed(new TestPassedInfo(m.TestClass.Class.Name, m.TestMethod.Method.Name, m.TestCase.Traits, m.Test.DisplayName, m.TestCollection.DisplayName, m.ExecutionTime, m.Output)))) + return !cancelled; + if (OnTestSkipped != null) + if (DispatchMessage(message, messageTypes, m => OnTestSkipped(new TestSkippedInfo(m.TestClass.Class.Name, m.TestMethod.Method.Name, m.TestCase.Traits, m.Test.DisplayName, m.TestCollection.DisplayName, m.Reason)))) + return !cancelled; + if (OnTestStarting != null) + if (DispatchMessage(message, messageTypes, m => OnTestStarting(new TestStartingInfo(m.TestClass.Class.Name, m.TestMethod.Method.Name, m.TestCase.Traits, m.Test.DisplayName, m.TestCollection.DisplayName)))) + return !cancelled; + + if (OnErrorMessage != null) + { + if (DispatchMessage(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.CatastrophicError, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault())))) + return !cancelled; + if (DispatchMessage(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestAssemblyCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault())))) + return !cancelled; + if (DispatchMessage(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestCaseCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault())))) + return !cancelled; + if (DispatchMessage(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestClassCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault())))) + return !cancelled; + if (DispatchMessage(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault())))) + return !cancelled; + if (DispatchMessage(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestCollectionCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault())))) + return !cancelled; + if (DispatchMessage(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestMethodCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault())))) + return !cancelled; + } + + return !cancelled; + } + } +} \ No newline at end of file diff --git a/xunit.runner.wpf/Converters/TestStateConverter.cs b/xunit.runner.wpf/Converters/TestStateConverter.cs index 46f1cc3..95b03cc 100644 --- a/xunit.runner.wpf/Converters/TestStateConverter.cs +++ b/xunit.runner.wpf/Converters/TestStateConverter.cs @@ -3,80 +3,80 @@ using System.Globalization; using System.Windows.Data; using System.Windows.Media; using System.Windows.Media.Imaging; -using Xunit.Runner.Data; namespace Xunit.Runner.Wpf.Converters { - public class TestStateConverter : IValueConverter + public class TestStateConverter : IValueConverter + { + private static ImageSource runningSource; + private static ImageSource failedSource; + private static ImageSource passedSource; + private static ImageSource skippedSource; + + private static SolidColorBrush skippedBrush = new SolidColorBrush(Color.FromRgb(0xEB, 0xCA, 0x00)); + + static TestStateConverter() { - private static ImageSource runningSource; - private static ImageSource failedSource; - private static ImageSource passedSource; - private static ImageSource skippedSource; - - private static SolidColorBrush skippedBrush = new SolidColorBrush(Color.FromRgb(0xEB, 0xCA, 0x00)); - - static TestStateConverter() - { - runningSource = LoadResourceImage("Running_small.png"); - failedSource = LoadResourceImage("Failed_small.png"); - passedSource = LoadResourceImage("Passed_small.png"); - skippedSource = LoadResourceImage("Skipped_small.png"); - } - - private static BitmapImage LoadResourceImage(string resourceName) - { - var image = new BitmapImage(); - image.BeginInit(); - image.UriSource = new Uri("pack://application:,,,/xunit.runner.wpf;component/Artwork/" + resourceName); - image.EndInit(); - return image; - } - - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - var state = (TestState)value; - if (targetType == typeof(Brush)) - { - switch (state) - { - case TestState.Running: - return Brushes.Blue; - case TestState.Failed: - return Brushes.Red; - case TestState.Passed: - return Brushes.Green; - case TestState.Skipped: - return skippedBrush; - default: - return Brushes.Gray; - } - } - else if (targetType == typeof(ImageSource)) - { - switch (state) - { - case TestState.Running: - return runningSource; - case TestState.Failed: - return failedSource; - case TestState.Passed: - return passedSource; - case TestState.Skipped: - return skippedSource; - default: - return null; - } - } - else - { - throw new NotSupportedException(); - } - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } + runningSource = LoadResourceImage("Running_small.png"); + failedSource = LoadResourceImage("Failed_small.png"); + passedSource = LoadResourceImage("Passed_small.png"); + skippedSource = LoadResourceImage("Skipped_small.png"); } + + private static BitmapImage LoadResourceImage(string resourceName) + { + var image = new BitmapImage(); + image.BeginInit(); + image.UriSource = new Uri("pack://application:,,,/xunit.runner.wpf;component/Artwork/" + resourceName); + image.EndInit(); + return image; + } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var state = (TestState)value; + if (targetType == typeof(Brush)) + { + switch (state) + { + case TestState.Running: + return Brushes.Blue; + case TestState.Failed: + return Brushes.Red; + case TestState.Passed: + return Brushes.Green; + case TestState.Skipped: + return skippedBrush; + default: + return Brushes.Gray; + } + } + else if (targetType == typeof(ImageSource)) + { + switch (state) + { + case TestState.Running: + return runningSource; + case TestState.Failed: + return failedSource; + case TestState.Passed: + return passedSource; + case TestState.Skipped: + return skippedSource; + default: + return null; + } + } + else + { + throw new NotSupportedException(); + } + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } + } diff --git a/xunit.runner.wpf/EnumAppDomains.cs b/xunit.runner.wpf/EnumAppDomains.cs new file mode 100644 index 0000000..b2e6b28 --- /dev/null +++ b/xunit.runner.wpf/EnumAppDomains.cs @@ -0,0 +1,50 @@ +using mscoree; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Xunit.Runner.Wpf +{ + public static class AppDomains + { + public static IEnumerable EnumAppDomains() + { + IntPtr enumHandle = IntPtr.Zero; + ICorRuntimeHost host = null; + + try + { + host = GetCorRuntimeHost(); + host.EnumDomains(out enumHandle); + object domain = null; + + host.NextDomain(enumHandle, out domain); + while (domain != null) + { + yield return (AppDomain)domain; + host.NextDomain(enumHandle, out domain); + } + } + finally + { + if (host != null) + { + if (enumHandle != IntPtr.Zero) + { + host.CloseEnum(enumHandle); + } + + Marshal.ReleaseComObject(host); + } + } + } + + private static ICorRuntimeHost GetCorRuntimeHost() + { + return (ICorRuntimeHost)Activator.CreateInstance(Marshal.GetTypeFromCLSID(new Guid("CB2F6723-AB3A-11D2-9C40-00C04FA30A3E"))); + } + } +} diff --git a/xunit.runner.wpf/Extensions.FuncComparer.cs b/xunit.runner.wpf/Extensions.FuncComparer.cs deleted file mode 100644 index e24fb96..0000000 --- a/xunit.runner.wpf/Extensions.FuncComparer.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Xunit.Runner.Wpf -{ - public static partial class Extensions - { - private class FuncComparer : IComparer - { - private readonly Func _comparison; - - public FuncComparer(Func comparison) - { - _comparison = comparison; - } - - public int Compare(T x, T y) => _comparison(x, y); - } - } -} diff --git a/xunit.runner.wpf/Extensions.cs b/xunit.runner.wpf/Extensions.cs index 38db713..26e3078 100644 --- a/xunit.runner.wpf/Extensions.cs +++ b/xunit.runner.wpf/Extensions.cs @@ -4,97 +4,111 @@ using System.Collections.ObjectModel; namespace Xunit.Runner.Wpf { - public static partial class Extensions + public static partial class Extensions + { + public static void AddRange(this ICollection list, IEnumerable items) where TEnumerable : TList { - public static void AddRange(this ICollection list, IEnumerable items) where TEnumerable : TList - { - foreach (var i in items) - { - list.Add(i); - } - } - - public static void AddRange(this ObservableCollection list, IEnumerable items) where TEnumerable : TList - { - foreach (var i in items) - { - list.Add(i); - } - } - - public static int BinarySearch(this ObservableCollection collection, int index, int length, TValue value, IComparer comparer, Func selector) - { - comparer = comparer ?? Comparer.Default; - - var low = index; - var high = (index + length) - 1; - - while (low <= high) - { - var mid = low + ((high - low) / 2); - var comp = comparer.Compare(selector(collection[mid]), value); - - if (comp == 0) - { - return mid; - } - - if (comp < 0) - { - low = mid + 1; - } - else - { - high = mid - 1; - } - } - - return ~low; - } - - public static int BinarySearch(this ObservableCollection collection, TValue value, IComparer comparer, Func selector) - { - return collection.BinarySearch(0, collection.Count, value, comparer, selector); - } - - public static int BinarySearch(this ObservableCollection collection, int index, int length, TValue value, Func comparison, Func selector) - { - return collection.BinarySearch(index, length, value, new FuncComparer(comparison), selector); - } - - public static int BinarySearch(this ObservableCollection collection, TValue value, Func comparison, Func selector) - { - return collection.BinarySearch(0, collection.Count, value, new FuncComparer(comparison), selector); - } - - public static int BinarySearch(this ObservableCollection collection, TValue value, Func selector) - { - return collection.BinarySearch(0, collection.Count, value, comparer: null, selector: selector); - } - - public static int BinarySearch(this ObservableCollection collection, int index, int length, T value, IComparer comparer) - { - return collection.BinarySearch(index, length, value, comparer, x => x); - } - - public static int BinarySearch(this ObservableCollection collection, T value, IComparer comparer) - { - return collection.BinarySearch(0, collection.Count, value, comparer, x => x); - } - - public static int BinarySearch(this ObservableCollection collection, T value) - { - return collection.BinarySearch(0, collection.Count, value, Comparer.Default, x => x); - } - - public static int BinarySearch(this ObservableCollection collection, int index, int length, T value, Func comparison) - { - return collection.BinarySearch(index, length, value, new FuncComparer(comparison), x => x); - } - - public static int BinarySearch(this ObservableCollection collection, T value, Func comparison) - { - return collection.BinarySearch(0, collection.Count, value, new FuncComparer(comparison), x => x); - } + foreach (var i in items) + { + list.Add(i); + } } + + public static void AddRange(this ObservableCollection list, IEnumerable items) where TEnumerable : TList + { + foreach (var i in items) + { + list.Add(i); + } + } + + public static int BinarySearch(this ObservableCollection collection, int index, int length, TValue value, IComparer comparer, Func selector) + { + comparer = comparer ?? Comparer.Default; + + var low = index; + var high = (index + length) - 1; + + while (low <= high) + { + var mid = low + ((high - low) / 2); + var comp = comparer.Compare(selector(collection[mid]), value); + + if (comp == 0) + { + return mid; + } + + if (comp < 0) + { + low = mid + 1; + } + else + { + high = mid - 1; + } + } + + return ~low; + } + + public static int BinarySearch(this ObservableCollection collection, TValue value, IComparer comparer, Func selector) + { + return collection.BinarySearch(0, collection.Count, value, comparer, selector); + } + + public static int BinarySearch(this ObservableCollection collection, int index, int length, TValue value, Func comparison, Func selector) + { + return collection.BinarySearch(index, length, value, new FuncComparer(comparison), selector); + } + + public static int BinarySearch(this ObservableCollection collection, TValue value, Func comparison, Func selector) + { + return collection.BinarySearch(0, collection.Count, value, new FuncComparer(comparison), selector); + } + + public static int BinarySearch(this ObservableCollection collection, TValue value, Func selector) + { + return collection.BinarySearch(0, collection.Count, value, comparer: null, selector: selector); + } + + public static int BinarySearch(this ObservableCollection collection, int index, int length, T value, IComparer comparer) + { + return collection.BinarySearch(index, length, value, comparer, x => x); + } + + public static int BinarySearch(this ObservableCollection collection, T value, IComparer comparer) + { + return collection.BinarySearch(0, collection.Count, value, comparer, x => x); + } + + public static int BinarySearch(this ObservableCollection collection, T value) + { + return collection.BinarySearch(0, collection.Count, value, Comparer.Default, x => x); + } + + public static int BinarySearch(this ObservableCollection collection, int index, int length, T value, Func comparison) + { + return collection.BinarySearch(index, length, value, new FuncComparer(comparison), x => x); + } + + public static int BinarySearch(this ObservableCollection collection, T value, Func comparison) + { + return collection.BinarySearch(0, collection.Count, value, new FuncComparer(comparison), x => x); + } + + private class FuncComparer : IComparer + { + private readonly Func _comparison; + + public FuncComparer(Func comparison) + { + _comparison = comparison; + } + + public int Compare(T x, T y) => _comparison(x, y); + } + } + + } diff --git a/xunit.runner.wpf/ICorRuntimeHost.cs b/xunit.runner.wpf/ICorRuntimeHost.cs new file mode 100644 index 0000000..27d6c7e --- /dev/null +++ b/xunit.runner.wpf/ICorRuntimeHost.cs @@ -0,0 +1,23 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace mscoree +{ + [CompilerGenerated] + [Guid("CB2F6722-AB3A-11D2-9C40-00C04FA30A3E")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [TypeIdentifier] + [ComImport] + [CLSCompliant(false)] + public interface ICorRuntimeHost + { + void _VtblGap1_11(); + + void EnumDomains(out IntPtr enumHandle); + + void NextDomain([In] IntPtr enumHandle, [MarshalAs(UnmanagedType.IUnknown)] out object appDomain); + + void CloseEnum([In] IntPtr enumHandle); + } +} \ No newline at end of file diff --git a/xunit.runner.wpf/ITestUtil.cs b/xunit.runner.wpf/ITestUtil.cs deleted file mode 100644 index 1cf8880..0000000 --- a/xunit.runner.wpf/ITestUtil.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Runner.Data; - -namespace Xunit.Runner.Wpf -{ - internal interface ITestUtil - { - /// - /// Discover the list of test cases which are available in the specified assembly. - /// - Task Discover( - string assebmlyFileName, - Action> testsDiscovered, - CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Begin a run of all unit tests for the given assembly. - /// - Task RunAll( - string assemblyFileName, - Action> testsFinished, - CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Begin a run of specific unit tests for the given assembly. - /// - Task RunSpecific( - string assemblyFileName, - ImmutableArray testCasesToRun, - Action> testsFinished, - CancellationToken cancellationToken = default(CancellationToken)); - } -} diff --git a/xunit.runner.wpf/Impl/RemoteTestUtil.BackgroundRunner.cs b/xunit.runner.wpf/Impl/RemoteTestUtil.BackgroundRunner.cs deleted file mode 100644 index 6e4907a..0000000 --- a/xunit.runner.wpf/Impl/RemoteTestUtil.BackgroundRunner.cs +++ /dev/null @@ -1,176 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Threading; -using Xunit.Runner.Data; - -namespace Xunit.Runner.Wpf.Impl -{ - internal partial class RemoteTestUtil - { - private sealed class BackgroundWriter - { - private readonly ClientWriter _writer; - private readonly ImmutableArray _data; - private readonly Action _writeValue; - private readonly CancellationToken _cancellationToken; - - internal BackgroundWriter(ClientWriter writer, ImmutableArray data, Action writeValue, CancellationToken cancellationToken) - { - _writer = writer; - _writeValue = writeValue; - _data = data; - _cancellationToken = cancellationToken; - } - - internal Task WriteAsync() - { - return Task.Run(() => GoOnBackground(), _cancellationToken); - } - - private void GoOnBackground() - { - foreach (var item in _data) - { - if (_cancellationToken.IsCancellationRequested) - { - break; - } - - _writer.Write(TestDataKind.Value); - _writeValue(_writer, item); - } - - _writer.Write(TestDataKind.EndOfData); - } - } - - /// - /// Utility for reading a collection of values from the given - /// value. - /// - /// - private sealed class BackgroundReader where T : class - { - private readonly ConcurrentQueue _queue; - private readonly ClientReader _reader; - private readonly Func _readValue; - - internal ClientReader Reader => _reader; - - internal BackgroundReader(ConcurrentQueue queue, ClientReader reader, Func readValue) - { - _queue = queue; - _reader = reader; - _readValue = readValue; - } - - internal Task ReadAsync(CancellationToken cancellationToken = default(CancellationToken)) - { - return Task.Run(() => GoOnBackground(cancellationToken), cancellationToken); - } - - /// - /// This will be called on a background thread to read the results of the test from the - /// named pipe client stream. - /// - /// - private void GoOnBackground(CancellationToken cancellationToken) - { - while (!cancellationToken.IsCancellationRequested) - { - try - { - var kind = _reader.ReadKind(); - if (kind != TestDataKind.Value) - { - break; - } - - var value = _readValue(_reader); - _queue.Enqueue(value); - } - catch - { - // TODO: Happens when the connection unexpectedly closes on us. Need to surface this - // to the user. - break; - } - } - - // Signal we are done - _queue.Enqueue(null); - } - } - - private sealed class BackgroundProducer where T : class - { - private const int MaxResultPerTick = 1000; - - private readonly Connection _connection; - private readonly ConcurrentQueue _queue; - private readonly DispatcherTimer _timer; - private readonly Action> _callback; - private readonly int _maxPerTick; - private readonly TaskCompletionSource _taskCompletionSource; - - internal Task Task => _taskCompletionSource.Task; - - internal BackgroundProducer( - Connection connection, - Dispatcher dispatcher, - ConcurrentQueue queue, - Action> callback, - int maxResultPerTick = MaxResultPerTick, - TimeSpan? interval = null) - { - _connection = connection; - _queue = queue; - _maxPerTick = maxResultPerTick; - _callback = callback; - _timer = new DispatcherTimer( - interval ?? TimeSpan.FromMilliseconds(500), - DispatcherPriority.Normal, - OnTimerTick, - dispatcher); - _taskCompletionSource = new TaskCompletionSource(); - } - - private void OnTimerTick(object sender, EventArgs e) - { - var i = 0; - var list = new List(); - var isDone = false; - T value; - while (i < _maxPerTick && _queue.TryDequeue(out value)) - { - if (value == null) - { - isDone = true; - break; - } - - list.Add(value); - } - - _callback(list); - - if (isDone) - { - try - { - _timer.Stop(); - _connection.Dispose(); - } - finally - { - _taskCompletionSource.SetResult(true); - } - } - } - } - } -} diff --git a/xunit.runner.wpf/Impl/RemoteTestUtil.Connection.cs b/xunit.runner.wpf/Impl/RemoteTestUtil.Connection.cs deleted file mode 100644 index 7b93849..0000000 --- a/xunit.runner.wpf/Impl/RemoteTestUtil.Connection.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.IO.Pipes; -using Xunit.Runner.Data; - -namespace Xunit.Runner.Wpf.Impl -{ - internal sealed partial class RemoteTestUtil : ITestUtil - { - private sealed class Connection : IDisposable - { - private NamedPipeClientStream _stream; - private ClientReader _reader; - - internal NamedPipeClientStream Stream => _stream; - - internal ClientReader Reader => _reader; - - internal Connection(NamedPipeClientStream stream) - { - _stream = stream; - _reader = new ClientReader(stream); - } - - internal void Dispose() - { - if (_stream == null) - { - return; - } - - try - { - _stream.WriteAsync(new byte[] { 0 }, 0, 1); - } - catch - { - // Signal to server we are done with the connection. Okay to fail because - // it means the server isn't listening anymore. - } - - _stream.Close(); - _stream = null; - } - - void IDisposable.Dispose() - { - Dispose(); - } - } - } -} diff --git a/xunit.runner.wpf/Impl/RemoteTestUtil.cs b/xunit.runner.wpf/Impl/RemoteTestUtil.cs deleted file mode 100644 index 0c97f1c..0000000 --- a/xunit.runner.wpf/Impl/RemoteTestUtil.cs +++ /dev/null @@ -1,153 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.IO.Pipes; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Threading; -using Xunit.Runner.Data; - -namespace Xunit.Runner.Wpf.Impl -{ - internal sealed partial class RemoteTestUtil : ITestUtil - { - private struct ProcessInfo - { - internal readonly string PipeName; - internal readonly Process Process; - - internal ProcessInfo(string pipeName, Process process) - { - PipeName = pipeName; - Process = process; - } - } - - private readonly Dispatcher _dispatcher; - private ProcessInfo? _processInfo; - - internal RemoteTestUtil(Dispatcher dispatcher) - { - _dispatcher = dispatcher; - _processInfo = StartWorkerProcess(); - } - - private async Task CreateConnection(string action, string argument, CancellationToken cancellationToken) - { - var pipeName = GetPipeName(); - - try - { - var stream = new NamedPipeClientStream(pipeName); - await stream.ConnectAsync(cancellationToken); - - var writer = new ClientWriter(stream); - writer.Write(action); - writer.Write(argument); - - return new Connection(stream); - } - catch - { - try - { - _processInfo?.Process.Kill(); - } - catch - { - // Inherent race condition here. Just need to make sure the process is - // dead as it can't even handle new connections. - } - - throw; - } - } - - private string GetPipeName() - { - var process = _processInfo?.Process; - if (process != null && !process.HasExited) - { - return _processInfo.Value.PipeName; - } - - _processInfo = StartWorkerProcess(); - return _processInfo.Value.PipeName; - } - - private static ProcessInfo StartWorkerProcess() - { - var pipeName = $"xunit.runner.wpf.pipe.{Guid.NewGuid()}"; - var processStartInfo = new ProcessStartInfo(); - processStartInfo.FileName = typeof(Xunit.Runner.Worker.Program).Assembly.Location; - processStartInfo.Arguments = $"{pipeName} {Process.GetCurrentProcess().Id}"; - processStartInfo.WindowStyle = ProcessWindowStyle.Hidden; - var process = Process.Start(processStartInfo); - return new ProcessInfo(pipeName, process); - } - - private void RecycleProcess() - { - var process = _processInfo?.Process; - if (process != null && !process.HasExited) - { - process.Kill(); - } - - _processInfo = StartWorkerProcess(); - } - - private async Task Discover(string assemblyPath, Action> callback, CancellationToken cancellationToken) - { - var connection = await CreateConnection(Constants.ActionDiscover, assemblyPath, cancellationToken); - await ProcessResultsCore(connection, r => r.ReadTestCaseData(), callback, cancellationToken); - - RecycleProcess(); - } - - private async Task RunCore(string actionName, string assemblyPath, ImmutableArray testCaseDisplayNames, Action> callback, CancellationToken cancellationToken) - { - var connection = await CreateConnection(actionName, assemblyPath, cancellationToken); - - if (!testCaseDisplayNames.IsDefaultOrEmpty) - { - var backgroundWriter = new BackgroundWriter(new ClientWriter(connection.Stream), testCaseDisplayNames, (w, s) => w.Write(s), cancellationToken); - await backgroundWriter.WriteAsync(); - } - - await ProcessResultsCore(connection, r => r.ReadTestResultData(), callback, cancellationToken); - } - - private async Task ProcessResultsCore(Connection connection, Func readValue, Action> callback, CancellationToken cancellationToken) - where T : class - { - var queue = new ConcurrentQueue(); - var backgroundReader = new BackgroundReader(queue, new ClientReader(connection.Stream), readValue); - var backgroundProducer = new BackgroundProducer(connection, _dispatcher, queue, callback); - - await backgroundReader.ReadAsync(cancellationToken); - await backgroundProducer.Task; - } - - #region ITestUtil - - Task ITestUtil.Discover(string assemblyFileName, Action> testsDiscovered, CancellationToken cancellationToken) - { - return Discover(assemblyFileName, testsDiscovered, cancellationToken); - } - - Task ITestUtil.RunAll(string assemblyFileName, Action> testsFinished, CancellationToken cancellationToken) - { - return RunCore(Constants.ActionRunAll, assemblyFileName, ImmutableArray.Empty, testsFinished, cancellationToken); - } - - Task ITestUtil.RunSpecific(string assemblyFileName, ImmutableArray testCases, Action> testsFinished, CancellationToken cancellationToken) - { - return RunCore(Constants.ActionRunSpecific, assemblyFileName, testCases, testsFinished, cancellationToken); - } - - #endregion - } -} diff --git a/xunit.runner.wpf/MainWindow.xaml b/xunit.runner.wpf/MainWindow.xaml index 878a482..3285738 100644 --- a/xunit.runner.wpf/MainWindow.xaml +++ b/xunit.runner.wpf/MainWindow.xaml @@ -1,375 +1,362 @@ - + + + + + + - - - - + + + + - - - - + + + - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - + - - - - - + - - - - + + + + + - + + + + + - - - - - - - - - + + + + + + + + + - - + + + - - - - - - + + + + + + -