Skip to content

Commit e9edfc6

Browse files
committed
Pump messages in test engine between tests, rearrange status bar and test engine caches. Closes #3637
1 parent 1e84e4f commit e9edfc6

File tree

17 files changed

+440
-232
lines changed

17 files changed

+440
-232
lines changed

Rubberduck.Core/UI/UnitTesting/TestExplorerControl.xaml

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -378,8 +378,8 @@
378378
Grid.Row="1"
379379
Margin="2"
380380
Background="DimGray"
381-
Maximum="{Binding Model.Tests.Count, Mode=OneWay}"
382-
Value="{Binding Model.ExecutedCount, Mode=OneWay}">
381+
Maximum="{Binding Model.CurrentRunTestCount, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
382+
Value="{Binding Model.ExecutedCount, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
383383
<ProgressBar.Foreground>
384384
<SolidColorBrush Color="{Binding Model.ProgressBarColor, UpdateSourceTrigger=PropertyChanged}" />
385385
</ProgressBar.Foreground>
@@ -395,14 +395,26 @@
395395
<DataGridTemplateColumn Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_Outcome}">
396396
<DataGridTemplateColumn.CellTemplate>
397397
<DataTemplate DataType="vm:TestMethodViewModel">
398-
<Image Source="{Binding Result.Outcome, Converter={StaticResource OutcomeIconConverter}}" Height="16" />
398+
<Grid>
399+
<Image Height="16" Width="16">
400+
<Image.Source>
401+
<MultiBinding Converter="{StaticResource OutcomeIconConverter}">
402+
<MultiBinding.Bindings>
403+
<Binding RelativeSource="{RelativeSource Self}" Path="DataContext"/>
404+
<Binding Path="Result.Outcome" />
405+
<Binding Path="RunState" />
406+
</MultiBinding.Bindings>
407+
</MultiBinding>
408+
</Image.Source>
409+
</Image>
410+
</Grid>
399411
</DataTemplate>
400412
</DataGridTemplateColumn.CellTemplate>
401413
</DataGridTemplateColumn>
402414
<DataGridTemplateColumn Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_QualifiedModuleName}">
403415
<DataGridTemplateColumn.CellTemplate>
404416
<DataTemplate DataType="vm:TestMethodViewModel">
405-
<TextBlock></TextBlock>
417+
<TextBlock Text="{Binding Method.Declaration.QualifiedModuleName}" />
406418
</DataTemplate>
407419
</DataGridTemplateColumn.CellTemplate>
408420
</DataGridTemplateColumn>

Rubberduck.Core/UI/UnitTesting/TestExplorerModel.cs

Lines changed: 111 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,93 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Collections.ObjectModel;
34
using System.Linq;
45
using System.Windows.Media;
5-
using System.Windows.Threading;
6+
using NLog;
67
using Rubberduck.UI.UnitTesting.ViewModels;
78
using Rubberduck.UnitTesting;
8-
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
99

