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