d861f4a6ee
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.
144 lines
5.2 KiB
C#
144 lines
5.2 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.IO.Pipes;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Threading;
|
|
using xunit.runner.data;
|
|
using xunit.runner.wpf.ViewModel;
|
|
|
|
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;
|
|
}
|
|
|
|
private async Task<Connection> CreateConnection(string action, string argument)
|
|
{
|
|
var pipeName = GetPipeName();
|
|
|
|
try
|
|
{
|
|
var stream = new NamedPipeClientStream(pipeName);
|
|
await stream.ConnectAsync();
|
|
|
|
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 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)
|
|
{
|
|
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
|
|
|
|
Task ITestUtil.Discover(string assemblyPath, Action<TestCaseData> callback, CancellationToken cancellationToken)
|
|
{
|
|
return Discover(assemblyPath, callback, cancellationToken);
|
|
}
|
|
|
|
Task ITestUtil.RunAll(string assemblyPath, Action<TestResultData> callback, CancellationToken cancellationToken)
|
|
{
|
|
return RunCore(Constants.ActionRunAll, assemblyPath, ImmutableArray<string>.Empty, callback, cancellationToken);
|
|
}
|
|
|
|
Task ITestUtil.RunSpecific(string assemblyPath, ImmutableArray<string> testCaseDisplayNames, Action<TestResultData> callback, CancellationToken cancellationToken)
|
|
{
|
|
return RunCore(Constants.ActionRunSpecific, assemblyPath, testCaseDisplayNames, callback, cancellationToken);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|