1010
namespace Rubberduck.UI.UnitTesting
1111
{
1212
internal class TestExplorerModel : ViewModelBase, IDisposable
1313
{
14-
private readonly IVBE _vbe;
15-
private readonly Dispatcher _dispatcher;
14+
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
15+
1616
private readonly ITestEngine _testEngine;
1717

18-
public TestExplorerModel(IVBE vbe, ITestEngine testEngine)
18+
public TestExplorerModel(ITestEngine testEngine)
1919
{
20-
_vbe = vbe;
2120
_testEngine = testEngine;
2221

2322
_testEngine.TestsRefreshed += HandleTestsRefreshed;
23+
_testEngine.TestRunStarted += HandleTestRunStarted;
24+
_testEngine.TestStarted += HandleTestStarted;
2425
_testEngine.TestRunCompleted += HandleRunCompletion;
2526
_testEngine.TestCompleted += HandleTestCompletion;
26-
_dispatcher = Dispatcher.CurrentDispatcher;
27+
}
28+
29+
public void ExecuteTests(IReadOnlyCollection<TestMethodViewModel> tests)
30+
{
31+
if (!tests.Any())
32+
{
33+
return;
34+
}
35+
36+
IsBusy = true;
37+
38+
try
39+
{
40+
_testEngine.Run(tests.Select(test => test.Method));
41+
}
42+
catch (Exception ex)
43+
{
44+
Logger.Warn(ex, "Test engine exception caught in TestExplorerModel.");
45+
}
46+
47+
IsBusy = false;
48+
}
49+
50+
private void HandleTestRunStarted(object sender, TestRunStartedEventArgs e)
51+
{
52+
if (e.Tests is null)
53+
{
54+
return;
55+
}
56+
57+
UpdateProgressBar(TestOutcome.Unknown, true);
58+
59+
var running = Tests.Where(test => e.Tests.Contains(test.Method)).ToList();
60+
61+
foreach (var test in running)
62+
{
63+
test.RunState = TestRunState.Queued;
64+
}
65+
}
66+
67+
private void HandleTestStarted(object sender, TestStartedEventArgs e)
68+
{
69+
var running = Tests.FirstOrDefault(test => test?.Method?.Equals(e.Test) ?? false);
70+
71+
if (running is null)
72+
{
73+
Logger.Warn($"{(e.Test is null ? "Null" : "Unexpected")} test result handled by TestExplorerModel.");
74+
return;
75+
}
76+
77+
running.RunState = TestRunState.Running;
2778
}
2879

2980
private void HandleRunCompletion(object sender, TestRunCompletedEventArgs e)
3081
{
3182
TotalDuration = e.Duration;
83+
84+
foreach (var test in Tests)
85+
{
86+
test.RunState = TestRunState.Stopped;
87+
}
88+
3289
ExecutedCount = Tests.Count(t => t.Result.Outcome != TestOutcome.Unknown);
90+
IsBusy = false;
3391
}
3492

3593
private void HandleTestCompletion(object sender, TestCompletedEventArgs e)
@@ -45,9 +103,11 @@ private void HandleTestCompletion(object sender, TestCompletedEventArgs e)
45103
{
46104
vmTest = Tests.First(inside => inside.Equals(vmTest));
47105
}
48-
vmTest.Result = e.Result;
49106

50-
RefreshProgressBarColor();
107+
vmTest.RunState = TestRunState.Stopped;
108+
vmTest.Result = e.Result;
109+
110+
UpdateProgressBar(vmTest.Result.Outcome);
51111
}
52112

53113
private void HandleTestsRefreshed(object sender, EventArgs args)
@@ -65,31 +125,52 @@ private void HandleTestsRefreshed(object sender, EventArgs args)
65125
}
66126
Tests.Add(adding);
67127
}
68-
RefreshProgressBarColor();
69128
}
70129

