11 Commits

Author SHA1 Message Date
Kevin Pilch-Bisson fd72605d9d Update to released xunit 2.1 2015-10-12 12:13:26 -07:00
Kevin Pilch-Bisson cb39c7af29 Merge pull request #27 from Pilchie/ui-tweaks
A few UI tweaks
2015-10-12 09:23:59 -07:00
Kevin Pilch-Bisson 6aa5a6c0cd Merge pull request #23 from Pilchie/oneproc
Couple of fixes
2015-10-12 09:21:31 -07:00
Jared Parsons 90643fbf8f Worker can process tasks in parallel 2015-10-12 09:20:44 -07:00
Jared Parsons 0d96b29a7a Allow opening of multiple assemblies at a time 2015-10-12 09:20:42 -07:00
Jared Parsons d861f4a6ee Made connection async
The connection to the worker process was sync on the UI thread which
could lead to hangs.  Made it an async operation instead.

This did require me to upgrade to the 4.6 framework to use the
NamedPipeClientStream::ConnectAsync method.  If that is a blocker I can
downgrade back to 4.5.2 and use Thread tricks to get a similar effect.
2015-10-12 09:20:41 -07:00
Jared Parsons 3f894e0e7c Correct debugging workflow
There is now a single worker process model for all requests.  Debugging
workflow is to attach to this process and debug there.

