diff --git a/xunit.runner.wpf/ITestUtil.cs b/xunit.runner.wpf/ITestUtil.cs
index eaca6a5..f0bae10 100644
--- a/xunit.runner.wpf/ITestUtil.cs
+++ b/xunit.runner.wpf/ITestUtil.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using xunit.runner.data;
@@ -20,7 +21,7 @@ namespace xunit.runner.wpf
///
/// Begin a run of a unit test for the given assembly.
///
- ITestRunSession Run(Dispatcher dispatcher, string assemblyPath);
+ ITestRunSession Run(Dispatcher dispatcher, string assemblyPath, CancellationToken cancellationToken = default(CancellationToken));
}
internal sealed class TestResultEventArgs : EventArgs
@@ -37,6 +38,16 @@ namespace xunit.runner.wpf
internal interface ITestRunSession
{
+ bool IsRunning { get; }
+
+ ///
+ /// Raised when an idividual test is finished running.
+ ///
event EventHandler TestFinished;
+
+ ///
+ /// Raised when the session has finished executing all of the specified tests.
+ ///
+ event EventHandler SessionFinished;
}
}
diff --git a/xunit.runner.wpf/Impl/RemoteTestUtil .BackgroundRunner.cs b/xunit.runner.wpf/Impl/RemoteTestUtil .BackgroundRunner.cs
index 9ac17ec..cf09a58 100644
--- a/xunit.runner.wpf/Impl/RemoteTestUtil .BackgroundRunner.cs
+++ b/xunit.runner.wpf/Impl/RemoteTestUtil .BackgroundRunner.cs
@@ -6,6 +6,7 @@ using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using xunit.runner.data;
using xunit.runner.wpf.ViewModel;
@@ -18,11 +19,13 @@ namespace xunit.runner.wpf.Impl
{
private readonly ConcurrentQueue _resultQueue;
private readonly BinaryReader _reader;
+ private readonly CancellationToken _cancellationToken;
- internal BackgroundRunner(ConcurrentQueue resultQueue, BinaryReader reader)
+ internal BackgroundRunner(ConcurrentQueue resultQueue, BinaryReader reader, CancellationToken cancellationToken)
{
_resultQueue = resultQueue;
_reader = reader;
+ _cancellationToken = cancellationToken;
}
///
@@ -32,7 +35,7 @@ namespace xunit.runner.wpf.Impl
///
internal Task GoOnBackground()
{
- while (true)
+ while (!_cancellationToken.IsCancellationRequested)
{
TestResultData result;
try
diff --git a/xunit.runner.wpf/Impl/RemoteTestUtil .RunSession.cs b/xunit.runner.wpf/Impl/RemoteTestUtil .RunSession.cs
index cc0ca7d..53ec4ad 100644
--- a/xunit.runner.wpf/Impl/RemoteTestUtil .RunSession.cs
+++ b/xunit.runner.wpf/Impl/RemoteTestUtil .RunSession.cs
@@ -24,6 +24,7 @@ namespace xunit.runner.wpf.Impl
private readonly DispatcherTimer _timer;
private bool _closed;
private event EventHandler _testFinished;
+ private event EventHandler _sessionFinished;
internal RunSession(Connection connection, Dispatcher dispatcher, ConcurrentQueue resultQueue)
{
@@ -58,16 +59,26 @@ namespace xunit.runner.wpf.Impl
_closed = true;
_timer.Stop();
((IDisposable)_connection).Dispose();
+
+ _sessionFinished?.Invoke(this, EventArgs.Empty);
}
#region ITestRunSession
+ bool ITestRunSession.IsRunning => !_closed;
+
event EventHandler ITestRunSession.TestFinished
{
add { _testFinished += value; }
remove { _testFinished -= value; }
}
+ event EventHandler ITestRunSession.SessionFinished
+ {
+ add { _sessionFinished += value; }
+ remove { _sessionFinished -= value; }
+ }
+
#endregion
}
}
diff --git a/xunit.runner.wpf/Impl/RemoteTestUtil.cs b/xunit.runner.wpf/Impl/RemoteTestUtil.cs
index 264fb71..9275f82 100644
--- a/xunit.runner.wpf/Impl/RemoteTestUtil.cs
+++ b/xunit.runner.wpf/Impl/RemoteTestUtil.cs
@@ -6,6 +6,7 @@ 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;
@@ -53,6 +54,8 @@ namespace xunit.runner.wpf.Impl
var processStartInfo = new ProcessStartInfo();
processStartInfo.FileName = typeof(xunit.runner.worker.Program).Assembly.Location;
processStartInfo.Arguments = $"{action} {argument}";
+ processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
+
var process = Process.Start(processStartInfo);
try
{
@@ -99,11 +102,11 @@ namespace xunit.runner.wpf.Impl
return list;
}
- private RunSession Run(Dispatcher dispatcher, string assemblyPath)
+ private RunSession Run(Dispatcher dispatcher, string assemblyPath, CancellationToken cancellationToken)
{
var connection = StartWorkerProcess(Constants.ActionRun, assemblyPath);
var queue = new ConcurrentQueue();
- var backgroundRunner = new BackgroundRunner(queue, new BinaryReader(connection.Stream, Constants.Encoding, leaveOpen: true));
+ var backgroundRunner = new BackgroundRunner(queue, new BinaryReader(connection.Stream, Constants.Encoding, leaveOpen: true), cancellationToken);
Task.Run(backgroundRunner.GoOnBackground);
return new RunSession(connection, dispatcher, queue);
@@ -116,9 +119,9 @@ namespace xunit.runner.wpf.Impl
return Discover(assemblyPath);
}
- ITestRunSession ITestUtil.Run(Dispatcher dispatcher, string assemblyPath)
+ ITestRunSession ITestUtil.Run(Dispatcher dispatcher, string assemblyPath, CancellationToken cancellationToken)
{
- return Run(dispatcher, assemblyPath);
+ return Run(dispatcher, assemblyPath, cancellationToken);
}
#endregion
diff --git a/xunit.runner.wpf/ViewModel/MainViewModel.cs b/xunit.runner.wpf/ViewModel/MainViewModel.cs
index cf0bcea..9649805 100644
--- a/xunit.runner.wpf/ViewModel/MainViewModel.cs
+++ b/xunit.runner.wpf/ViewModel/MainViewModel.cs
@@ -25,8 +25,10 @@ namespace xunit.runner.wpf.ViewModel
private readonly ObservableCollection allTestCases = new ObservableCollection();
private CancellationTokenSource filterCancellationTokenSource = new CancellationTokenSource();
+ private List testRunSessionList = new List();
+ private CancellationTokenSource cancellationTokenSource;
private bool isBusy;
- private bool isCancelRequested;
+
public MainViewModel()
{
if (IsInDesignMode)
@@ -205,16 +207,6 @@ namespace xunit.runner.wpf.ViewModel
}
}
- private bool IsCancelRequested
- {
- get { return isCancelRequested; }
- set
- {
- isCancelRequested = value;
- CancelCommand.RaiseCanExecuteChanged();
- }
- }
-
private static void OnExecuteExit()
{
Application.Current.Shutdown();
@@ -227,7 +219,6 @@ namespace xunit.runner.wpf.ViewModel
{
try
{
- IsBusy = true;
TestsCompleted = 0;
TestsPassed = 0;
TestsFailed = 0;
@@ -239,24 +230,47 @@ namespace xunit.runner.wpf.ViewModel
{
MessageBox.Show(ex.ToString());
}
- finally
- {
- IsBusy = false;
- IsCancelRequested = false;
- }
}
private void RunTests()
{
+ Debug.Assert(this.cancellationTokenSource == null);
+ Debug.Assert(this.testRunSessionList.Count == 0);
+
foreach (var tc in TestCases)
{
tc.State = TestState.NotRun;
}
- // Hacky way of using one assembly for now. Will expand later.
- var assemblyPath = TestCases.Select(x => x.AssemblyFileName).First();
- var session = this.testUtil.Run(Dispatcher.CurrentDispatcher, assemblyPath);
- session.TestFinished += OnTestFinished;
+ // TODO: Need a way to filter based on traits, selected test cases, etc ... For now we just run
+ // everything.
+
+ this.cancellationTokenSource = new CancellationTokenSource();
+ foreach (var assemblyPath in TestCases.Select(x => x.AssemblyFileName).Distinct())
+ {
+ var session = this.testUtil.Run(Dispatcher.CurrentDispatcher, assemblyPath, this.cancellationTokenSource.Token);
+ session.TestFinished += OnTestFinished;
+ session.SessionFinished += delegate { OnTestRunSessionFinished(session); };
+ this.testRunSessionList.Add(session);
+ }
+
+ this.IsBusy = true;
+ this.RunCommand.RaiseCanExecuteChanged();
+ this.CancelCommand.RaiseCanExecuteChanged();
+ }
+
+ private void OnTestRunSessionFinished(ITestRunSession session)
+ {
+ Debug.Assert(this.testRunSessionList.Contains(session));
+ this.testRunSessionList.Remove(session);
+
+ if (this.testRunSessionList.Count == 0)
+ {
+ this.cancellationTokenSource = null;
+ this.IsBusy = false;
+ this.RunCommand.RaiseCanExecuteChanged();
+ this.CancelCommand.RaiseCanExecuteChanged();
+ }
}
private void OnTestFinished(object sender, TestResultEventArgs e)
@@ -284,11 +298,15 @@ namespace xunit.runner.wpf.ViewModel
}
}
- private bool CanExecuteCancel() => IsBusy && !IsCancelRequested;
+ private bool CanExecuteCancel()
+ {
+ return this.cancellationTokenSource != null && !this.cancellationTokenSource.IsCancellationRequested;
+ }
private void OnExecuteCancel()
{
- this.IsCancelRequested = true;
+ Debug.Assert(CanExecuteCancel());
+ this.cancellationTokenSource.Cancel();
}
public bool IsPassedFilterChecked