71-
private void RefreshProgressBarColor()
130+
private static readonly Dictionary<TestOutcome, Color> OutcomeColors = new Dictionary<TestOutcome, Color>
131+
{
132+
{ TestOutcome.Unknown, Colors.DimGray },
133+
{ TestOutcome.Succeeded, Colors.LimeGreen },
134+
{ TestOutcome.Inconclusive, Colors.Gold },
135+
{ TestOutcome.Ignored, Colors.Gold },
136+
{ TestOutcome.Failed, Colors.Red }
137+
};
138+
139+
private TestOutcome _bestOutcome = TestOutcome.Unknown;
140+
private void UpdateProgressBar(TestOutcome output, bool reset = false)
72141
{
73-
var overallOutcome = _testEngine.CurrentAggregateOutcome;
74-
switch (overallOutcome)
75-
{
76-
case TestOutcome.Failed:
77-
ProgressBarColor = Colors.Red;
78-
break;
79-
case TestOutcome.Inconclusive:
80-
ProgressBarColor = Colors.Gold;
81-
break;
82-
case TestOutcome.Succeeded:
83-
ProgressBarColor = Colors.LimeGreen;
84-
break;
85-
default:
86-
ProgressBarColor = Colors.DimGray;
87-
break;
142+
if (reset)
143+
{
144+
ExecutedCount = 0;
145+
CurrentRunTestCount = 0;
146+
ProgressBarColor = OutcomeColors[TestOutcome.Unknown];
147+
return;
148+
}
149+
150+
ExecutedCount++;
151+
152+
if (output <= _bestOutcome)
153+
{
154+
return;
88155
}
156+
157+
_bestOutcome = output;
158+
ProgressBarColor = OutcomeColors[output];
89159
}
90-
160+
91161
public ObservableCollection<TestMethodViewModel> Tests { get; } = new ObservableCollection<TestMethodViewModel>();
92-
162+
163+
private int _runCount;
164+
public int CurrentRunTestCount
165+
{
166+
get => _runCount;
167+
set
168+
{
169+
_runCount = value;
170+
OnPropertyChanged();
171+
}
172+
}
173+
93174
private long _totalDuration;
94175
public long TotalDuration
95176
{
@@ -138,7 +219,9 @@ public void Dispose()
138219
{
139220
if (_testEngine != null)
140221
{
222+
_testEngine.TestRunStarted -= HandleTestRunStarted;
141223
_testEngine.TestCompleted -= HandleTestCompletion;
224+
_testEngine.TestStarted -= HandleTestStarted;
142225
_testEngine.TestsRefreshed -= HandleTestsRefreshed;
143226
_testEngine.TestRunCompleted -= HandleRunCompletion;
144227
}

Rubberduck.Core/UI/UnitTesting/TestExplorerViewModel.cs

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ internal enum TestExplorerGrouping
2929

3030
internal sealed class TestExplorerViewModel : ViewModelBase, INavigateSelection, IDisposable
3131
{
32-
private readonly RubberduckParserState _state;
3332
private readonly ITestEngine _testEngine;
3433
private readonly IClipboardWriter _clipboard;
3534
private readonly ISettingsFormFactory _settingsFormFactory;
@@ -41,13 +40,12 @@ public TestExplorerViewModel(RubberduckParserState state,
4140
IGeneralConfigService configService,
4241
ISettingsFormFactory settingsFormFactory)
4342
{
44-
_state = state;
4543
_testEngine = testEngine;
4644
_testEngine.TestCompleted += TestEngineTestCompleted;
4745
_clipboard = clipboard;
4846
_settingsFormFactory = settingsFormFactory;
4947

50-
NavigateCommand = new NavigateCommand(_state.ProjectsProvider);
48+
NavigateCommand = new NavigateCommand(state.ProjectsProvider);
5149
RunSingleTestCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteSingleTestCommand, CanExecuteSingleTest);
5250
RunSelectedTestsCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteSelectedTestsCommand, CanExecuteSelectedTestsCommand);
5351
RunSelectedGroupCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteRunSelectedGroupCommand, CanExecuteSelectedGroupCommand);
@@ -190,7 +188,7 @@ private void ExecuteSingleTestCommand(object obj)
190188
return;
191189
}
192190

193-
ExecuteTests(new[] { MouseOverTest.Method });
191+
Model.ExecuteTests(new List<TestMethodViewModel> { MouseOverTest });
194192
}
195193

196194
private void ExecuteSelectedTestsCommand(object obj)
@@ -200,14 +198,14 @@ private void ExecuteSelectedTestsCommand(object obj)
200198
return;
201199
}
202200

203-
var models = viewModels.OfType<TestMethodViewModel>().Select(model => model.Method).ToArray();
201+
var models = viewModels.OfType<TestMethodViewModel>().ToList();
204202

205203
if (!models.Any())
206204
{
207205
return;
208206
}
209207

210-
ExecuteTests(models);
208+
Model.ExecuteTests(models);
211209
}
212210

