From 8c3c86d918b79932061fc404b5a821b94e2a45df Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Tue, 31 Oct 2017 17:23:22 -0700 Subject: [PATCH] made wpf xunit runner to support showing/filtering running tests so that if we get deadlock or hang tests, we can see what the test it was running easily. --- xunit.runner.data/TestResultData.cs | 1 + .../MessageSinks/BaseTestRunSink.cs | 7 ++ xunit.runner.worker/RunUtil.cs | 5 + xunit.runner.wpf/Artwork/Running_large.png | Bin 0 -> 659 bytes xunit.runner.wpf/Artwork/Running_small.png | Bin 0 -> 402 bytes .../Converters/TestStateConverter.cs | 6 ++ xunit.runner.wpf/MainWindow.xaml | 15 +++ xunit.runner.wpf/ViewModel/MainViewModel.cs | 98 ++++++++++++------ xunit.runner.wpf/ViewModel/SearchQuery.cs | 1 + xunit.runner.wpf/xunit.runner.wpf.csproj | 6 ++ 10 files changed, 110 insertions(+), 29 deletions(-) create mode 100644 xunit.runner.wpf/Artwork/Running_large.png create mode 100644 xunit.runner.wpf/Artwork/Running_small.png diff --git a/xunit.runner.data/TestResultData.cs b/xunit.runner.data/TestResultData.cs index 7e06f27..fefd0eb 100644 --- a/xunit.runner.data/TestResultData.cs +++ b/xunit.runner.data/TestResultData.cs @@ -10,6 +10,7 @@ public enum TestState { All = 0, NotRun, + Running, Passed, Skipped, Failed, diff --git a/xunit.runner.worker/MessageSinks/BaseTestRunSink.cs b/xunit.runner.worker/MessageSinks/BaseTestRunSink.cs index a99063e..3f3d5a6 100644 --- a/xunit.runner.worker/MessageSinks/BaseTestRunSink.cs +++ b/xunit.runner.worker/MessageSinks/BaseTestRunSink.cs @@ -19,6 +19,12 @@ protected override void DisposeCore(bool disposing) protected override bool OnMessage(IMessageSinkMessage message) { + var testStarted = message as ITestStarting; + if (testStarted != null) + { + OnTestStarted(testStarted); + } + var testFailed = message as ITestFailed; if (testFailed != null) { @@ -47,6 +53,7 @@ protected override bool OnMessage(IMessageSinkMessage message) protected virtual bool ShouldContinue => true; + protected abstract void OnTestStarted(ITestStarting testStarted); protected abstract void OnTestFailed(ITestFailed testFailed); protected abstract void OnTestPassed(ITestPassed testPassed); protected abstract void OnTestSkipped(ITestSkipped testSkipped); diff --git a/xunit.runner.worker/RunUtil.cs b/xunit.runner.worker/RunUtil.cs index 961b292..aab3b60 100644 --- a/xunit.runner.worker/RunUtil.cs +++ b/xunit.runner.worker/RunUtil.cs @@ -30,6 +30,11 @@ private void Process(string displayName, string uniqueID, TestState state, strin _writer.Write(result); } + protected override void OnTestStarted(ITestStarting testStarted) + { + Process(testStarted.TestCase.DisplayName, testStarted.TestCase.UniqueID, TestState.Running); + } + protected override void OnTestFailed(ITestFailed testFailed) { var displayName = testFailed.TestCase.DisplayName; diff --git a/xunit.runner.wpf/Artwork/Running_large.png b/xunit.runner.wpf/Artwork/Running_large.png new file mode 100644 index 0000000000000000000000000000000000000000..69ccfbcde052216018d903996875348e23d792a3 GIT binary patch literal 659 zcmV;E0&M+>P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf0pv+UK~z{r?Uu1h z13?gm=M*eLz)A?%B%O_&51?RWV?;=8ULa^^V`XD!$OFXG0WoQ;1bqN2ZS0ap2v`Xr z0U@08ud`t;(yy4;?)wKJJaOU;B}`iv!j9mC-bw@IgAKunp1K#;{$0@(RAcc&Ql`aNpCHR^bgSGQE!Dp4+*lNqYAh%*_Fi7kMgWwhuCU&NVx6dNX^a5Yim^}<`GAs6%_&ObCvB$Vc1f(O5vdg~YlQX`D-7qAw;Gr+y{VNKP#0Qfk6N7Jn^Io6sao zi@z>_=o`1}^(9B$4|M+aC%MVeZXd5WYqNW z`}_N%4Hb3*``I7;58z2^J|dFv>H)(O)*0seYJaDF_;mR6_h5gYzvo~6VPtJS((viM zW7dSP2M;r(SWU3ffAia$L8pOv&)qZM7(FD5ii+$QK2AKmpzhz&pB1Lf3?aU@E$m;c zCUM^r-XzQBu#RDugTRrt-49N$*LTTmW7-k*%2kM;OXk;vd$@?>0*L3XUgGqi(Y$Fr_Xa)o4mNq+ThTp l6xD}sTa%8znG@Styz=XregFTP|6{nRv{gV&_mdv7b^wqPpws{W literal 0 HcmV?d00001 diff --git a/xunit.runner.wpf/Converters/TestStateConverter.cs b/xunit.runner.wpf/Converters/TestStateConverter.cs index 2b0af42..46f1cc3 100644 --- a/xunit.runner.wpf/Converters/TestStateConverter.cs +++ b/xunit.runner.wpf/Converters/TestStateConverter.cs @@ -9,6 +9,7 @@ namespace Xunit.Runner.Wpf.Converters { public class TestStateConverter : IValueConverter { + private static ImageSource runningSource; private static ImageSource failedSource; private static ImageSource passedSource; private static ImageSource skippedSource; @@ -17,6 +18,7 @@ public class TestStateConverter : IValueConverter static TestStateConverter() { + runningSource = LoadResourceImage("Running_small.png"); failedSource = LoadResourceImage("Failed_small.png"); passedSource = LoadResourceImage("Passed_small.png"); skippedSource = LoadResourceImage("Skipped_small.png"); @@ -38,6 +40,8 @@ public object Convert(object value, Type targetType, object parameter, CultureIn { switch (state) { + case TestState.Running: + return Brushes.Blue; case TestState.Failed: return Brushes.Red; case TestState.Passed: @@ -52,6 +56,8 @@ public object Convert(object value, Type targetType, object parameter, CultureIn { switch (state) { + case TestState.Running: + return runningSource; case TestState.Failed: return failedSource; case TestState.Passed: diff --git a/xunit.runner.wpf/MainWindow.xaml b/xunit.runner.wpf/MainWindow.xaml index 9b44790..e85a916 100644 --- a/xunit.runner.wpf/MainWindow.xaml +++ b/xunit.runner.wpf/MainWindow.xaml @@ -277,6 +277,21 @@ VerticalAlignment="Center" /> + + + + + + + allTestCaseUniqueIDs = new HashSet(); private readonly ObservableCollection allTestCases = new ObservableCollection(); private readonly TraitCollectionView traitCollectionView = new TraitCollectionView(); + private readonly HashSet runningTestSet = new HashSet(); + private CancellationTokenSource filterCancellationTokenSource = new CancellationTokenSource(); private CancellationTokenSource cancellationTokenSource; @@ -52,7 +54,7 @@ public bool AutoReloadAssemblies public ObservableCollection RecentAssemblies { get; } = new ObservableCollection(); - private ImmutableList runningTests; + private ImmutableList testsToRun; public ICommand ExitCommand { get; } public ICommand WindowLoadedCommand { get; } @@ -153,10 +155,13 @@ private static bool TestCaseMatches(TestCaseViewModel testCase, SearchQuery sear } } - var noFilter = !(searchQuery.FilterFailedTests | searchQuery.FilterPassedTests | searchQuery.FilterSkippedTests); + var noFilter = !(searchQuery.FilterRunningTests | searchQuery.FilterFailedTests | searchQuery.FilterPassedTests | searchQuery.FilterSkippedTests); switch (testCase.State) { + case TestState.Running: + return noFilter || searchQuery.FilterRunningTests; + case TestState.Passed: return noFilter || searchQuery.FilterPassedTests; @@ -263,6 +268,13 @@ private TaskbarProgressBarState GetTaskBarState() } } + private int testsRunning = 0; + public int TestsRunning + { + get { return testsRunning; } + set { Set(ref testsRunning, value); } + } + private int testsPassed = 0; public int TestsPassed { @@ -560,12 +572,12 @@ private bool CanExecuteRunSelected() private async void OnExecuteRunAll() { - Debug.Assert(this.runningTests == null); + Debug.Assert(this.testsToRun == null); UpdateTestCaseInfo(useSelected: false); await ExecuteTestSessionOperation(RunFilteredTests); - this.runningTests = null; + this.testsToRun = null; } private List RunFilteredTests() @@ -575,13 +587,13 @@ private List RunFilteredTests() private async void OnExecuteRunSelected() { - Debug.Assert(this.runningTests == null); + Debug.Assert(this.testsToRun == null); Debug.Assert(this.SelectedTestCase != null); UpdateTestCaseInfo(useSelected: true); await ExecuteTestSessionOperation(RunSelectedTests); - this.runningTests = null; + this.testsToRun = null; } private List RunSelectedTests() @@ -593,37 +605,38 @@ private List RunTests(ImmutableList tests) { Debug.Assert(this.isBusy); Debug.Assert(this.cancellationTokenSource != null); - Debug.Assert(this.runningTests == null); + Debug.Assert(this.testsToRun == null); TestsCompleted = 0; + TestsRunning = 0; TestsPassed = 0; TestsFailed = 0; TestsSkipped = 0; CurrentRunState = TestState.NotRun; Output = string.Empty; - this.runningTests = tests; + this.testsToRun = tests; - foreach (var tc in this.runningTests) + foreach (var tc in this.testsToRun) { tc.State = TestState.NotRun; } - var runAll = this.runningTests.Count == this.allTestCases.Count; + var runAll = this.testsToRun.Count == this.allTestCases.Count; var testSessionList = new List(); - foreach (var assemblyFileName in this.runningTests.Select(x => x.AssemblyFileName).Distinct()) + foreach (var assemblyFileName in this.testsToRun.Select(x => x.AssemblyFileName).Distinct()) { Task task; if (runAll) { - task = this.testUtil.RunAll(assemblyFileName, OnTestsFinished, this.cancellationTokenSource.Token); + task = this.testUtil.RunAll(assemblyFileName, OnTestStateChange, this.cancellationTokenSource.Token); } else { var builder = ImmutableArray.CreateBuilder(); - foreach (var testCase in this.runningTests) + foreach (var testCase in this.testsToRun) { if (testCase.AssemblyFileName == assemblyFileName) { @@ -631,7 +644,7 @@ private List RunTests(ImmutableList tests) } } - task = this.testUtil.RunSpecific(assemblyFileName, builder.ToImmutable(), OnTestsFinished, this.cancellationTokenSource.Token); + task = this.testUtil.RunSpecific(assemblyFileName, builder.ToImmutable(), OnTestStateChange, this.cancellationTokenSource.Token); } testSessionList.Add(task); @@ -711,28 +724,43 @@ private void OnTestsDiscovered(IEnumerable testCases) } } - private void OnTestsFinished(IEnumerable testResultData) + private void OnTestStateChange(IEnumerable testResultData) { - Debug.Assert(this.runningTests != null); + Debug.Assert(this.testsToRun != null); foreach (var result in testResultData) { - var testCase = this.runningTests.Single(x => x.UniqueID == result.TestCaseUniqueID); + var testCase = this.testsToRun.Single(x => x.UniqueID == result.TestCaseUniqueID); testCase.State = result.TestState; - TestsCompleted++; - switch (result.TestState) + if (result.TestState == TestState.Running) { - case TestState.Passed: - TestsPassed++; - break; - case TestState.Failed: - TestsFailed++; - Output = Output + result.Output; - break; - case TestState.Skipped: - TestsSkipped++; - break; + if (runningTestSet.Add(result.TestCaseUniqueID)) + { + TestsRunning++; + } + } + else + { + if (runningTestSet.Remove(result.TestCaseUniqueID)) + { + TestsRunning--; + } + + TestsCompleted++; + switch (result.TestState) + { + case TestState.Passed: + TestsPassed++; + break; + case TestState.Failed: + TestsFailed++; + Output = Output + result.Output; + break; + case TestState.Skipped: + TestsSkipped++; + break; + } } if (result.TestState > CurrentRunState) @@ -820,6 +848,18 @@ private void UpdateAutoReloadStatus() } } + public bool FilterRunningTests + { + get { return searchQuery.FilterRunningTests; } + set + { + if (Set(ref searchQuery.FilterRunningTests, value)) + { + FilterAfterDelay(); + } + } + } + public bool FilterPassedTests { get { return searchQuery.FilterPassedTests; } diff --git a/xunit.runner.wpf/ViewModel/SearchQuery.cs b/xunit.runner.wpf/ViewModel/SearchQuery.cs index 2105fce..84570c5 100644 --- a/xunit.runner.wpf/ViewModel/SearchQuery.cs +++ b/xunit.runner.wpf/ViewModel/SearchQuery.cs @@ -4,6 +4,7 @@ namespace Xunit.Runner.Wpf.ViewModel { public class SearchQuery { + public bool FilterRunningTests = false; public bool FilterFailedTests = false; public bool FilterPassedTests = false; public bool FilterSkippedTests = false; diff --git a/xunit.runner.wpf/xunit.runner.wpf.csproj b/xunit.runner.wpf/xunit.runner.wpf.csproj index 28ef644..d902b82 100644 --- a/xunit.runner.wpf/xunit.runner.wpf.csproj +++ b/xunit.runner.wpf/xunit.runner.wpf.csproj @@ -203,6 +203,12 @@ + + + + + +