10 Commits

Author SHA1 Message Date
Jared Parsons 13afb6eea5 Fixed the race condition in the run and discover tasks 2015-08-27 17:13:55 -07:00
Jared Parsons e3a17c5308 Respond to PR feedback
Handled everything but the race condition.  Going to fix that in a
separate commit.
2015-08-27 16:41:14 -07:00
Jared Parsons 89be98bebc Naming consistency 2015-08-23 23:09:23 -07:00
Jared Parsons 9e5ac70234 Fix reload bugs
Fixes a couple of bugs in the Reload / Remove logic.
2015-08-23 23:08:00 -07:00
Jared Parsons b35da545d6 Added remove all assemblies menu item 2015-08-23 23:01:54 -07:00
Jared Parsons 3a7d01b87e Add Assembly reload support
Can now reload individual or all currently loaded assemblies.

closes #2
2015-08-23 21:25:21 -07:00
Jared Parsons 0e24227de0 Ability to clear trait selection
This should fulfill issue #7
2015-08-23 21:00:28 -07:00
Jared Parsons 65f3fc970e Test run can be filtered by traits 2015-08-23 20:50:59 -07:00
Jared Parsons 06f1c8c703 Traits displaying in the UI 2015-08-23 19:20:34 -07:00
Jared Parsons 29aa127230 Merge pull request #19 from Pilchie/remote
Move discover and execution to a remote process
2015-08-22 12:02:28 -07:00
16 changed files with 435 additions and 295 deletions
+9 -2
View File
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
@@ -12,27 +13,33 @@ namespace xunit.runner.data
public string SerializedForm { get; set; }
public string DisplayName { get; set; }
public string AssemblyPath { get; set; }
public Dictionary<string, List<string>> TraitMap { get; set; }
public TestCaseData(string serializedForm, string displayName, string assemblyPath)
public TestCaseData(string serializedForm, string displayName, string assemblyPath, Dictionary<string, List<string>> traitMap)
{
SerializedForm = serializedForm;
DisplayName = displayName;
AssemblyPath = assemblyPath;
TraitMap = traitMap;
}
public static TestCaseData ReadFrom(BinaryReader reader)
{
var formatter = new BinaryFormatter();
var serializedForm = reader.ReadString();
var displayName = reader.ReadString();
var assemblyPath = reader.ReadString();
return new TestCaseData(serializedForm, displayName, assemblyPath);
var traitMap = (Dictionary<string, List<string>>)formatter.Deserialize(reader.BaseStream);
return new TestCaseData(serializedForm, displayName, assemblyPath, traitMap);
}
public void WriteTo(BinaryWriter writer)
{
var formatter = new BinaryFormatter();
writer.Write(SerializedForm);
writer.Write(DisplayName);
writer.Write(AssemblyPath);
formatter.Serialize(writer.BaseStream, TraitMap);
}
}
}
+4 -1
View File
@@ -16,11 +16,13 @@ namespace xunit.runner.worker
{
private readonly ITestFrameworkDiscoverer _discoverer;
private readonly ClientWriter _writer;
private readonly Dictionary<string, List<string>> _traitMap;
internal Impl(ITestFrameworkDiscoverer discoverer, ClientWriter writer)
{
_discoverer = discoverer;
_writer = writer;
_traitMap = new Dictionary<string, List<string>>(StringComparer.Ordinal);
}
protected override bool Visit(ITestCaseDiscoveryMessage testCaseDiscovered)
@@ -29,7 +31,8 @@ namespace xunit.runner.worker
var testCaseData = new TestCaseData(
_discoverer.Serialize(testCase),
testCase.DisplayName,
testCaseDiscovered.TestAssembly.Assembly.AssemblyPath);
testCaseDiscovered.TestAssembly.Assembly.AssemblyPath,
testCase.Traits);
Console.WriteLine(testCase.DisplayName);
_writer.Write(TestDataKind.Value);
+3 -62
View File
@@ -17,75 +17,16 @@ namespace xunit.runner.wpf
/// <summary>
/// Discover the list of test cases which are available in the specified assembly.
/// </summary>
ITestDiscoverSession Discover(string assemblyPath, CancellationToken cancellationToken = default(CancellationToken));
Task Discover(string assemblyPath, Action<TestCaseData> callback, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Begin a run of all unit tests for the given assembly.
/// </summary>
ITestRunSession RunAll(string assemblyPath, CancellationToken cancellationToken = default(CancellationToken));
Task RunAll(string assemblyPath, Action<TestResultData> callback, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Begin a run of specific unit tests for the given assembly.
/// </summary>
ITestRunSession RunSpecific(string assemblyPath, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken = default(CancellationToken));
Task RunSpecific(string assemblyPath, ImmutableArray<string> testCaseDisplayNames, Action<TestResultData> callback, CancellationToken cancellationToken = default(CancellationToken));
}
internal interface ITestSession
{
/// <summary>
/// Task which will be completed when the session is finished.
/// </summary>
Task Task { get; }
}
internal interface ITestRunSession : ITestSession
{
/// <summary>
/// Raised when an individual test is finished running.
/// </summary>
event EventHandler<TestResultDataEventArgs> TestFinished;
/// <summary>
/// Raised when the session has finished executing all of the specified tests.
/// </summary>
event EventHandler SessionFinished;
}
internal interface ITestDiscoverSession : ITestSession
{
/// <summary>
/// Raised when an individual test is finished running.
/// </summary>
event EventHandler<TestCaseDataEventArgs> TestDiscovered;
/// <summary>
/// Raised when the session has finished executing all of the specified tests.
/// </summary>
event EventHandler SessionFinished;
}
internal sealed class TestCaseDataEventArgs : EventArgs
{
internal readonly TestCaseData TestCaseData;
internal TestCaseDataEventArgs(TestCaseData data)
{
TestCaseData = data;
}
}
internal sealed class TestResultDataEventArgs : EventArgs
{
internal readonly TestResultData TestResultData;
internal string TestCaseDisplayName => TestResultData.TestCaseDisplayName;
internal TestState TestState => TestResultData.TestState;
internal string Output => TestResultData.Output;
internal TestResultDataEventArgs(TestResultData testResultData)
{
TestResultData = testResultData;
}
}
}
@@ -116,7 +116,7 @@ namespace xunit.runner.wpf.Impl
private readonly Connection _connection;
private readonly ConcurrentQueue<T> _queue;
private readonly DispatcherTimer _timer;
private readonly Action<List<T>> _callback;
private readonly Action<T> _callback;
private readonly int _maxPerTick;
private readonly TaskCompletionSource<bool> _taskCompletionSource;
@@ -126,7 +126,7 @@ namespace xunit.runner.wpf.Impl
Connection connection,
Dispatcher dispatcher,
ConcurrentQueue<T> queue,
Action<List<T>> callback,
Action<T> callback,
int maxResultPerTick = MaxResultPerTick,
TimeSpan? interval = null)
{
@@ -159,16 +159,15 @@ namespace xunit.runner.wpf.Impl
list.Add(value);
}
if (list.Count > 0)
{
_callback(list);
foreach (var cur in list)
{
_callback(cur);
}
if (isDone)
{
try
{
_callback(null);
_timer.Stop();
_connection.Dispose();
}
@@ -1,70 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
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;
using xunit.runner.wpf.ViewModel;
namespace xunit.runner.wpf.Impl
{
internal partial class RemoteTestUtil
{
private sealed class DiscoverSession : ITestDiscoverSession
{
private readonly Task _task;
private event EventHandler<TestCaseDataEventArgs> _testDiscovered;
private event EventHandler _sessionFinished;
internal DiscoverSession(Connection connection, Dispatcher dispatcher, CancellationToken cancellationToken)
{
var queue = new ConcurrentQueue<TestCaseData>();
var backgroundReader = new BackgroundReader<TestCaseData>(queue, new ClientReader(connection.Stream), r => r.ReadTestCaseData(), cancellationToken);
backgroundReader.ReadAsync();
var backgroundProducer = new BackgroundProducer<TestCaseData>(connection, dispatcher, queue, OnDiscovered);
_task = backgroundProducer.Task;
}
private void OnDiscovered(List<TestCaseData> list)
{
Debug.Assert(!_task.IsCompleted);
if (list == null)
{
_sessionFinished?.Invoke(this, EventArgs.Empty);
return;
}
foreach (var cur in list)
{
_testDiscovered?.Invoke(this, new TestCaseDataEventArgs(cur));
}
}
#region ITestRunSession
Task ITestSession.Task => _task;
event EventHandler<TestCaseDataEventArgs> ITestDiscoverSession.TestDiscovered
{
add { _testDiscovered += value; }
remove { _testDiscovered -= value; }
}
event EventHandler ITestDiscoverSession.SessionFinished
{
add { _sessionFinished += value; }
remove { _sessionFinished -= value; }
}
#endregion
}
}
}
@@ -1,100 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
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;
using xunit.runner.wpf.ViewModel;
namespace xunit.runner.wpf.Impl
{
internal partial class RemoteTestUtil
{
private sealed class RunSession : ITestRunSession
{
private readonly Task _task;
private event EventHandler<TestResultDataEventArgs> _testFinished;
private event EventHandler _sessionFinished;
internal RunSession(Connection connection, Dispatcher dispatcher, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken)
{
var queue = CreateQueue(connection, testCaseDisplayNames, cancellationToken);
var backgroundProducer = new BackgroundProducer<TestResultData>(connection, dispatcher, queue, OnDataProduced);
_task = backgroundProducer.Task;
}
/// <summary>
/// Create the <see cref="ConcurrentQueue{T}"/> which will be populated with the <see cref="TestResultData"/>
/// as it arrives from the worker.
/// </summary>
private static ConcurrentQueue<TestResultData> CreateQueue(Connection connection, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken)
{
var queue = new ConcurrentQueue<TestResultData>();
var unused = CreateQueueCore(queue, connection, testCaseDisplayNames, cancellationToken);
return queue;
}
private static async Task CreateQueueCore(ConcurrentQueue<TestResultData> queue, Connection connection, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken)
{
try
{
if (!testCaseDisplayNames.IsDefaultOrEmpty)
{
var backgroundWriter = new BackgroundWriter<string>(new ClientWriter(connection.Stream), testCaseDisplayNames, (w, s) => w.Write(s), cancellationToken);
await backgroundWriter.WriteAsync();
}
var backgroundReader = new BackgroundReader<TestResultData>(queue, new ClientReader(connection.Stream), r => r.ReadTestResultData(), cancellationToken);
await backgroundReader.ReadAsync();
}
catch (Exception ex)
{
Debug.Fail(ex.Message);
// Signal data completed
queue.Enqueue(null);
}
}
private void OnDataProduced(List<TestResultData> list)
{
Debug.Assert(!_task.IsCompleted);
if (list == null)
{
_sessionFinished?.Invoke(this, EventArgs.Empty);
return;
}
foreach (var cur in list)
{
_testFinished?.Invoke(this, new wpf.TestResultDataEventArgs(cur));
}
}
#region ITestRunSession
Task ITestSession.Task => _task;
event EventHandler<TestResultDataEventArgs> ITestRunSession.TestFinished
{
add { _testFinished += value; }
remove { _testFinished -= value; }
}
event EventHandler ITestRunSession.SessionFinished
{
add { _sessionFinished += value; }
remove { _sessionFinished -= value; }
}
#endregion
}
}
}
+47 -13
View File
@@ -46,39 +46,73 @@ namespace xunit.runner.wpf.Impl
}
}
private DiscoverSession Discover(string assemblyPath, CancellationToken cancellationToken)
private Task Discover(string assemblyPath, Action<TestCaseData> callback, CancellationToken cancellationToken)
{
var connection = StartWorkerProcess(Constants.ActionDiscover, assemblyPath);
return new DiscoverSession(connection, _dispatcher, cancellationToken);
var queue = new ConcurrentQueue<TestCaseData>();
var backgroundReader = new BackgroundReader<TestCaseData>(queue, new ClientReader(connection.Stream), r => r.ReadTestCaseData(), cancellationToken);
backgroundReader.ReadAsync();
var backgroundProducer = new BackgroundProducer<TestCaseData>(connection, _dispatcher, queue, callback);
return backgroundProducer.Task;
}
private RunSession RunAll(string assemblyPath, CancellationToken cancellationToken)
private Task RunCore(string actionName, string assemblyPath, ImmutableArray<string> testCaseDisplayNames, Action<TestResultData> callback, CancellationToken cancellationToken)
{
var connection = StartWorkerProcess(Constants.ActionRunAll, assemblyPath);
return new RunSession(connection, _dispatcher, ImmutableArray<string>.Empty, cancellationToken);
var queue = CreateRunQueue(connection, testCaseDisplayNames, cancellationToken);
var backgroundProducer = new BackgroundProducer<TestResultData>(connection, _dispatcher, queue, callback);
return backgroundProducer.Task;
}
private RunSession RunSpecific(string assemblyPath, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken)
/// <summary>
/// Create the <see cref="ConcurrentQueue{T}"/> which will be populated with the <see cref="TestResultData"/>
/// as it arrives from the worker.
/// </summary>
private static ConcurrentQueue<TestResultData> CreateRunQueue(Connection connection, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken)
{
var connection = StartWorkerProcess(Constants.ActionRunSpecific, assemblyPath);
return new RunSession(connection, _dispatcher, testCaseDisplayNames, cancellationToken);
var queue = new ConcurrentQueue<TestResultData>();
var unused = CreateRunQueueCore(queue, connection, testCaseDisplayNames, cancellationToken);
return queue;
}
private static async Task CreateRunQueueCore(ConcurrentQueue<TestResultData> queue, Connection connection, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken)
{
try
{
if (!testCaseDisplayNames.IsDefaultOrEmpty)
{
var backgroundWriter = new BackgroundWriter<string>(new ClientWriter(connection.Stream), testCaseDisplayNames, (w, s) => w.Write(s), cancellationToken);
await backgroundWriter.WriteAsync();
}
var backgroundReader = new BackgroundReader<TestResultData>(queue, new ClientReader(connection.Stream), r => r.ReadTestResultData(), cancellationToken);
await backgroundReader.ReadAsync();
}
catch (Exception ex)
{
Debug.Fail(ex.Message);
// Signal data completed
queue.Enqueue(null);
}
}
#region ITestUtil
ITestDiscoverSession ITestUtil.Discover(string assemblyPath, CancellationToken cancellationToken)
Task ITestUtil.Discover(string assemblyPath, Action<TestCaseData> callback, CancellationToken cancellationToken)
{
return Discover(assemblyPath, cancellationToken);
return Discover(assemblyPath, callback, cancellationToken);
}
ITestRunSession ITestUtil.RunAll(string assemblyPath, CancellationToken cancellationToken)
Task ITestUtil.RunAll(string assemblyPath, Action<TestResultData> callback, CancellationToken cancellationToken)
{
return RunAll(assemblyPath, cancellationToken);
return RunCore(Constants.ActionRunAll, assemblyPath, ImmutableArray<string>.Empty, callback, cancellationToken);
}
ITestRunSession ITestUtil.RunSpecific(string assemblyPath, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken)
Task ITestUtil.RunSpecific(string assemblyPath, ImmutableArray<string> testCaseDisplayNames, Action<TestResultData> callback, CancellationToken cancellationToken)
{
return RunSpecific(assemblyPath, testCaseDisplayNames, cancellationToken);
return RunCore(Constants.ActionRunSpecific, assemblyPath, testCaseDisplayNames, callback, cancellationToken);
}
#endregion
+37 -1
View File
@@ -95,10 +95,46 @@
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Reload" Command="{Binding AssemblyReloadCommand}" />
<MenuItem Header="Reload All" Command="{Binding AssemblyReloadAllCommand}" />
<Separator />
<MenuItem Header="Remove" Command="{Binding AssemblyRemoveCommand}" />
<MenuItem Header="Remove All" Command="{Binding AssemblyRemoveAllCommand}" />
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
<Label Content="Traits:"
Grid.Row="4" />
<ListBox Grid.Row="5" />
<ListBox Grid.Row="5"
ItemsSource="{Binding Traits}">
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:TraitViewModel">
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Clear" Command="{Binding TraitsClearCommand}" />
</ContextMenu>
</ListBox.ContextMenu>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding TraitSelectionChangedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
</Grid>
</GroupBox>
+177 -28
View File
@@ -24,6 +24,7 @@ namespace xunit.runner.wpf.ViewModel
{
private readonly ITestUtil testUtil;
private readonly ObservableCollection<TestCaseViewModel> allTestCases = new ObservableCollection<TestCaseViewModel>();
private readonly TraitCollectionView traitCollectionView = new TraitCollectionView();
private CancellationTokenSource filterCancellationTokenSource = new CancellationTokenSource();
private CancellationTokenSource cancellationTokenSource;
@@ -48,6 +49,12 @@ namespace xunit.runner.wpf.ViewModel
this.WindowLoadedCommand = new RelayCommand(OnExecuteWindowLoaded);
this.RunCommand = new RelayCommand(OnExecuteRun, CanExecuteRun);
this.CancelCommand = new RelayCommand(OnExecuteCancel, CanExecuteCancel);
this.TraitSelectionChangedCommand = new RelayCommand(OnExecuteTraitSelectionChanged);
this.TraitsClearCommand = new RelayCommand(OnExecuteTraitsClear);
this.AssemblyReloadCommand = new RelayCommand(OnExecuteAssemblyReload, CanExecuteAssemblyReload);
this.AssemblyReloadAllCommand = new RelayCommand(OnExecuteAssemblyReloadAll);
this.AssemblyRemoveCommand = new RelayCommand(OnExecuteAssemblyRemove, CanExecuteAssemblyRemove);
this.AssemblyRemoveAllCommand = new RelayCommand(OnExecuteAssemblyRemoveAll);
}
private static bool TestCaseMatches(TestCaseViewModel testCase, SearchQuery searchQuery)
@@ -57,6 +64,24 @@ namespace xunit.runner.wpf.ViewModel
return false;
}
if (searchQuery.TraitSet.Count > 0)
{
var anyMatch = false;
foreach (var cur in testCase.Traits)
{
if (searchQuery.TraitSet.Contains(cur))
{
anyMatch = true;
break;
}
}
if (!anyMatch)
{
return false;
}
}
switch (testCase.State)
{
case TestState.Passed:
@@ -87,9 +112,20 @@ namespace xunit.runner.wpf.ViewModel
public ICommand WindowLoadedCommand { get; }
public RelayCommand RunCommand { get; }
public RelayCommand CancelCommand { get; }
public ICommand TraitSelectionChangedCommand { get; }
public ICommand TraitsClearCommand { get; }
public ICommand AssemblyReloadCommand { get; }
public ICommand AssemblyReloadAllCommand { get; }
public ICommand AssemblyRemoveCommand { get; }
public ICommand AssemblyRemoveAllCommand { get; }
public CommandBindingCollection CommandBindings { get; }
public List<TestAssemblyViewModel> SelectedAssemblies
{
get { return Assemblies.Where(x => x.IsSelected).ToList(); }
}
private string methodsCaption;
public string MethodsCaption
{
@@ -189,6 +225,7 @@ namespace xunit.runner.wpf.ViewModel
public ObservableCollection<TestAssemblyViewModel> Assemblies { get; } = new ObservableCollection<TestAssemblyViewModel>();
public FilteredCollectionView<TestCaseViewModel, SearchQuery> TestCases { get; }
public ObservableCollection<TraitViewModel> Traits => this.traitCollectionView.Collection;
private async void OnExecuteOpen(object sender, ExecutedRoutedEventArgs e)
{
@@ -218,18 +255,15 @@ namespace xunit.runner.wpf.ViewModel
{
await ExecuteTestSessionOperation(() =>
{
var testSessionList = new List<ITestSession>();
var taskList = new List<Task>();
foreach (var assembly in assemblies)
{
var assemblyPath = assembly.AssemblyFileName;
var session = this.testUtil.Discover(assemblyPath, cancellationTokenSource.Token);
session.TestDiscovered += OnTestDiscovered;
testSessionList.Add(session);
taskList.Add(this.testUtil.Discover(assemblyPath, OnTestDiscovered, cancellationTokenSource.Token));
Assemblies.Add(new TestAssemblyViewModel(assembly));
}
return testSessionList;
return taskList;
});
}
finally
@@ -238,6 +272,75 @@ namespace xunit.runner.wpf.ViewModel
}
}
private async Task ReloadAssemblies(IEnumerable<TestAssemblyViewModel> assemblies)
{
var loadingDialog = new LoadingDialog { Owner = MainWindow.Instance };
try
{
await ExecuteTestSessionOperation(() =>
{
var taskList = new List<Task>();
foreach (var assembly in assemblies)
{
var assemblyPath = assembly.FileName;
RemoveAssemblyTestCases(assemblyPath);
taskList.Add(this.testUtil.Discover(assemblyPath, OnTestDiscovered, cancellationTokenSource.Token));
}
return taskList;
});
RebuildTraits();
}
finally
{
loadingDialog.Close();
}
}
private void RemoveAssemblies(IEnumerable<TestAssemblyViewModel> assemblies)
{
foreach (var assembly in assemblies.ToList())
{
RemoveAssemblyTestCases(assembly.FileName);
Assemblies.Remove(assembly);
}
RebuildTraits();
}
private void RemoveAssemblyTestCases(string assemblyPath)
{
var i = 0;
while (i < this.allTestCases.Count)
{
if (this.allTestCases[i].AssemblyFileName == assemblyPath)
{
this.allTestCases.RemoveAt(i);
}
else
{
i++;
}
}
}
/// <summary>
/// Reloading an assembly could have changed the traits. There is no easy way
/// to selectively edit this list (traits can cross assembly boundaries). Just
/// do a full reload instead.
/// way to
/// </summary>
private void RebuildTraits()
{
this.traitCollectionView.Collection.Clear();
foreach (var testCase in this.allTestCases)
{
this.traitCollectionView.Add(testCase.Traits);
}
}
private bool IsBusy
{
get { return isBusy; }
@@ -289,7 +392,7 @@ namespace xunit.runner.wpf.ViewModel
await ExecuteTestSessionOperation(RunTests);
}
private List<ITestSession> RunTests()
private List<Task> RunTests()
{
Debug.Assert(this.isBusy);
Debug.Assert(this.cancellationTokenSource != null);
@@ -306,17 +409,15 @@ namespace xunit.runner.wpf.ViewModel
tc.State = TestState.NotRun;
}
// TODO: Need a way to filter based on traits
var runAll = TestCases.Count == this.allTestCases.Count;
var testSessionList = new List<ITestSession>();
var testSessionList = new List<Task>();
foreach (var assemblyPath in TestCases.Select(x => x.AssemblyFileName).Distinct())
{
ITestRunSession session;
Task task;
if (runAll)
{
session = this.testUtil.RunAll(assemblyPath, this.cancellationTokenSource.Token);
task = this.testUtil.RunAll(assemblyPath, OnTestFinished, this.cancellationTokenSource.Token);
}
else
{
@@ -324,17 +425,16 @@ namespace xunit.runner.wpf.ViewModel
.Where(x => x.AssemblyFileName == assemblyPath)
.Select(x => x.DisplayName)
.ToImmutableArray();
session = this.testUtil.RunSpecific(assemblyPath, testCaseDisplayNames, this.cancellationTokenSource.Token);
task = this.testUtil.RunSpecific(assemblyPath, testCaseDisplayNames, OnTestFinished, this.cancellationTokenSource.Token);
}
session.TestFinished += OnTestFinished;
testSessionList.Add(session);
testSessionList.Add(task);
}
return testSessionList;
}
private async Task ExecuteTestSessionOperation(Func<List<ITestSession>> operation)
private async Task ExecuteTestSessionOperation(Func<List<Task>> operation)
{
Debug.Assert(!this.IsBusy);
Debug.Assert(this.cancellationTokenSource == null);
@@ -344,8 +444,8 @@ namespace xunit.runner.wpf.ViewModel
this.IsBusy = true;
this.cancellationTokenSource = new CancellationTokenSource();
var testSessionList = operation();
await Task.WhenAll(testSessionList.Select(x => x.Task));
var taskList = operation();
await Task.WhenAll(taskList);
}
catch (Exception ex)
{
@@ -359,35 +459,38 @@ namespace xunit.runner.wpf.ViewModel
}
}
private void OnTestDiscovered(object sender, TestCaseDataEventArgs e)
private void OnTestDiscovered(TestCaseData testCaseData)
{
var t = e.TestCaseData;
allTestCases.Add(new TestCaseViewModel(t.SerializedForm, t.DisplayName, t.AssemblyPath));
var traitList = testCaseData.TraitMap.Count == 0
? ImmutableArray<TraitViewModel>.Empty
: testCaseData.TraitMap.SelectMany(pair => pair.Value.Select(value => new TraitViewModel(pair.Key, value))).ToImmutableArray();
this.allTestCases.Add(new TestCaseViewModel(testCaseData.SerializedForm, testCaseData.DisplayName, testCaseData.AssemblyPath, traitList));
this.traitCollectionView.Add(traitList);
}
private void OnTestFinished(object sender, TestResultDataEventArgs e)
private void OnTestFinished(TestResultData testResultData)
{
var testCase = TestCases.Single(x => x.DisplayName == e.TestCaseDisplayName);
testCase.State = e.TestState;
var testCase = TestCases.Single(x => x.DisplayName == testResultData.TestCaseDisplayName);
testCase.State = testResultData.TestState;
TestsCompleted++;
switch (e.TestState)
switch (testResultData.TestState)
{
case TestState.Passed:
TestsPassed++;
break;
case TestState.Failed:
TestsFailed++;
Output = Output + e.Output;
Output = Output + testResultData.Output;
break;
case TestState.Skipped:
TestsSkipped++;
break;
}
if (e.TestState > CurrentRunState)
if (testResultData.TestState > CurrentRunState)
{
CurrentRunState = e.TestState;
CurrentRunState = testResultData.TestState;
}
}
@@ -402,6 +505,52 @@ namespace xunit.runner.wpf.ViewModel
this.cancellationTokenSource.Cancel();
}
private void OnExecuteTraitSelectionChanged()
{
this.searchQuery.TraitSet = new HashSet<TraitViewModel>(
this.traitCollectionView.Collection.Where(x => x.IsSelected),
TraitViewModelComparer.Instance);
FilterAfterDelay();
}
private void OnExecuteTraitsClear()
{
foreach (var cur in this.traitCollectionView.Collection)
{
cur.IsSelected = false;
}
}
private bool CanExecuteAssemblyReload()
{
return SelectedAssemblies.Count > 0;
}
private async void OnExecuteAssemblyReload()
{
await ReloadAssemblies(SelectedAssemblies);
}
private async void OnExecuteAssemblyReloadAll()
{
await ReloadAssemblies(Assemblies);
}
private bool CanExecuteAssemblyRemove()
{
return SelectedAssemblies.Count > 0;
}
private void OnExecuteAssemblyRemove()
{
RemoveAssemblies(SelectedAssemblies);
}
private void OnExecuteAssemblyRemoveAll()
{
RemoveAssemblies(Assemblies.ToArray());
}
public bool IncludePassedTests
{
get { return searchQuery.IncludePassedTests; }
+1 -1
View File
@@ -11,7 +11,7 @@ namespace xunit.runner.wpf.ViewModel
public bool IncludeFailedTests = true;
public bool IncludePassedTests = true;
public bool IncludeSkippedTests = true;
public string SearchString = string.Empty;
public HashSet<TraitViewModel> TraitSet = new HashSet<TraitViewModel>(TraitViewModelComparer.Instance);
}
}
@@ -10,15 +10,22 @@ namespace xunit.runner.wpf.ViewModel
{
public class TestAssemblyViewModel : ViewModelBase
{
private readonly AssemblyAndConfigFile assembly;
private readonly AssemblyAndConfigFile _assembly;
private bool _isSelected;
public TestAssemblyViewModel(AssemblyAndConfigFile assembly)
{
this.assembly = assembly;
_assembly = assembly;
}
public string FileName => assembly.AssemblyFileName;
public string ConfigFileName => Path.GetFileNameWithoutExtension(assembly.ConfigFileName);
public string DisplayName => Path.GetFileNameWithoutExtension(assembly.AssemblyFileName);
public string FileName => _assembly.AssemblyFileName;
public string ConfigFileName => Path.GetFileNameWithoutExtension(_assembly.ConfigFileName);
public string DisplayName => Path.GetFileNameWithoutExtension(_assembly.AssemblyFileName);
public bool IsSelected
{
get { return _isSelected; }
set { Set(ref _isSelected, value, nameof(IsSelected)); }
}
}
}
@@ -1,6 +1,7 @@
using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
@@ -11,24 +12,28 @@ namespace xunit.runner.wpf.ViewModel
{
public class TestCaseViewModel : ViewModelBase
{
public TestCaseViewModel(string testCase, string displayName, string assemblyFileName)
private TestState _state = TestState.NotRun;
public TestCaseViewModel(string testCase, string displayName, string assemblyFileName, ImmutableArray<TraitViewModel> traits)
{
this.TestCase = testCase;
this.DisplayName = displayName;
this.AssemblyFileName = assemblyFileName;
this.Traits = traits;
}
public string DisplayName { get; }
private TestState state = TestState.NotRun;
public TestState State
{
get { return state; }
set { Set(ref state, value); }
get { return _state; }
set { Set(ref _state, value); }
}
public string AssemblyFileName { get; }
public string TestCase { get; }
public ImmutableArray<TraitViewModel> Traits { get; }
}
}
@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace xunit.runner.wpf.ViewModel
{
public sealed partial class TraitCollectionView
{
private readonly TraitViewModelComparer _comparer = TraitViewModelComparer.Instance;
private readonly ObservableCollection<TraitViewModel> _collection = new ObservableCollection<TraitViewModel>();
public ObservableCollection<TraitViewModel> Collection => _collection;
public TraitCollectionView()
{
}
public void Add(ImmutableArray<TraitViewModel> traitList)
{
if (traitList.IsDefaultOrEmpty)
{
return;
}
foreach (var traitViewModel in traitList)
{
InsertIfNotPresent(traitViewModel);
}
}
private void InsertIfNotPresent(TraitViewModel trait)
{
// TODO: make it a binary search
for (int i = 0; i < _collection.Count; i++)
{
var current = _collection[i];
var result = _comparer.Compare(trait, current);
if (result < 0)
{
_collection.Insert(i, trait);
return;
}
if (result == 0)
{
return;
}
}
_collection.Add(trait);
}
}
}
@@ -0,0 +1,31 @@
using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace xunit.runner.wpf.ViewModel
{
public sealed class TraitViewModel : ViewModelBase
{
private bool _isSelected;
public string Name { get; }
public string Value { get; }
public string DisplayName { get; }
public bool IsSelected
{
get { return _isSelected; }
set { Set(ref _isSelected, value); }
}
public TraitViewModel(string name, string value)
{
Name = name;
Value = value;
DisplayName = $"{Name}={Value}";
}
}
}
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace xunit.runner.wpf.ViewModel
{
internal sealed class TraitViewModelComparer : IEqualityComparer<TraitViewModel>, IComparer<TraitViewModel>
{
internal static readonly TraitViewModelComparer Instance = new TraitViewModelComparer();
private readonly StringComparer _comparer = StringComparer.Ordinal;
public int Compare(TraitViewModel x, TraitViewModel y)
{
var result = _comparer.Compare(x.Name, y.Name);
if (result != 0)
{
return result;
}
return _comparer.Compare(x.Value, y.Value);
}
public bool Equals(TraitViewModel x, TraitViewModel y)
{
return _comparer.Equals(x.Name, y.Name)
&& _comparer.Equals(x.Value, y.Value);
}
public int GetHashCode(TraitViewModel obj)
{
return obj.Name.GetHashCode();
}
}
}
+3 -2
View File
@@ -86,19 +86,20 @@
<Compile Include="Extensions.cs" />
<Compile Include="FilteredCollectionView.cs" />
<Compile Include="Impl\RemoteTestUtil.Connection.cs" />
<Compile Include="Impl\RemoteTestUtil.DiscoverSession.cs" />
<Compile Include="Impl\RemoteTestUtil.RunSession.cs" />
<Compile Include="Impl\RemoteTestUtil.BackgroundRunner.cs" />
<Compile Include="Impl\RemoteTestUtil.cs" />
<Compile Include="ITestUtil.cs" />
<Compile Include="LoadingDialog.xaml.cs">
<DependentUpon>LoadingDialog.xaml</DependentUpon>
</Compile>
<Compile Include="ViewModel\TraitCollectionView.cs" />
<Compile Include="ViewModel\AssemblyAndConfigFile.cs" />
<Compile Include="ViewModel\MainViewModel.cs" />
<Compile Include="ViewModel\SearchQuery.cs" />
<Compile Include="ViewModel\TestCaseViewModel.cs" />
<Compile Include="ViewModel\TestAssemblyViewModel.cs" />
<Compile Include="ViewModel\TraitViewModel.cs" />
<Compile Include="ViewModel\TraitViewModelComparer.cs" />
<Compile Include="ViewModel\ViewModelLocator.cs" />
<Page Include="LoadingDialog.xaml">
<SubType>Designer</SubType>