213211
private void ExecuteRunSelectedGroupCommand(object obj)
@@ -221,14 +219,7 @@ private void ExecuteRunSelectedGroupCommand(object obj)
221219
return;
222220
}
223221

224-
ExecuteTests(tests.Items.OfType<TestMethodViewModel>().Select(t => t.Method));
225-
}
226-
227-
private void ExecuteTests(IEnumerable<TestMethod> tests)
228-
{
229-
Model.IsBusy = true;
230-
_testEngine.Run(tests);
231-
Model.IsBusy = false;
222+
Model.ExecuteTests(tests.Items.OfType<TestMethodViewModel>().ToList());
232223
}
233224

234225
private void ExecuteResetResultsCommand(object parameter)
@@ -300,6 +291,7 @@ private void OpenSettings(object param)
300291

301292
public void Dispose()
302293
{
294+
_testEngine.TestCompleted -= TestEngineTestCompleted;
303295
Model.Dispose();
304296
}
305297
}
Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,54 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Drawing;
43
using System.Globalization;
5-
using System.IO;
64
using System.Windows.Data;
75
using System.Windows.Media;
8-
using System.Windows.Media.Imaging;
96
using Rubberduck.UnitTesting;
107
using Rubberduck.Resources;
8+
using Rubberduck.UI.UnitTesting.ViewModels;
9+
using ImageSourceConverter = Rubberduck.UI.Converters.ImageSourceConverter;
1110

1211
namespace Rubberduck.UI.UnitTesting
1312
{
14-
public class TestOutcomeImageSourceConverter : IValueConverter
13+
public class TestOutcomeImageSourceConverter : ImageSourceConverter, IMultiValueConverter
1514
{
15+
private static readonly ImageSource QueuedIcon = ToImageSource(RubberduckUI.clock);
16+
private static readonly ImageSource RunningIcon = ToImageSource(RubberduckUI.hourglass);
17+
1618
private static readonly IDictionary<TestOutcome,ImageSource> Icons =
1719
new Dictionary<TestOutcome, ImageSource>
1820
{
1921
{ TestOutcome.Unknown, ToImageSource(RubberduckUI.question_white) },
2022
{ TestOutcome.Succeeded, ToImageSource(RubberduckUI.tick_circle) },
2123
{ TestOutcome.Failed, ToImageSource(RubberduckUI.cross_circle) },
2224
{ TestOutcome.Inconclusive, ToImageSource(RubberduckUI.exclamation) },
23-
{ TestOutcome.Ignored, ToImageSource(RubberduckUI.minus_white) },
25+
{ TestOutcome.Ignored, ToImageSource(RubberduckUI.minus_white) }
2426
};
2527

26-
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
28+
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
2729
{
28-
if (value?.GetType() != typeof(TestOutcome))
30+
if (!(value is TestMethodViewModel test))
2931
{
3032
return null;
3133
}
3234

33-
var outcome = (TestOutcome)value;
35+
if (test.RunState != TestRunState.Stopped)
36+
{
37+
return test.RunState == TestRunState.Running ? RunningIcon : QueuedIcon;
38+
}
39+
40+
var outcome = test.Result.Outcome;
3441
return Icons[outcome];
3542
}
3643

37-
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
44+
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
3845
{
39-
throw new NotImplementedException();
46+
return values.Length == 0 ? null : Convert(values[0], targetType, parameter, culture);
4047
}
4148

42-
private static ImageSource ToImageSource(Image source)
49+
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
4350
{
44-
var ms = new MemoryStream();
45-
((Bitmap)source).Save(ms, System.Drawing.Imaging.ImageFormat.Png);
46-
var image = new BitmapImage();
47-
image.BeginInit();
48-
ms.Seek(0, SeekOrigin.Begin);
49-
image.StreamSource = ms;
50-
image.EndInit();
51-
52-
return image;
51+
throw new NotImplementedException();
5352
}
5453
}
5554
}

0 commit comments

Comments
 (0)