Cancellation for unit test runs re-implemented
This commit is contained in:
@@ -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
|
||||
/// <summary>
|
||||
/// Begin a run of a unit test for the given assembly.
|
||||
/// </summary>
|
||||
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; }
|
||||
|
||||
/// <summary>
|
||||
/// Raised when an idividual test is finished running.
|
||||
/// </summary>
|
||||
event EventHandler<TestResultEventArgs> TestFinished;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the session has finished executing all of the specified tests.
|
||||
/// </summary>
|
||||
event EventHandler SessionFinished;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<TestResultData> _resultQueue;
|
||||
private readonly BinaryReader _reader;
|
||||
private readonly CancellationToken _cancellationToken;
|
||||
|
||||
internal BackgroundRunner(ConcurrentQueue<TestResultData> resultQueue, BinaryReader reader)
|
||||
internal BackgroundRunner(ConcurrentQueue<TestResultData> resultQueue, BinaryReader reader, CancellationToken cancellationToken)
|
||||
{
|
||||
_resultQueue = resultQueue;
|
||||
_reader = reader;
|
||||
_cancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -32,7 +35,7 @@ namespace xunit.runner.wpf.Impl
|
||||
/// <returns></returns>
|
||||
internal Task GoOnBackground()
|
||||
{
|
||||
while (true)
|
||||
while (!_cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
TestResultData result;
|
||||
try
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace xunit.runner.wpf.Impl
|
||||
private readonly DispatcherTimer _timer;
|
||||
private bool _closed;
|
||||
private event EventHandler<TestResultEventArgs> _testFinished;
|
||||
private event EventHandler _sessionFinished;
|
||||
|
||||
internal RunSession(Connection connection, Dispatcher dispatcher, ConcurrentQueue<TestResultData> 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<TestResultEventArgs> ITestRunSession.TestFinished
|
||||
{
|
||||
add { _testFinished += value; }
|
||||
remove { _testFinished -= value; }
|
||||
}
|
||||
|
||||
event EventHandler ITestRunSession.SessionFinished
|
||||
{
|
||||
add { _sessionFinished += value; }
|
||||
remove { _sessionFinished -= value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<TestResultData>();
|
||||
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
|
||||
|
||||
@@ -25,8 +25,10 @@ namespace xunit.runner.wpf.ViewModel
|
||||
private readonly ObservableCollection<TestCaseViewModel> allTestCases = new ObservableCollection<TestCaseViewModel>();
|
||||
private CancellationTokenSource filterCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
private List<ITestRunSession> testRunSessionList = new List<ITestRunSession>();
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user