closes #20
2015-10-12 09:19:44 -07:00
Kevin Pilch-Bisson 4371a4e837 Merge pull request #26 from Pilchie/issue-25
Fix issue where running a specific test was treated as running all of them
2015-10-12 09:11:57 -07:00
Dustin Campbell 65adde358c A few UI tweaks
Notably, the images for passed, failed and skipped have been replaced with images from the Visual Studio Image Library.
2015-10-12 08:58:11 -07:00
Dustin Campbell ae5e46d772 Fix issue where running a specific test was treated as running all of them 2015-10-12 06:28:42 -07:00
Jared Parsons 9934a70f52 Merge pull request #22 from Pilchie/reload
Implement Assembly reload and remove support
2015-08-29 10:10:55 -07:00
29 changed files with 437 additions and 288 deletions
+8 -11
View File
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\xunit.core.2.1.0-beta4-build3109\build\portable-net45+netcore45+wp8+wpa81\xunit.core.props" Condition="Exists('..\packages\xunit.core.2.1.0-beta4-build3109\build\portable-net45+netcore45+wp8+wpa81\xunit.core.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -45,12 +44,16 @@
<HintPath>..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.assert, Version=2.1.0.3109, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.assert.2.1.0-beta4-build3109\lib\dotnet\xunit.assert.dll</HintPath>
<Reference Include="xunit.assert, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.core, Version=2.1.0.3109, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.core.2.1.0-beta4-build3109\lib\dotnet\xunit.core.dll</HintPath>
<Reference Include="xunit.core, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.execution.desktop, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
@@ -62,12 +65,6 @@
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>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}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\xunit.core.2.1.0-beta4-build3109\build\portable-net45+netcore45+wp8+wpa81\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.1.0-beta4-build3109\build\portable-net45+netcore45+wp8+wpa81\xunit.core.props'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
+5 -4
View File
@@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="xunit" version="2.1.0-beta4-build3109" targetFramework="net452" />
<package id="xunit" version="2.1.0" targetFramework="net452" />
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
<package id="xunit.assert" version="2.1.0-beta4-build3109" targetFramework="net452" />
<package id="xunit.core" version="2.1.0-beta4-build3109" targetFramework="net452" />
<package id="xunit.extensibility.core" version="2.1.0-beta4-build3109" targetFramework="net452" />
<package id="xunit.assert" version="2.1.0" targetFramework="net452" />
<package id="xunit.core" version="2.1.0" targetFramework="net452" />
<package id="xunit.extensibility.core" version="2.1.0" targetFramework="net452" />
<package id="xunit.extensibility.execution" version="2.1.0" targetFramework="net452" />
</packages>
+3 -3
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
</startup>
</configuration>
</configuration>
+1 -1
View File
@@ -46,7 +46,7 @@ namespace xunit.runner.worker
{
using (AssemblyHelper.SubscribeResolve())
using (var xunit = new XunitFrontController(
useAppDomain: true,
AppDomainSupport.IfAvailable,
assemblyFileName: fileName,
diagnosticMessageSink: new MessageVisitor(),
shadowCopy: false))
+117
View File
@@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using xunit.runner.data;
namespace xunit.runner.worker
{
internal sealed class Listener
{
private readonly string _pipeName;
private readonly List<Task> _taskList = new List<Task>();
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: NamedPipeServerStream.MaxAllowedServerInstances);
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 argument = reader.ReadString();
switch (action)
{
case Constants.ActionDiscover:
Discover(stream, argument);
break;
case Constants.ActionRunAll:
RunAll(stream, argument);
break;
case Constants.ActionRunSpecific:
RunSpecific(stream, argument);
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 assemblyPath)
{
Console.WriteLine($"discover started: {assemblyPath}");
DiscoverUtil.Go(assemblyPath, stream);
Console.WriteLine("discover ended");
}
private static void RunAll(Stream stream, string assemblyPath)
{
Console.WriteLine($"run all started: {assemblyPath}");
RunUtil.RunAll(assemblyPath, stream);
Console.WriteLine("run all ended");
}
private static void RunSpecific(Stream stream, string assemblyPath)
{
Console.WriteLine($"run specific started: {assemblyPath}");
RunUtil.RunSpecific(assemblyPath, stream);
Console.WriteLine("run specific ended");
}
}
}
+14 -64
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
@@ -16,83 +17,32 @@ namespace xunit.runner.worker
public static int Main(string[] args)
{
if (args.Length < 3)
if (args.Length < 2)
{
Usage();
return ExitError;
}
string pipeName = args[0];
string action = args[1];
string argument = args[2];
try
var pipeName = args[0];
var parentPid = Int32.Parse(args[1]);
var process = Process.GetProcessById(parentPid);
if (process == null)
{
using (var connection = CreateConnection(pipeName))
{
connection.WaitForClientConnect();
var stream = connection.Stream;
switch (action)
{
case Constants.ActionDiscover:
Discover(stream, argument);
break;
case Constants.ActionRunAll:
RunAll(stream, argument);
break;
case Constants.ActionRunSpecific:
RunSpecific(stream, argument);
break;
default:
Usage();
return ExitError;
}
connection.WaitForClientDone();
}
}
catch (Exception ex)
{
// Errors will happen during a rude shut down from the client. Print out to the screen
// for diagnostics and continue on.
Console.Error.WriteLine(ex.Message);
Console.WriteLine($"Invalid parent pid {parentPid}");
return ExitError;
}
Task.Run(() => WaitForParentExit(process));
var listener = new Listener(pipeName);
listener.Go();
return ExitSuccess;
}
private static Connection CreateConnection(string pipeName)
private static void WaitForParentExit(Process process)
{
if (pipeName == "test")
{
return new TestConnection();
}
return new NamedPipeConnection(pipeName);
}
private static void Discover(Stream stream, string assemblyPath)
{
Console.WriteLine($"discover started: {assemblyPath}");
DiscoverUtil.Go(assemblyPath, stream);
Console.WriteLine("discover ended");
}
private static void RunAll(Stream stream, string assemblyPath)
{
Console.WriteLine($"run all started: {assemblyPath}");
RunUtil.RunAll(assemblyPath, stream);
Console.WriteLine("run all ended");
}
private static void RunSpecific(Stream stream, string assemblyPath)
{
Console.WriteLine($"run specific started: {assemblyPath}");
RunUtil.RunSpecific(assemblyPath, stream);
Console.WriteLine("run specific ended");
process.WaitForExit();
Environment.Exit(ExitSuccess);
}
private static void Usage()
+2 -2
View File
@@ -122,8 +122,8 @@ namespace xunit.runner.worker
{
using (AssemblyHelper.SubscribeResolve())
using (var xunit = new XunitFrontController(
AppDomainSupport.IfAvailable,
assemblyFileName: assemblyPath,
useAppDomain: true,
shadowCopy: false,
diagnosticMessageSink: new MessageVisitor()))
using (var writer = new ClientWriter(stream))
@@ -139,8 +139,8 @@ namespace xunit.runner.worker
{
using (AssemblyHelper.SubscribeResolve())
using (var xunit = new XunitFrontController(
AppDomainSupport.IfAvailable,
assemblyFileName: assemblyPath,
useAppDomain: true,
shadowCopy: false,
diagnosticMessageSink: new MessageVisitor()))
using (var writer = new ClientWriter(stream))
+1 -1
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
<package id="xunit.runner.utility" version="2.1.0-beta4-build3109" targetFramework="net452" />
<package id="xunit.runner.utility" version="2.1.0" targetFramework="net46" />
</packages>
@@ -9,9 +9,10 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>xunit.runner.worker</RootNamespace>
<AssemblyName>xunit.runner.worker</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -44,13 +45,15 @@
<Reference Include="xunit.abstractions">
<HintPath>..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
</Reference>
<Reference Include="xunit.runner.utility.desktop">
<HintPath>..\packages\xunit.runner.utility.2.1.0-beta4-build3109\lib\net35\xunit.runner.utility.desktop.dll</HintPath>
<Reference Include="xunit.runner.utility.desktop, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.runner.utility.2.1.0\lib\net35\xunit.runner.utility.desktop.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Connection.cs" />
<Compile Include="DiscoverUtil.cs" />
<Compile Include="Listener.cs" />
<Compile Include="MessageVisitor.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
+3 -3
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
</startup>
</configuration>
</configuration>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

@@ -1,27 +1,23 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using xunit.runner.data;
using xunit.runner.wpf.ViewModel;
namespace xunit.runner.wpf.Converters
{
public class TestStateConverter : IValueConverter
{
private static ImageSource passedSource;
private static ImageSource failedSource;
private static ImageSource passedSource;
private static ImageSource skippedSource;
static TestStateConverter()
{
passedSource = LoadResourceImage("Passed.ico");
failedSource = LoadResourceImage("Failed.ico");
skippedSource = LoadResourceImage("Skipped.ico");
failedSource = LoadResourceImage("Failed_small.png");
passedSource = LoadResourceImage("Passed_small.png");
skippedSource = LoadResourceImage("Skipped_small.png");
}
private static BitmapImage LoadResourceImage(string resourceName)
@@ -42,10 +38,10 @@ namespace xunit.runner.wpf.Converters
{
case TestState.Failed:
return Brushes.Red;
case TestState.Skipped:
return Brushes.Yellow;
case TestState.Passed:
return Brushes.Green;
case TestState.Skipped:
return Brushes.Yellow;
default:
return Brushes.Gray;
}
@@ -56,10 +52,10 @@ namespace xunit.runner.wpf.Converters
{
case TestState.Failed:
return failedSource;
case TestState.Skipped:
return skippedSource;
case TestState.Passed:
return passedSource;
case TestState.Skipped:
return skippedSource;
default:
return null;
}
@@ -54,26 +54,29 @@ namespace xunit.runner.wpf.Impl
}
}
/// <summary>
/// Utility for reading a collection of <see cref="{T}"/> values from the given
/// <see cref="ClientReader"/> value.
/// </summary>
/// <typeparam name="T"></typeparam>
private sealed class BackgroundReader<T> where T : class
{
private readonly ConcurrentQueue<T> _queue;
private readonly ClientReader _reader;
private readonly Func<ClientReader, T> _readValue;
private readonly CancellationToken _cancellationToken;
internal ClientReader Reader => _reader;
internal BackgroundReader(ConcurrentQueue<T> queue, ClientReader reader, Func<ClientReader, T> readValue, CancellationToken cancellationToken)
internal BackgroundReader(ConcurrentQueue<T> queue, ClientReader reader, Func<ClientReader, T> readValue)
{
_queue = queue;
_reader = reader;
_readValue = readValue;
_cancellationToken = cancellationToken;
}
internal Task ReadAsync()
internal Task ReadAsync(CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run(() => GoOnBackground(), _cancellationToken);
return Task.Run(() => GoOnBackground(cancellationToken), cancellationToken);
}
/// <summary>
@@ -81,9 +84,9 @@ namespace xunit.runner.wpf.Impl
/// named pipe client stream.
/// </summary>
/// <returns></returns>
private void GoOnBackground()
private void GoOnBackground(CancellationToken cancellationToken)
{
while (!_cancellationToken.IsCancellationRequested)
while (!cancellationToken.IsCancellationRequested)
{
try
{
@@ -19,47 +19,37 @@ namespace xunit.runner.wpf.Impl
private sealed class Connection : IDisposable
{
private NamedPipeClientStream _stream;
private Process _process;
private ClientReader _reader;
internal NamedPipeClientStream Stream => _stream;
internal ClientReader Reader => _reader;
internal Connection(NamedPipeClientStream stream, Process process)
internal Connection(NamedPipeClientStream stream)
{
_stream = stream;
_process = process;
_reader = new ClientReader(stream);
}
internal void Dispose()
{
if (_process != null)
if (_stream == null)
{
Debug.Assert(_stream != null);
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();
try
{
_process.Kill();
}
catch
{
// Inherent race condition shutting down the process.
}
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()
+79 -55
View File
@@ -17,85 +17,109 @@ 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 static Connection StartWorkerProcess(string action, string argument)
private async Task<Connection> CreateConnection(string action, string argument)
{
var pipeName = $"xunit.runner.wpf.pipe.{Guid.NewGuid()}";
var processStartInfo = new ProcessStartInfo();
processStartInfo.FileName = typeof(xunit.runner.worker.Program).Assembly.Location;
processStartInfo.Arguments = $"{pipeName} {action} {argument}";
processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
var pipeName = GetPipeName();
var process = Process.Start(processStartInfo);
try
{
var stream = new NamedPipeClientStream(pipeName);
stream.Connect();
return new Connection(stream, process);
await stream.ConnectAsync();
var writer = new ClientWriter(stream);
writer.Write(action);
writer.Write(argument);
return new Connection(stream);
}
catch
{
process.Kill();
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 Task Discover(string assemblyPath, Action<TestCaseData> callback, CancellationToken cancellationToken)
private string GetPipeName()
{
var connection = StartWorkerProcess(Constants.ActionDiscover, assemblyPath);
var queue = new ConcurrentQueue<TestCaseData>();
var backgroundReader = new BackgroundReader<TestCaseData>(queue, new ClientReader(connection.Stream), r => r.ReadTestCaseData(), cancellationToken);
backgroundReader.ReadAsync();
var backgroundProducer = new BackgroundProducer<TestCaseData>(connection, _dispatcher, queue, callback);
return backgroundProducer.Task;
}
private Task RunCore(string actionName, string assemblyPath, ImmutableArray<string> testCaseDisplayNames, Action<TestResultData> callback, CancellationToken cancellationToken)
{
var connection = StartWorkerProcess(Constants.ActionRunAll, assemblyPath);
var queue = CreateRunQueue(connection, testCaseDisplayNames, cancellationToken);
var backgroundProducer = new BackgroundProducer<TestResultData>(connection, _dispatcher, queue, callback);
return backgroundProducer.Task;
}
/// <summary>
/// Create the <see cref="ConcurrentQueue{T}"/> which will be populated with the <see cref="TestResultData"/>
/// as it arrives from the worker.
/// </summary>
private static ConcurrentQueue<TestResultData> CreateRunQueue(Connection connection, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken)
{
var queue = new ConcurrentQueue<TestResultData>();
var unused = CreateRunQueueCore(queue, connection, testCaseDisplayNames, cancellationToken);
return queue;
}
private static async Task CreateRunQueueCore(ConcurrentQueue<TestResultData> queue, Connection connection, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken)
{
try
var process = _processInfo?.Process;
if (process != null && !process.HasExited)
{
if (!testCaseDisplayNames.IsDefaultOrEmpty)
{
var backgroundWriter = new BackgroundWriter<string>(new ClientWriter(connection.Stream), testCaseDisplayNames, (w, s) => w.Write(s), cancellationToken);
await backgroundWriter.WriteAsync();
}
var backgroundReader = new BackgroundReader<TestResultData>(queue, new ClientReader(connection.Stream), r => r.ReadTestResultData(), cancellationToken);
await backgroundReader.ReadAsync();
return _processInfo.Value.PipeName;
}
catch (Exception ex)
_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 async Task Discover(string assemblyPath, Action<TestCaseData> callback, CancellationToken cancellationToken)
{
var connection = await CreateConnection(Constants.ActionDiscover, assemblyPath);
await ProcessResultsCore(connection, r => r.ReadTestCaseData(), callback, cancellationToken);
}
private async Task RunCore(string actionName, string assemblyPath, ImmutableArray<string> testCaseDisplayNames, Action<TestResultData> callback, CancellationToken cancellationToken)
{
var connection = await CreateConnection(actionName, assemblyPath);
if (!testCaseDisplayNames.IsDefaultOrEmpty)
{
Debug.Fail(ex.Message);
// Signal data completed
queue.Enqueue(null);
var backgroundWriter = new BackgroundWriter<string>(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<T>(Connection connection, Func<ClientReader, T> readValue, Action<T> callback, CancellationToken cancellationToken)
where T : class
{
var queue = new ConcurrentQueue<T>();
var backgroundReader = new BackgroundReader<T>(queue, new ClientReader(connection.Stream), readValue);
var backgroundProducer = new BackgroundProducer<T>(connection, _dispatcher, queue, callback);
await backgroundReader.ReadAsync(cancellationToken);
await backgroundProducer.Task;
}
#region ITestUtil
+97 -40
View File
@@ -8,6 +8,7 @@
xmlns:vm="clr-namespace:xunit.runner.wpf.ViewModel"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
DataContext="{Binding Main, Source={StaticResource Locator}}"
Title="xUnit.net Test Runner"
@@ -62,6 +63,7 @@
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
@@ -80,6 +82,7 @@
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Content="Search:"
Grid.Row="0" />
<TextBox Grid.Row="1"
@@ -110,6 +113,7 @@
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
<Label Content="Traits:"
Grid.Row="4" />
<ListBox Grid.Row="5"
@@ -163,7 +167,7 @@
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<GroupBox Header="{Binding MethodsCaption}"
<GroupBox Header="{Binding TestCasesCaption}"
Margin="3"
Grid.Row="0">
<Grid>
@@ -172,42 +176,57 @@
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ToggleButton IsChecked="{Binding IncludePassedTests}"
Margin="3">
<StackPanel Orientation="Horizontal">
<Image Source="Artwork\Passed.ico"
Height="16" />
<TextBlock Margin="4,0,0,0"
Text="{Binding TestsPassed}" />
</StackPanel>
</ToggleButton>
<ToggleButton IsChecked="{Binding IncludeFailedTests}"
Margin="3"
Grid.Column="1">
<StackPanel Orientation="Horizontal">
<Image Source="Artwork\Failed.ico"
Height="16" />
<TextBlock Margin="4,0,0,0"
Text="{Binding TestsFailed}" />
</StackPanel>
</ToggleButton>
<ToggleButton IsChecked="{Binding IncludeSkippedTests}"
Margin="3"
Grid.Column="2">
<StackPanel Orientation="Horizontal">
<Image Source="Artwork\Skipped.ico"
Height="16" />
<TextBlock Margin="4,0,0,0"
Text="{Binding TestsSkipped}" />
</StackPanel>
</ToggleButton>
</Grid>
<!--<ToolBarTray Grid.Row="0"
ToolBarTray.IsLocked="True"
Background="Transparent"
RenderOptions.BitmapScalingMode="NearestNeighbor">
<ToolBar>-->
<StackPanel Orientation="Horizontal">
<ToggleButton IsChecked="{Binding IncludePassedTests}"
BorderThickness="0"
Background="Transparent"
Margin="0,4,2,4"
Grid.Column="0">
<StackPanel Orientation="Horizontal">
<Image Source="Artwork\Passed_large.png" />
<TextBlock Margin="4,0"
FontSize="16"
Text="{Binding TestsPassed, StringFormat={}{0:#\,0}}"
VerticalAlignment="Center" />
</StackPanel>
</ToggleButton>
<ToggleButton IsChecked="{Binding IncludeFailedTests}"
BorderThickness="0"
Background="Transparent"
Margin="2,4"
Grid.Column="1">
<StackPanel Orientation="Horizontal">
<Image Source="Artwork\Failed_large.png" />
<TextBlock Margin="4,0"
FontSize="16"
Text="{Binding TestsFailed, StringFormat={}{0:#\,0}}"
VerticalAlignment="Center" />
</StackPanel>
</ToggleButton>
<ToggleButton IsChecked="{Binding IncludeSkippedTests}"
BorderThickness="0"
Background="Transparent"
Margin="2,4,0,4"
Grid.Column="2">
<StackPanel Orientation="Horizontal">
<Image Source="Artwork\Skipped_large.png" />
<TextBlock Margin="4,0"
FontSize="16"
Text="{Binding TestsSkipped, StringFormat={}{0:#\,0}}"
VerticalAlignment="Center" />
</StackPanel>
</ToggleButton>
</StackPanel>
<!--</ToolBar>
</ToolBarTray>-->
<ListBox ItemsSource="{Binding TestCases}"
SelectionMode="Extended"
Grid.Row="1">
@@ -220,10 +239,11 @@
</Grid.ColumnDefinitions>
<Image Width="16"
Margin="2"
Margin="0,0,2,0"
Source="{Binding Path=State, Mode=OneWay, Converter={StaticResource TestStateConverter}}"
Grid.Column="0" />
<TextBlock Text="{Binding DisplayName}"
VerticalAlignment="Center"
Grid.Column="1" />
</Grid>
</DataTemplate>
@@ -256,8 +276,45 @@
BorderThickness="1"
Margin="3" />
<StatusBar>
<StatusBarItem>
<Label />
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="16"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem Grid.Column="1">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Tests passed: "
Foreground="DarkGreen"/>
<TextBlock Text="{Binding TestsPassed, StringFormat={}{0:#\,0}}"/>
</StackPanel>
</StatusBarItem>
<StatusBarItem Grid.Column="2">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Tests failed: "
Foreground="DarkRed"/>
<TextBlock Text="{Binding TestsFailed, StringFormat={}{0:#\,0}}"/>
</StackPanel>
</StatusBarItem>
<StatusBarItem Grid.Column="3">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Tests skipped: "
Foreground="DarkGoldenrod"/>
<TextBlock Text="{Binding TestsSkipped, StringFormat={}{0:#\,0}}"/>
</StackPanel>
</StatusBarItem>
</StatusBar>
</StackPanel>
+17 -25
View File
@@ -8,10 +8,10 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace xunit.runner.wpf.Properties
{
namespace xunit.runner.wpf.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
@@ -22,48 +22,40 @@ namespace xunit.runner.wpf.Properties
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ((resourceMan == null))
{
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("xunit.runner.wpf.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set
{
set {
resourceCulture = value;
}
}
+9 -13
View File
@@ -8,21 +8,17 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace xunit.runner.wpf.Properties
{
namespace xunit.runner.wpf.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
public static Settings Default {
get {
return defaultInstance;
}
}
+9 -11
View File
@@ -12,9 +12,6 @@ using System.Collections.Specialized;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System.IO.Pipes;
using System.IO;
using System.Text;
using xunit.runner.data;
using System.Windows.Threading;
@@ -40,7 +37,7 @@ namespace xunit.runner.wpf.ViewModel
CommandBindings = CreateCommandBindings();
this.testUtil = new xunit.runner.wpf.Impl.RemoteTestUtil(Dispatcher.CurrentDispatcher);
this.MethodsCaption = "Methods (0)";
this.TestCasesCaption = "Test Cases (0)";
TestCases = new FilteredCollectionView<TestCaseViewModel, SearchQuery>(
allTestCases, TestCaseMatches, searchQuery, TestComparer.Instance);
@@ -104,7 +101,7 @@ namespace xunit.runner.wpf.ViewModel
private void TestCases_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
MethodsCaption = $"Methods ({TestCases.Count})";
TestCasesCaption = $"Test Cases ({TestCases.Count:#,0})";
MaximumProgress = TestCases.Count;
}
@@ -126,11 +123,11 @@ namespace xunit.runner.wpf.ViewModel
get { return Assemblies.Where(x => x.IsSelected).ToList(); }
}
private string methodsCaption;
public string MethodsCaption
private string testCasesCaption;
public string TestCasesCaption
{
get { return methodsCaption; }
private set { Set(ref methodsCaption, value); }
get { return testCasesCaption; }
private set { Set(ref testCasesCaption, value); }
}
private int testsCompleted = 0;
@@ -232,6 +229,7 @@ namespace xunit.runner.wpf.ViewModel
var fileDialog = new OpenFileDialog
{
Filter = "Unit Test Assemblies|*.dll",
Multiselect = true
};
if (fileDialog.ShowDialog(Application.Current.MainWindow) != true)
@@ -239,8 +237,8 @@ namespace xunit.runner.wpf.ViewModel
return;
}
var fileName = fileDialog.FileName;
await AddAssemblies(new[] { new AssemblyAndConfigFile(fileName, configFileName: null) });
var assemblies = fileDialog.FileNames.Select(x => new AssemblyAndConfigFile(x, configFileName: null));
await AddAssemblies(assemblies);
}
private async Task AddAssemblies(IEnumerable<AssemblyAndConfigFile> assemblies)
+9 -1
View File
@@ -3,7 +3,15 @@
<package id="CommonServiceLocator" version="1.3" targetFramework="net452" />
<package id="MvvmLight" version="5.1.1.0" targetFramework="net452" />
<package id="MvvmLightLibs" version="5.1.1.0" targetFramework="net452" />
<package id="System.Collections" version="4.0.0" targetFramework="net46" />
<package id="System.Collections.Immutable" version="1.1.37" targetFramework="net452" />
<package id="System.Diagnostics.Debug" version="4.0.0" targetFramework="net46" />
<package id="System.Globalization" version="4.0.0" targetFramework="net46" />
<package id="System.Linq" version="4.0.0" targetFramework="net46" />
<package id="System.Resources.ResourceManager" version="4.0.0" targetFramework="net46" />
<package id="System.Runtime" version="4.0.0" targetFramework="net46" />
<package id="System.Runtime.Extensions" version="4.0.0" targetFramework="net46" />
<package id="System.Threading" version="4.0.0" targetFramework="net46" />
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
<package id="xunit.runner.utility" version="2.1.0-beta4-build3109" targetFramework="net452" />
<package id="xunit.runner.utility" version="2.1.0" targetFramework="net46" />
</packages>
+22 -5
View File
@@ -9,13 +9,14 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>xunit.runner.wpf</RootNamespace>
<AssemblyName>xunit.runner.wpf</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -75,6 +76,10 @@
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="xunit.runner.utility.desktop, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.runner.utility.2.1.0\lib\net35\xunit.runner.utility.desktop.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
@@ -148,9 +153,6 @@
</ItemGroup>
<ItemGroup>
<Resource Include="Artwork\xunit-dot-net-small-logo.png" />
<Resource Include="Artwork\Passed.ico" />
<Resource Include="Artwork\Skipped.ico" />
<Resource Include="Artwork\Failed.ico" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SampleTestAssembly\SampleTestAssembly.csproj">
@@ -168,7 +170,22 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<WCFMetadata Include="Service References\" />
<Resource Include="Artwork\Passed_small.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Artwork\Passed_large.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Artwork\Failed_small.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Artwork\Failed_large.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Artwork\Skipped_small.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Artwork\Skipped_large.png" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.