Compare commits
46 Commits
v1.0.10
...
UseNullable
| Author | SHA1 | Date | |
|---|---|---|---|
| a96ce278d2 | |||
| a10c13626c | |||
| e9dfb23e18 | |||
| f6e8918bef | |||
| 18275067e9 | |||
| 39f89587fc | |||
| 8c3c86d918 | |||
| 761f801e43 | |||
| 643d9061c8 | |||
| 6d4a249a97 | |||
| 5ed3579b30 | |||
| 1f55afd2e9 | |||
| c6c48067a7 | |||
| ab5b3afea2 | |||
| feb8588961 | |||
| 08907d707c | |||
| de354d8095 | |||
| dd668f753b | |||
| a5712e104a | |||
| f2e1c0fb5e | |||
| 80fc0702d1 | |||
| 39c5a33d56 | |||
| 686404bb7f | |||
| e41071ddb0 | |||
| 13f0cedb64 | |||
| 93601521fc | |||
| 24522e8749 | |||
| 070d3bcb5e | |||
| 78501d237a | |||
| 1e2fe65dfa | |||
| 0a52154c60 | |||
| d26343057f | |||
| ba398d66ad | |||
| 412c9b78fe | |||
| 8eea004410 | |||
| e52d9195ef | |||
| 80a52c0f30 | |||
| a3ceb785fa | |||
| 9a94bfd08d | |||
| cd411e5d56 | |||
| ed8641a896 | |||
| bb9ed9106b | |||
| 5e966ff461 | |||
| 83bd1be9bf | |||
| 84b0c7b4c8 | |||
| 7dc9b5d7d1 |
@@ -214,3 +214,4 @@ FakesAssemblies/
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
/Settings.XamlStyle
|
||||
|
||||
@@ -3,4 +3,8 @@ XUnit Gui written in WPF
|
||||
|
||||
A simple replacement for the old winforms xunit.gui with support for xunit 2.0.
|
||||
|
||||
Find it on NuGet at [xunit.runner.wpf](https://www.nuget.org/packages/xunit.runner.wpf).
|
||||
|
||||

|
||||
|
||||
[](https://ci.appveyor.com/project/Pilchie/xunit-runner-wpf/branch/master)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
Steps to make a new release
|
||||
===========================
|
||||
|
||||
1. Make sure no one else is planning on doing anything that would trigger a build
|
||||
2. Check the "next build number" on [AppVeyor](https://ci.appveyor.com/project/Pilchie/xunit-runner-wpf/settings)
|
||||
3. Click on [releases](https://github.com/Pilchie/xunit.runner.wpf/releases) -> `Draft a new release`
|
||||
4. Set the version to `v1.0.nextbuildnumber` from 2
|
||||
5. Set the title to `v1.0.nextbuildnumber - Some reason for the release to exist`
|
||||
6. Click `Publish`
|
||||
7. This will create the release, and start a new build on AppVeyor
|
||||
8. Download the .nupkg from the AppVeyor artifacts page for that new build - (e.g. https://ci.appveyor.com/project/Pilchie/xunit-runner-wpf/build/1.0.15/artifacts)
|
||||
9. Go back to the release you created in 6, and add the nupkg, and write a changelog
|
||||
10. Tell [@Pilchie](https://github.com/Pilchie) to upload the nupkg to NuGet.org
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 127 KiB |
@@ -7,7 +7,7 @@ namespace Xunit.Runner.Data
|
||||
{
|
||||
private readonly BinaryReader _reader;
|
||||
private bool _closed;
|
||||
private Exception _exception;
|
||||
private Exception? _exception;
|
||||
|
||||
public bool IsConnected => !_closed;
|
||||
|
||||
|
||||
@@ -6,13 +6,15 @@ namespace Xunit.Runner.Data
|
||||
public sealed class TestCaseData
|
||||
{
|
||||
public string DisplayName { get; set; }
|
||||
public string UniqueID { get; set; }
|
||||
public string SkipReason { get; set; }
|
||||
public string AssemblyPath { get; set; }
|
||||
public Dictionary<string, List<string>> TraitMap { get; set; }
|
||||
|
||||
public TestCaseData(string displayName, string skipReason, string assemblyPath, Dictionary<string, List<string>> traitMap)
|
||||
public TestCaseData(string displayName, string uniqueID, string skipReason, string assemblyPath, Dictionary<string, List<string>> traitMap)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
UniqueID = uniqueID;
|
||||
SkipReason = skipReason;
|
||||
AssemblyPath = assemblyPath;
|
||||
TraitMap = traitMap;
|
||||
@@ -21,6 +23,7 @@ namespace Xunit.Runner.Data
|
||||
public static TestCaseData ReadFrom(BinaryReader reader)
|
||||
{
|
||||
var displayName = reader.ReadString();
|
||||
var uniqueID = reader.ReadString();
|
||||
var skipReason = reader.ReadString();
|
||||
var assemblyPath = reader.ReadString();
|
||||
var count = reader.ReadInt32();
|
||||
@@ -40,12 +43,13 @@ namespace Xunit.Runner.Data
|
||||
traitMap.Add(key, values);
|
||||
}
|
||||
|
||||
return new TestCaseData(displayName, skipReason, assemblyPath, traitMap);
|
||||
return new TestCaseData(displayName, uniqueID, skipReason, assemblyPath, traitMap);
|
||||
}
|
||||
|
||||
public void WriteTo(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(DisplayName);
|
||||
writer.Write(UniqueID);
|
||||
writer.Write(SkipReason ?? string.Empty);
|
||||
writer.Write(AssemblyPath);
|
||||
writer.Write(TraitMap.Count);
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Xunit.Runner.Data
|
||||
{
|
||||
All = 0,
|
||||
NotRun,
|
||||
Running,
|
||||
Passed,
|
||||
Skipped,
|
||||
Failed,
|
||||
@@ -18,12 +19,14 @@ namespace Xunit.Runner.Data
|
||||
public sealed class TestResultData
|
||||
{
|
||||
public string TestCaseDisplayName { get; set; }
|
||||
public string TestCaseUniqueID { get; set; }
|
||||
public TestState TestState { get; set; }
|
||||
public string Output { get; set; }
|
||||
|
||||
public TestResultData(string displayName, TestState state, string output = "")
|
||||
public TestResultData(string displayName, string uniqueID, TestState state, string output = "")
|
||||
{
|
||||
TestCaseDisplayName = displayName;
|
||||
TestCaseUniqueID = uniqueID;
|
||||
TestState = state;
|
||||
Output = output;
|
||||
}
|
||||
@@ -31,14 +34,16 @@ namespace Xunit.Runner.Data
|
||||
public static TestResultData ReadFrom(BinaryReader reader)
|
||||
{
|
||||
var displayName = reader.ReadString();
|
||||
var uniqueID = reader.ReadString();
|
||||
var state = (TestState)reader.ReadInt32();
|
||||
var output = reader.ReadString();
|
||||
return new TestResultData(displayName, state, output);
|
||||
return new TestResultData(displayName, uniqueID, state, output);
|
||||
}
|
||||
|
||||
public void WriteTo(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(TestCaseDisplayName);
|
||||
writer.Write(TestCaseUniqueID);
|
||||
writer.Write((int)TestState);
|
||||
writer.Write(Output);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Xunit.Runner.Worker
|
||||
var testCase = testCaseDiscovered.TestCase;
|
||||
var testCaseData = new TestCaseData(
|
||||
testCase.DisplayName,
|
||||
testCase.UniqueID,
|
||||
testCase.SkipReason,
|
||||
testCaseDiscovered.TestAssembly.Assembly.AssemblyPath,
|
||||
testCase.Traits);
|
||||
@@ -34,7 +35,7 @@ namespace Xunit.Runner.Worker
|
||||
|
||||
internal static void Go(string assemblyFileName, Stream stream)
|
||||
{
|
||||
Go(assemblyFileName, stream, AppDomainSupport.Denied,
|
||||
Go(assemblyFileName, stream, AppDomainSupport.IfAvailable,
|
||||
(xunit, configuration, writer) =>
|
||||
{
|
||||
using (var sink = new TestDiscoverySink(writer))
|
||||
|
||||
@@ -19,6 +19,12 @@ namespace Xunit.Runner.Worker.MessageSinks
|
||||
|
||||
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 @@ namespace Xunit.Runner.Worker.MessageSinks
|
||||
|
||||
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);
|
||||
|
||||
@@ -37,8 +37,8 @@ namespace Xunit.Runner.Worker
|
||||
{
|
||||
Console.WriteLine("xunit.runner.worker [pipe name] [action] [assembly path]");
|
||||
Console.WriteLine("\tpipe name: Name of the pipe this worker should communicate on");
|
||||
Console.WriteLine("\taction: Action performed by the worker (run or discover tests");
|
||||
Console.WriteLine("\assembly path: Path of assembly to perform the action against");
|
||||
Console.WriteLine("\taction: Action performed by the worker (run or discover tests)");
|
||||
Console.WriteLine("\tassembly path: Path of assembly to perform the action against");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,15 +21,20 @@ namespace Xunit.Runner.Worker
|
||||
|
||||
protected override bool ShouldContinue => _writer.IsConnected;
|
||||
|
||||
private void Process(string displayName, TestState state, string output = "")
|
||||
private void Process(string displayName, string uniqueID, TestState state, string output = "")
|
||||
{
|
||||
Console.WriteLine($"{state} - {displayName}");
|
||||
var result = new TestResultData(displayName, state, output);
|
||||
System.Diagnostics.Trace.WriteLine($"{state} - {displayName}");
|
||||
var result = new TestResultData(displayName, uniqueID, state, output);
|
||||
|
||||
_writer.Write(TestDataKind.Value);
|
||||
_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;
|
||||
@@ -46,35 +51,35 @@ namespace Xunit.Runner.Worker
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
Process(testFailed.TestCase.DisplayName, TestState.Failed, builder.ToString());
|
||||
Process(testFailed.TestCase.DisplayName, testFailed.TestCase.UniqueID, TestState.Failed, builder.ToString());
|
||||
}
|
||||
|
||||
protected override void OnTestPassed(ITestPassed testPassed)
|
||||
{
|
||||
Process(testPassed.TestCase.DisplayName, TestState.Passed);
|
||||
Process(testPassed.TestCase.DisplayName, testPassed.TestCase.UniqueID, TestState.Passed);
|
||||
}
|
||||
|
||||
protected override void OnTestSkipped(ITestSkipped testSkipped)
|
||||
{
|
||||
Process(testSkipped.TestCase.DisplayName, TestState.Skipped);
|
||||
Process(testSkipped.TestCase.DisplayName, testSkipped.TestCase.UniqueID, TestState.Skipped);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TestDiscoverySink : BaseTestDiscoverySink
|
||||
{
|
||||
private readonly HashSet<string> _testCaseNameSet;
|
||||
private readonly HashSet<string> _testCaseUniqueIDSet;
|
||||
private readonly List<ITestCase> _testCaseList;
|
||||
|
||||
internal TestDiscoverySink(HashSet<string> testCaseDisplayNameSet, List<ITestCase> testCaseList)
|
||||
internal TestDiscoverySink(HashSet<string> testCaseUniqueIDSet, List<ITestCase> testCaseList)
|
||||
{
|
||||
_testCaseNameSet = testCaseDisplayNameSet;
|
||||
_testCaseUniqueIDSet = testCaseUniqueIDSet;
|
||||
_testCaseList = testCaseList;
|
||||
}
|
||||
|
||||
protected override void OnTestDiscovered(ITestCaseDiscoveryMessage testCaseDiscovered)
|
||||
{
|
||||
var testCase = testCaseDiscovered.TestCase;
|
||||
if (_testCaseNameSet.Contains(testCase.DisplayName))
|
||||
if (_testCaseUniqueIDSet.Contains(testCase.UniqueID))
|
||||
{
|
||||
_testCaseList.Add(testCaseDiscovered.TestCase);
|
||||
}
|
||||
@@ -82,9 +87,9 @@ namespace Xunit.Runner.Worker
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read out the set of test case display names to run.
|
||||
/// Read out the set of test case unique IDs to run.
|
||||
/// </summary>
|
||||
private static List<string> ReadTestCaseDisplayNames(Stream stream)
|
||||
private static List<string> ReadTestCaseUniqueIDs(Stream stream)
|
||||
{
|
||||
using (var reader = new ClientReader(stream))
|
||||
{
|
||||
@@ -133,14 +138,14 @@ namespace Xunit.Runner.Worker
|
||||
|
||||
internal static void RunSpecific(string assemblyFileName, Stream stream)
|
||||
{
|
||||
var testCaseNameSet = new HashSet<string>(ReadTestCaseDisplayNames(stream), StringComparer.Ordinal);
|
||||
var testCaseUniqueIDSet = new HashSet<string>(ReadTestCaseUniqueIDs(stream), StringComparer.Ordinal);
|
||||
|
||||
Go(assemblyFileName, stream, AppDomainSupport.IfAvailable,
|
||||
(xunit, configuration, writer) =>
|
||||
{
|
||||
using (var sink = new TestRunSink(writer))
|
||||
{
|
||||
var testCaseList = GetTestCaseList(xunit, configuration, testCaseNameSet);
|
||||
var testCaseList = GetTestCaseList(xunit, configuration, testCaseUniqueIDSet);
|
||||
|
||||
xunit.RunTests(testCaseList, sink,
|
||||
executionOptions: TestFrameworkOptions.ForExecution(configuration));
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.23107.0
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26430.4
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xunit.runner.wpf", "xunit.runner.wpf\xunit.runner.wpf.csproj", "{34FB519C-FB49-4B31-ACA2-7F7879311BCF}"
|
||||
EndProject
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 659 B |
Binary file not shown.
|
After Width: | Height: | Size: 402 B |
@@ -16,16 +16,14 @@ namespace Xunit.Runner.Wpf
|
||||
}
|
||||
}
|
||||
|
||||
public static CommandBindingCollection GetRegistration(UIElement element)
|
||||
public static CommandBindingCollection? GetRegistration(UIElement element)
|
||||
=> (element != null ? (CommandBindingCollection)element.GetValue(Registration) : null);
|
||||
|
||||
private static void OnRegistrationChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
UIElement element = sender as UIElement;
|
||||
if (element != null)
|
||||
if (sender is UIElement element)
|
||||
{
|
||||
CommandBindingCollection bindings = e.NewValue as CommandBindingCollection;
|
||||
if (bindings != null)
|
||||
if (e.NewValue is CommandBindingCollection bindings)
|
||||
{
|
||||
element.CommandBindings.AddRange(bindings);
|
||||
}
|
||||
|
||||
@@ -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 @@ namespace Xunit.Runner.Wpf.Converters
|
||||
|
||||
static TestStateConverter()
|
||||
{
|
||||
runningSource = LoadResourceImage("Running_small.png");
|
||||
failedSource = LoadResourceImage("Failed_small.png");
|
||||
passedSource = LoadResourceImage("Passed_small.png");
|
||||
skippedSource = LoadResourceImage("Skipped_small.png");
|
||||
@@ -31,13 +33,15 @@ namespace Xunit.Runner.Wpf.Converters
|
||||
return image;
|
||||
}
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
var state = (TestState)value;
|
||||
if (targetType == typeof(Brush))
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case TestState.Running:
|
||||
return Brushes.Blue;
|
||||
case TestState.Failed:
|
||||
return Brushes.Red;
|
||||
case TestState.Passed:
|
||||
@@ -52,6 +56,8 @@ namespace Xunit.Runner.Wpf.Converters
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case TestState.Running:
|
||||
return runningSource;
|
||||
case TestState.Failed:
|
||||
return failedSource;
|
||||
case TestState.Passed:
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Xunit.Runner.Wpf
|
||||
private readonly ObservableCollection<T> dataSource;
|
||||
private readonly List<T> filteredList;
|
||||
private readonly Func<T, TFilterArg, bool> filter;
|
||||
private readonly IComparer<T> sort;
|
||||
|
||||
public FilteredCollectionView(ObservableCollection<T> dataSource, Func<T, TFilterArg, bool> filter, TFilterArg filterArgument, IComparer<T> sort)
|
||||
{
|
||||
@@ -26,6 +27,7 @@ namespace Xunit.Runner.Wpf
|
||||
this.filter = filter;
|
||||
this.filterArgument = filterArgument;
|
||||
this.filteredList = new List<T>();
|
||||
this.sort = sort;
|
||||
|
||||
this.dataSource.CollectionChanged += this.dataSource_CollectionChanged;
|
||||
|
||||
@@ -71,6 +73,7 @@ namespace Xunit.Runner.Wpf
|
||||
}
|
||||
}
|
||||
|
||||
this.filteredList.Sort(sort);
|
||||
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
@@ -115,7 +118,7 @@ namespace Xunit.Runner.Wpf
|
||||
{
|
||||
if (this.filter(item, this.filterArgument))
|
||||
{
|
||||
int index = this.filteredList.IndexOf(item);
|
||||
int index = this.filteredList.BinarySearch(item, sort);
|
||||
if (index < 0)
|
||||
{
|
||||
this.filteredList.Insert(~index, item);
|
||||
@@ -138,7 +141,7 @@ namespace Xunit.Runner.Wpf
|
||||
observable.PropertyChanged -= this.dataSource_ItemChanged;
|
||||
}
|
||||
|
||||
int index = this.filteredList.IndexOf(item);
|
||||
int index = this.filteredList.BinarySearch(item, sort);
|
||||
if (index >= 0)
|
||||
{
|
||||
this.filteredList.RemoveAt(index);
|
||||
@@ -149,7 +152,7 @@ namespace Xunit.Runner.Wpf
|
||||
private void dataSource_ItemChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
var item = (T)sender;
|
||||
int index = this.filteredList.IndexOf(item);
|
||||
int index = this.filteredList.BinarySearch(item, sort);
|
||||
if (this.filter(item, this.FilterArgument))
|
||||
{
|
||||
if (index < 0)
|
||||
@@ -196,7 +199,7 @@ namespace Xunit.Runner.Wpf
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public bool Contains(T item) => this.filteredList.Contains(item);
|
||||
public bool Contains(T item) => this.filteredList.BinarySearch(item, sort) >= 0;
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
@@ -216,7 +219,14 @@ namespace Xunit.Runner.Wpf
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
|
||||
public int IndexOf(T item) => this.filteredList.IndexOf(item);
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
int location = this.filteredList.BinarySearch(item, sort);
|
||||
if (location < 0)
|
||||
return -1;
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
@@ -273,7 +283,13 @@ namespace Xunit.Runner.Wpf
|
||||
|
||||
object IList.this[int index]
|
||||
{
|
||||
get { return this[index]; }
|
||||
get
|
||||
{
|
||||
// Can't figure out how to not get a warning here.
|
||||
#pragma warning disable CS8603
|
||||
return this[index];
|
||||
#pragma warning restore
|
||||
}
|
||||
set { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Xunit.Runner.Wpf
|
||||
{
|
||||
internal interface ITestAssemblyWatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a new assembly to list of assemblies to be autoreloaded.
|
||||
/// </summary>
|
||||
void AddAssembly(string assemblyFileName);
|
||||
|
||||
/// <summary>
|
||||
/// Removes an assembly from the list of assemblies ot be autoreloaded.
|
||||
/// </summary>
|
||||
void RemoveAssembly(string assemblyFileName);
|
||||
|
||||
/// <summary>
|
||||
/// Enables watching of all assemblies.
|
||||
/// </summary>
|
||||
/// <param name="reloader">Action to perform when a file change is detected</param>
|
||||
void EnableWatch(Func<IEnumerable<string>, bool> reloader);
|
||||
|
||||
/// <summary>
|
||||
/// Disables watching of all assemblies
|
||||
/// </summary>
|
||||
void DisableWatch();
|
||||
}
|
||||
}
|
||||
@@ -55,13 +55,13 @@ namespace Xunit.Runner.Wpf.Impl
|
||||
/// <typeparam name="T"></typeparam>
|
||||
private sealed class BackgroundReader<T> where T : class
|
||||
{
|
||||
private readonly ConcurrentQueue<T> _queue;
|
||||
private readonly ConcurrentQueue<T?> _queue;
|
||||
private readonly ClientReader _reader;
|
||||
private readonly Func<ClientReader, T> _readValue;
|
||||
|
||||
internal ClientReader Reader => _reader;
|
||||
|
||||
internal BackgroundReader(ConcurrentQueue<T> queue, ClientReader reader, Func<ClientReader, T> readValue)
|
||||
internal BackgroundReader(ConcurrentQueue<T?> queue, ClientReader reader, Func<ClientReader, T> readValue)
|
||||
{
|
||||
_queue = queue;
|
||||
_reader = reader;
|
||||
@@ -111,7 +111,7 @@ namespace Xunit.Runner.Wpf.Impl
|
||||
private const int MaxResultPerTick = 1000;
|
||||
|
||||
private readonly Connection _connection;
|
||||
private readonly ConcurrentQueue<T> _queue;
|
||||
private readonly ConcurrentQueue<T?> _queue;
|
||||
private readonly DispatcherTimer _timer;
|
||||
private readonly Action<List<T>> _callback;
|
||||
private readonly int _maxPerTick;
|
||||
@@ -122,7 +122,7 @@ namespace Xunit.Runner.Wpf.Impl
|
||||
internal BackgroundProducer(
|
||||
Connection connection,
|
||||
Dispatcher dispatcher,
|
||||
ConcurrentQueue<T> queue,
|
||||
ConcurrentQueue<T?> queue,
|
||||
Action<List<T>> callback,
|
||||
int maxResultPerTick = MaxResultPerTick,
|
||||
TimeSpan? interval = null)
|
||||
@@ -144,8 +144,7 @@ namespace Xunit.Runner.Wpf.Impl
|
||||
var i = 0;
|
||||
var list = new List<T>();
|
||||
var isDone = false;
|
||||
T value;
|
||||
while (i < _maxPerTick && _queue.TryDequeue(out value))
|
||||
while (i < _maxPerTick && _queue.TryDequeue(out T? value))
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
|
||||
@@ -8,10 +8,10 @@ namespace Xunit.Runner.Wpf.Impl
|
||||
{
|
||||
private sealed class Connection : IDisposable
|
||||
{
|
||||
private NamedPipeClientStream _stream;
|
||||
private NamedPipeClientStream? _stream;
|
||||
private ClientReader _reader;
|
||||
|
||||
internal NamedPipeClientStream Stream => _stream;
|
||||
internal NamedPipeClientStream Stream => _stream ?? throw new ObjectDisposedException(nameof(Connection));
|
||||
|
||||
internal ClientReader Reader => _reader;
|
||||
|
||||
|
||||
@@ -34,14 +34,14 @@ namespace Xunit.Runner.Wpf.Impl
|
||||
_processInfo = StartWorkerProcess();
|
||||
}
|
||||
|
||||
private async Task<Connection> CreateConnection(string action, string argument)
|
||||
private async Task<Connection> CreateConnection(string action, string argument, CancellationToken cancellationToken)
|
||||
{
|
||||
var pipeName = GetPipeName();
|
||||
|
||||
try
|
||||
{
|
||||
var stream = new NamedPipeClientStream(pipeName);
|
||||
await stream.ConnectAsync();
|
||||
await stream.ConnectAsync(cancellationToken);
|
||||
|
||||
var writer = new ClientWriter(stream);
|
||||
writer.Write(action);
|
||||
@@ -101,7 +101,7 @@ namespace Xunit.Runner.Wpf.Impl
|
||||
|
||||
private async Task Discover(string assemblyPath, Action<List<TestCaseData>> callback, CancellationToken cancellationToken)
|
||||
{
|
||||
var connection = await CreateConnection(Constants.ActionDiscover, assemblyPath);
|
||||
var connection = await CreateConnection(Constants.ActionDiscover, assemblyPath, cancellationToken);
|
||||
await ProcessResultsCore(connection, r => r.ReadTestCaseData(), callback, cancellationToken);
|
||||
|
||||
RecycleProcess();
|
||||
@@ -109,7 +109,7 @@ namespace Xunit.Runner.Wpf.Impl
|
||||
|
||||
private async Task RunCore(string actionName, string assemblyPath, ImmutableArray<string> testCaseDisplayNames, Action<List<TestResultData>> callback, CancellationToken cancellationToken)
|
||||
{
|
||||
var connection = await CreateConnection(actionName, assemblyPath);
|
||||
var connection = await CreateConnection(actionName, assemblyPath, cancellationToken);
|
||||
|
||||
if (!testCaseDisplayNames.IsDefaultOrEmpty)
|
||||
{
|
||||
@@ -123,7 +123,7 @@ namespace Xunit.Runner.Wpf.Impl
|
||||
private async Task ProcessResultsCore<T>(Connection connection, Func<ClientReader, T> readValue, Action<List<T>> callback, CancellationToken cancellationToken)
|
||||
where T : class
|
||||
{
|
||||
var queue = new ConcurrentQueue<T>();
|
||||
var queue = new ConcurrentQueue<T?>();
|
||||
var backgroundReader = new BackgroundReader<T>(queue, new ClientReader(connection.Stream), readValue);
|
||||
var backgroundProducer = new BackgroundProducer<T>(connection, _dispatcher, queue, callback);
|
||||
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Xunit.Runner.Wpf.Impl
|
||||
{
|
||||
internal sealed class TestAssemblyWatcher : ITestAssemblyWatcher
|
||||
{
|
||||
private readonly object sync = new object();
|
||||
private readonly IDictionary<string, FileSystemWatcher> watchedAssemblies = new Dictionary<string, FileSystemWatcher>();
|
||||
private readonly Dispatcher dispatcher;
|
||||
private bool isEnabled = false;
|
||||
private ReloadDebouncer? debouncer;
|
||||
|
||||
public TestAssemblyWatcher(Dispatcher dispatcher)
|
||||
{
|
||||
this.dispatcher = dispatcher;
|
||||
}
|
||||
|
||||
public void AddAssembly(string assemblyFileName)
|
||||
{
|
||||
// Assumptions about adding and removing assemblies are broken if this isn't true
|
||||
Debug.Assert(string.Equals(assemblyFileName, Path.GetFullPath(assemblyFileName), StringComparison.Ordinal));
|
||||
|
||||
lock (sync)
|
||||
{
|
||||
if (watchedAssemblies.ContainsKey(assemblyFileName))
|
||||
{
|
||||
// Already watching this assembly, nothing to do but return
|
||||
return;
|
||||
}
|
||||
|
||||
FileSystemWatcher watcher = new FileSystemWatcher
|
||||
{
|
||||
Path = Path.GetDirectoryName(assemblyFileName),
|
||||
NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite,
|
||||
Filter = Path.GetFileName(assemblyFileName)
|
||||
};
|
||||
|
||||
watcher.Changed += new FileSystemEventHandler(OnChanged);
|
||||
watcher.Created += new FileSystemEventHandler(OnChanged);
|
||||
|
||||
watchedAssemblies[assemblyFileName] = watcher;
|
||||
|
||||
if (isEnabled)
|
||||
{
|
||||
watcher.EnableRaisingEvents = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAssembly(string assemblyFileName)
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
if (watchedAssemblies.ContainsKey(assemblyFileName))
|
||||
{
|
||||
watchedAssemblies[assemblyFileName].Dispose();
|
||||
watchedAssemblies.Remove(assemblyFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void EnableWatch(Func<IEnumerable<string>, bool> reloader)
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
isEnabled = true;
|
||||
|
||||
foreach (var watcher in watchedAssemblies.Values)
|
||||
{
|
||||
watcher.EnableRaisingEvents = true;
|
||||
}
|
||||
|
||||
this.debouncer = new ReloadDebouncer(dispatcher, reloader);
|
||||
}
|
||||
}
|
||||
|
||||
public void DisableWatch()
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
isEnabled = false;
|
||||
|
||||
foreach (var watcher in watchedAssemblies.Values)
|
||||
{
|
||||
watcher.EnableRaisingEvents = false;
|
||||
}
|
||||
|
||||
this.debouncer?.Cancel();
|
||||
this.debouncer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnChanged(object source, FileSystemEventArgs args)
|
||||
{
|
||||
debouncer?.AddAssembly(args.FullPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Because, during a build of a number of projects many file system events will be triggered for potentially many
|
||||
/// test assemblies, we need to batch our update requests. This class will do this, waiting for 100 ms after receiving
|
||||
/// a new reload request to send the reload requests. This timer resets every time a reload request is received. Note
|
||||
/// that if you continuously rebuild, this will technicially never finish batching and nothing will reload, but this
|
||||
/// assumes that file events will stop at some point.
|
||||
///
|
||||
/// If the reloader returns false, meaning that the reload was not kicked off successfully, we back off for a full second
|
||||
/// before reattempting to queue the updates.
|
||||
/// </summary>
|
||||
private class ReloadDebouncer
|
||||
{
|
||||
private readonly object sync = new object();
|
||||
private readonly Dispatcher dispatcher;
|
||||
private readonly Func<IEnumerable<string>, bool> reloader;
|
||||
|
||||
private ISet<string> assembliesToReload = new HashSet<string>();
|
||||
private bool newAssemblyAdded = false;
|
||||
private bool running = false;
|
||||
private bool cancelled = false;
|
||||
|
||||
public ReloadDebouncer(Dispatcher dispatcher, Func<IEnumerable<string>, bool> reloader)
|
||||
{
|
||||
this.dispatcher = dispatcher;
|
||||
this.reloader = reloader;
|
||||
}
|
||||
|
||||
public void AddAssembly(string assembly)
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
assembliesToReload.Add(assembly);
|
||||
|
||||
if (!Start())
|
||||
{
|
||||
newAssemblyAdded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
running = false;
|
||||
}
|
||||
|
||||
private bool Start()
|
||||
{
|
||||
if (running)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
running = true;
|
||||
Task.Run((Action)Debounce);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async void Debounce()
|
||||
{
|
||||
bool backOff = false;
|
||||
|
||||
do
|
||||
{
|
||||
await Task.Delay(backOff ? 1000 : 100);
|
||||
backOff = false;
|
||||
|
||||
lock (sync)
|
||||
{
|
||||
void Reset()
|
||||
{
|
||||
assembliesToReload = new HashSet<string>();
|
||||
running = false;
|
||||
}
|
||||
|
||||
if (cancelled)
|
||||
{
|
||||
Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
// New assemblies added, so we need to wait again
|
||||
if (newAssemblyAdded)
|
||||
{
|
||||
newAssemblyAdded = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// No new assemblies added, time to alert and exit
|
||||
if (!dispatcher.Invoke(() => reloader(assembliesToReload)))
|
||||
{
|
||||
// If the reloader returned false, it's still busy from the last reload request or other user action.
|
||||
// Back off for a full second to give it time, then continue as previous
|
||||
backOff = true;
|
||||
continue;
|
||||
}
|
||||
Reset();
|
||||
}
|
||||
|
||||
} while (running);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+201
-149
@@ -1,24 +1,25 @@
|
||||
<Window x:Class="Xunit.Runner.Wpf.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Xunit.Runner.Wpf"
|
||||
xmlns:converters="clr-namespace:Xunit.Runner.Wpf.Converters"
|
||||
xmlns:vm="clr-namespace:Xunit.Runner.Wpf.ViewModel"
|
||||
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
|
||||
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
mc:Ignorable="d"
|
||||
DataContext="{Binding Main, Source={StaticResource Locator}}"
|
||||
Title="xUnit.net Test Runner"
|
||||
Icon="Artwork\Application.ico"
|
||||
ResizeMode="CanResizeWithGrip"
|
||||
Height="600"
|
||||
MinHeight="525"
|
||||
Width="525"
|
||||
MinWidth="425"
|
||||
Name="Main">
|
||||
<Window
|
||||
x:Class="Xunit.Runner.Wpf.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform"
|
||||
xmlns:converters="clr-namespace:Xunit.Runner.Wpf.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
|
||||
xmlns:local="clr-namespace:Xunit.Runner.Wpf"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:vm="clr-namespace:Xunit.Runner.Wpf.ViewModel"
|
||||
Name="Main"
|
||||
Title="xUnit.net Test Runner"
|
||||
Width="525"
|
||||
Height="600"
|
||||
MinWidth="425"
|
||||
MinHeight="525"
|
||||
DataContext="{Binding Main, Source={StaticResource Locator}}"
|
||||
Icon="Artwork\Application.ico"
|
||||
ResizeMode="CanResizeWithGrip"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="Loaded">
|
||||
@@ -43,12 +44,10 @@
|
||||
|
||||
<Menu Grid.Row="0">
|
||||
<MenuItem Header="_File">
|
||||
<MenuItem Header="E_xit"
|
||||
Command="{Binding ExitCommand}" />
|
||||
<MenuItem Command="{Binding ExitCommand}" Header="E_xit" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Assembly">
|
||||
<MenuItem Header="_Open"
|
||||
Command="ApplicationCommands.Open" />
|
||||
<MenuItem Command="ApplicationCommands.Open" Header="_Open" />
|
||||
<MenuItem Header="R_ecent" ItemsSource="{Binding RecentAssemblies}">
|
||||
<MenuItem.ItemContainerStyle>
|
||||
<Style TargetType="MenuItem">
|
||||
@@ -59,8 +58,14 @@
|
||||
</MenuItem.ItemContainerStyle>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="_Unload" Command="{Binding AssemblyRemoveAllCommand}"/>
|
||||
<MenuItem Header="_Reload" Command="{Binding AssemblyReloadAllCommand}" />
|
||||
<MenuItem Command="{Binding AssemblyRemoveAllCommand}" Header="_Unload" />
|
||||
<MenuItem Command="{Binding AssemblyReloadAllCommand}" Header="_Reload" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{Binding AutoReloadAssembliesCommand}"
|
||||
Header="_Auto Reload Test Assemblies"
|
||||
IsCheckable="True"
|
||||
IsChecked="{Binding AutoReloadAssemblies}" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Project">
|
||||
<MenuItem Header="_Open" />
|
||||
@@ -84,10 +89,11 @@
|
||||
<RowDefinition Height="auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<GroupBox Header="Refinements"
|
||||
Margin="3"
|
||||
Grid.Column="0"
|
||||
Grid.Row="0">
|
||||
<GroupBox
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="3"
|
||||
Header="Refinements">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
@@ -98,25 +104,24 @@
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label Content="Search:"
|
||||
Grid.Row="0" />
|
||||
<TextBox Grid.Row="1"
|
||||
Text="{Binding FilterString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<Label Grid.Row="0" Content="Search:" />
|
||||
<TextBox Grid.Row="1" Text="{Binding FilterString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
|
||||
<Label Content="Assemblies:"
|
||||
Grid.Row="2" />
|
||||
<ListBox Height="175"
|
||||
ItemsSource="{Binding Assemblies}"
|
||||
SelectionMode="Extended"
|
||||
Grid.Row="3">
|
||||
<Label Grid.Row="2" Content="Assemblies:" />
|
||||
<ListBox
|
||||
Grid.Row="3"
|
||||
Height="175"
|
||||
ItemsSource="{Binding Assemblies}"
|
||||
SelectionMode="Extended">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="vm:TestAssemblyViewModel">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding DisplayName}" />
|
||||
|
||||
<TextBlock Text=" Discovering tests..."
|
||||
FontStyle="Italic"
|
||||
Foreground="Gray">
|
||||
<TextBlock
|
||||
FontStyle="Italic"
|
||||
Foreground="Gray"
|
||||
Text=" Discovering tests...">
|
||||
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
@@ -136,41 +141,36 @@
|
||||
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type ListBoxItem}">
|
||||
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
|
||||
<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}" />
|
||||
<MenuItem Command="{Binding AssemblyReloadCommand}" Header="Reload" />
|
||||
<MenuItem Command="{Binding AssemblyReloadAllCommand}" Header="Reload All" />
|
||||
<Separator />
|
||||
<MenuItem Header="Remove" Command="{Binding AssemblyRemoveCommand}" />
|
||||
<MenuItem Header="Remove All" Command="{Binding AssemblyRemoveAllCommand}" />
|
||||
<MenuItem Command="{Binding AssemblyRemoveCommand}" Header="Remove" />
|
||||
<MenuItem Command="{Binding AssemblyRemoveAllCommand}" Header="Remove All" />
|
||||
</ContextMenu>
|
||||
</ListBox.ContextMenu>
|
||||
</ListBox>
|
||||
|
||||
<Label Content="Traits:"
|
||||
Grid.Row="4" />
|
||||
<Label Grid.Row="4" Content="Traits:" />
|
||||
|
||||
<TreeView Grid.Row="5"
|
||||
ItemsSource="{Binding Traits}">
|
||||
<TreeView Grid.Row="5" ItemsSource="{Binding Traits}">
|
||||
|
||||
<TreeView.Resources>
|
||||
<HierarchicalDataTemplate DataType="{x:Type vm:TraitViewModel}"
|
||||
ItemsSource="{Binding Children}">
|
||||
<HierarchicalDataTemplate DataType="{x:Type vm:TraitViewModel}" ItemsSource="{Binding Children}">
|
||||
|
||||
<CheckBox IsChecked="{Binding IsChecked}" Content="{Binding Text}" >
|
||||
<CheckBox Content="{Binding Text}" IsChecked="{Binding IsChecked}">
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="Checked">
|
||||
<cmd:EventToCommand Command="{Binding DataContext.TraitCheckedChangedCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
CommandParameter="{Binding}" />
|
||||
<cmd:EventToCommand Command="{Binding DataContext.TraitCheckedChangedCommand, RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter="{Binding}" />
|
||||
</i:EventTrigger>
|
||||
|
||||
<i:EventTrigger EventName="Unchecked">
|
||||
<cmd:EventToCommand Command="{Binding DataContext.TraitCheckedChangedCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
CommandParameter="{Binding}" />
|
||||
<cmd:EventToCommand Command="{Binding DataContext.TraitCheckedChangedCommand, RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter="{Binding}" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
</CheckBox>
|
||||
@@ -186,7 +186,7 @@
|
||||
|
||||
<TreeView.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Clear" Command="{Binding TraitsClearCommand}" />
|
||||
<MenuItem Command="{Binding TraitsClearCommand}" Header="Clear" />
|
||||
</ContextMenu>
|
||||
</TreeView.ContextMenu>
|
||||
|
||||
@@ -194,34 +194,40 @@
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<Grid Grid.Column="0"
|
||||
Grid.Row="1">
|
||||
<Grid Grid.Row="1" Grid.Column="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Content="_Run All"
|
||||
Command="{Binding RunCommand}"
|
||||
Margin="3"
|
||||
Grid.Column="0" />
|
||||
<Button Content="_Cancel"
|
||||
Command="{Binding CancelCommand}"
|
||||
Margin="0,3,3,3"
|
||||
Grid.Column="1" />
|
||||
<Button
|
||||
Grid.Column="0"
|
||||
Margin="10,0,0,0"
|
||||
Command="{Binding RunAllCommand}"
|
||||
Content="_Run All" />
|
||||
<Button
|
||||
Grid.Column="1"
|
||||
Command="{Binding RunSelectedCommand}"
|
||||
Content="Run _Selected" />
|
||||
<Button
|
||||
Grid.Column="2"
|
||||
Margin="0,0,10,0"
|
||||
Command="{Binding CancelCommand}"
|
||||
Content="_Cancel" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Column="1"
|
||||
Grid.Row="0">
|
||||
<Grid Grid.Row="0" Grid.Column="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" MinHeight="200px" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="*" MinHeight="200px"/>
|
||||
<RowDefinition Height="*" MinHeight="200px" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<GroupBox Header="{Binding TestCasesCaption}"
|
||||
Margin="3"
|
||||
Grid.Row="0">
|
||||
<GroupBox
|
||||
Grid.Row="0"
|
||||
Margin="3"
|
||||
Header="{Binding TestCasesCaption}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
@@ -229,55 +235,82 @@
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ToggleButton IsChecked="{Binding FilterPassedTests}"
|
||||
BorderThickness="0"
|
||||
Background="Transparent"
|
||||
Margin="0,4,2,4"
|
||||
Grid.Column="0"
|
||||
Command="{Binding TestFilterChanged}">
|
||||
<ToggleButton
|
||||
Grid.Column="0"
|
||||
Margin="0,4,2,4"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Command="{Binding TestFilterChanged}"
|
||||
IsChecked="{Binding FilterPassedTests}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Artwork\Passed_large.png" />
|
||||
<TextBlock Margin="4,0"
|
||||
FontSize="16"
|
||||
Text="{Binding TestsPassed, StringFormat={}{0:#\,0}}"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock
|
||||
Margin="4,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
Text="{Binding TestsPassed, StringFormat={}{0:#\,0}}" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
<ToggleButton IsChecked="{Binding FilterFailedTests}"
|
||||
BorderThickness="0"
|
||||
Background="Transparent"
|
||||
Margin="2,4"
|
||||
Grid.Column="1"
|
||||
Command="{Binding TestFilterChanged}">
|
||||
<ToggleButton
|
||||
Grid.Column="1"
|
||||
Margin="2,4"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Command="{Binding TestFilterChanged}"
|
||||
IsChecked="{Binding FilterFailedTests}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Artwork\Failed_large.png" />
|
||||
<TextBlock Margin="4,0"
|
||||
FontSize="16"
|
||||
Text="{Binding TestsFailed, StringFormat={}{0:#\,0}}"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock
|
||||
Margin="4,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
Text="{Binding TestsFailed, StringFormat={}{0:#\,0}}" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
<ToggleButton IsChecked="{Binding FilterSkippedTests}"
|
||||
BorderThickness="0"
|
||||
Background="Transparent"
|
||||
Margin="2,4,0,4"
|
||||
Grid.Column="2"
|
||||
Command="{Binding TestFilterChanged}">
|
||||
<ToggleButton
|
||||
Grid.Column="2"
|
||||
Margin="2,4,0,4"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Command="{Binding TestFilterChanged}"
|
||||
IsChecked="{Binding FilterSkippedTests}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Artwork\Skipped_large.png" />
|
||||
<TextBlock Margin="4,0"
|
||||
FontSize="16"
|
||||
Text="{Binding TestsSkipped, StringFormat={}{0:#\,0}}"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock
|
||||
Margin="4,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
Text="{Binding TestsSkipped, StringFormat={}{0:#\,0}}" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
<ToggleButton
|
||||
Grid.Column="2"
|
||||
Margin="2,4,0,4"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Command="{Binding TestFilterChanged}"
|
||||
IsChecked="{Binding FilterRunningTests}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Artwork\Running_large.png" />
|
||||
<TextBlock
|
||||
Margin="4,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
Text="{Binding TestsRunning, StringFormat={}{0:#\,0}}" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
|
||||
<ListBox ItemsSource="{Binding FilteredTestCases}"
|
||||
SelectionMode="Extended"
|
||||
Grid.Row="1">
|
||||
<ListBox
|
||||
x:Name="TestCases"
|
||||
Grid.Row="1"
|
||||
ItemsSource="{Binding FilteredTestCases}"
|
||||
SelectedItem="{Binding SelectedTestCase, Mode=TwoWay}"
|
||||
SelectionChanged="TestCases_SelectionChanged"
|
||||
SelectionMode="Extended">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="vm:TestCaseViewModel">
|
||||
<Grid>
|
||||
@@ -286,49 +319,71 @@
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Image Width="16"
|
||||
Margin="0,0,2,0"
|
||||
Source="{Binding Path=State, Mode=OneWay, Converter={StaticResource TestStateConverter}}"
|
||||
Grid.Column="0" />
|
||||
<Image
|
||||
Grid.Column="0"
|
||||
Width="16"
|
||||
Margin="0,0,2,0"
|
||||
Source="{Binding Path=State, Mode=OneWay, Converter={StaticResource TestStateConverter}}" />
|
||||
|
||||
<TextBlock Text="{Binding DisplayName}"
|
||||
VerticalAlignment="Center"
|
||||
Grid.Column="1" />
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding DisplayName}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
<ListBox.InputBindings>
|
||||
<KeyBinding Key="Enter" Command="{Binding RunSelectedCommand}" />
|
||||
</ListBox.InputBindings>
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="MouseDoubleClick">
|
||||
<cmd:EventToCommand Command="{Binding Path=RunSelectedCommand}" PassEventArgsToCommand="False" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<GridSplitter Grid.Row="1" HorizontalAlignment="Stretch" Height="3" Background="White"/>
|
||||
<GridSplitter
|
||||
Grid.Row="1"
|
||||
Height="3"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="White" />
|
||||
|
||||
<GroupBox Header="Output"
|
||||
Margin="3"
|
||||
Grid.Row="2">
|
||||
<TextBox IsReadOnly="True"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Visible"
|
||||
FontFamily="Consolas"
|
||||
Text="{Binding Output}"/>
|
||||
<GroupBox
|
||||
Grid.Row="2"
|
||||
Margin="3"
|
||||
Header="Output">
|
||||
<TextBox
|
||||
FontFamily="Consolas"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding Output}"
|
||||
VerticalScrollBarVisibility="Visible" />
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
|
||||
<GridSplitter Grid.Column="0" VerticalAlignment="Stretch" Width="3" Background="White"/>
|
||||
|
||||
<ProgressBar Foreground="{Binding Path=CurrentRunState, Converter={StaticResource TestStateConverter}, Mode=OneWay}"
|
||||
Minimum="0"
|
||||
Maximum="{Binding MaximumProgress}"
|
||||
Value="{Binding Path=TestsCompleted}"
|
||||
Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
Margin="3" />
|
||||
|
||||
<GridSplitter
|
||||
Grid.Column="0"
|
||||
Width="3"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="White" />
|
||||
|
||||
<ProgressBar
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="3"
|
||||
Foreground="{Binding Path=CurrentRunState, Converter={StaticResource TestStateConverter}, Mode=OneWay}"
|
||||
Maximum="{Binding MaximumProgress}"
|
||||
Minimum="0"
|
||||
Value="{Binding Path=TestsCompleted}" />
|
||||
</Grid>
|
||||
|
||||
<StackPanel Grid.Row="2">
|
||||
<Border BorderBrush="LightGray"
|
||||
BorderThickness="1"
|
||||
Margin="3" />
|
||||
<Border
|
||||
Margin="3"
|
||||
BorderBrush="LightGray"
|
||||
BorderThickness="1" />
|
||||
<StatusBar>
|
||||
<StatusBar.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
@@ -338,7 +393,7 @@
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="16" />
|
||||
</Grid.ColumnDefinitions>
|
||||
</Grid>
|
||||
</ItemsPanelTemplate>
|
||||
@@ -346,28 +401,25 @@
|
||||
|
||||
<StatusBarItem Grid.Column="1">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Tests passed: "
|
||||
Foreground="DarkGreen"/>
|
||||
<TextBlock Foreground="DarkGreen" Text="Tests passed: " />
|
||||
|
||||
<TextBlock Text="{Binding TestsPassed, StringFormat={}{0:#\,0}}"/>
|
||||
<TextBlock Text="{Binding TestsPassed, StringFormat={}{0:#\,0}}" />
|
||||
</StackPanel>
|
||||
</StatusBarItem>
|
||||
|
||||
<StatusBarItem Grid.Column="2">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Tests failed: "
|
||||
Foreground="DarkRed"/>
|
||||
<TextBlock Foreground="DarkRed" Text="Tests failed: " />
|
||||
|
||||
<TextBlock Text="{Binding TestsFailed, StringFormat={}{0:#\,0}}"/>
|
||||
<TextBlock Text="{Binding TestsFailed, StringFormat={}{0:#\,0}}" />
|
||||
</StackPanel>
|
||||
</StatusBarItem>
|
||||
|
||||
<StatusBarItem Grid.Column="3">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Tests skipped: "
|
||||
Foreground="DarkGoldenrod"/>
|
||||
<TextBlock Foreground="DarkGoldenrod" Text="Tests skipped: " />
|
||||
|
||||
<TextBlock Text="{Binding TestsSkipped, StringFormat={}{0:#\,0}}"/>
|
||||
<TextBlock Text="{Binding TestsSkipped, StringFormat={}{0:#\,0}}" />
|
||||
</StackPanel>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
|
||||
@@ -5,16 +5,21 @@ using Xunit.Runner.Wpf.Persistence;
|
||||
|
||||
namespace Xunit.Runner.Wpf
|
||||
{
|
||||
using ViewModel;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public static Window Instance { get; private set; }
|
||||
|
||||
// WPF generates fields that are marked as non-nullable, but not definitely initialized.
|
||||
#pragma warning disable CS8618
|
||||
public MainWindow()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
#pragma warning restore
|
||||
|
||||
protected override void OnSourceInitialized(EventArgs e)
|
||||
{
|
||||
@@ -29,5 +34,26 @@ namespace Xunit.Runner.Wpf
|
||||
|
||||
base.OnClosing(e);
|
||||
}
|
||||
|
||||
private void TestCases_SelectionChanged(Object sender, System.Windows.Controls.SelectionChangedEventArgs e)
|
||||
{
|
||||
foreach (var item in e.AddedItems)
|
||||
{
|
||||
var model = item as TestCaseViewModel;
|
||||
if (model != null)
|
||||
{
|
||||
model.IsSelected = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in e.RemovedItems)
|
||||
{
|
||||
var model = item as TestCaseViewModel;
|
||||
if (model != null)
|
||||
{
|
||||
model.IsSelected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,21 +14,24 @@ namespace Xunit.Runner.Wpf.Persistence
|
||||
private const string RecentAssemblyElementName = "recent_assembly";
|
||||
private const string SettingsElementName = "settings";
|
||||
private const string VersionAttributeName = "version";
|
||||
private const string AutoReloadAssembliesElementName = "auto_reload_assemblies";
|
||||
|
||||
private const int MaxRecentAssemblies = 10;
|
||||
|
||||
private static readonly Version s_latestVersion = new Version(1, 0, 0, 0);
|
||||
|
||||
private List<string> recentAssemblies;
|
||||
private bool autoReloadAssemblies;
|
||||
|
||||
private Settings()
|
||||
{
|
||||
recentAssemblies = new List<string>();
|
||||
autoReloadAssemblies = true;
|
||||
}
|
||||
|
||||
public void AddRecentAssembly(string filePath)
|
||||
{
|
||||
for (int i = recentAssemblies.Count - 1; i > 0; i--)
|
||||
for (int i = recentAssemblies.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (StringComparer.OrdinalIgnoreCase.Equals(recentAssemblies[i], filePath))
|
||||
{
|
||||
@@ -44,6 +47,13 @@ namespace Xunit.Runner.Wpf.Persistence
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggleAutoReloadAssemblies()
|
||||
{
|
||||
autoReloadAssemblies = !autoReloadAssemblies;
|
||||
}
|
||||
|
||||
public bool GetAutoReloadAssemblies() => autoReloadAssemblies;
|
||||
|
||||
public ImmutableArray<string> GetRecentAssemblies()
|
||||
{
|
||||
return this.recentAssemblies.ToImmutableArray();
|
||||
@@ -66,6 +76,8 @@ namespace Xunit.Runner.Wpf.Persistence
|
||||
xml.Add(recentAssembliesElement);
|
||||
}
|
||||
|
||||
xml.Add(new XElement(AutoReloadAssembliesElementName, autoReloadAssemblies));
|
||||
|
||||
xml.Save(xmlFile);
|
||||
}
|
||||
}
|
||||
@@ -103,6 +115,21 @@ namespace Xunit.Runner.Wpf.Persistence
|
||||
}
|
||||
}
|
||||
|
||||
var autoReloadAssembliesElement = xml.Element(AutoReloadAssembliesElementName);
|
||||
if (autoReloadAssembliesElement != null)
|
||||
{
|
||||
if (!bool.TryParse(autoReloadAssembliesElement.Value, out var autoReloadAssemblies))
|
||||
{
|
||||
autoReloadAssemblies = true;
|
||||
}
|
||||
|
||||
settings.autoReloadAssemblies = autoReloadAssemblies;
|
||||
}
|
||||
else
|
||||
{
|
||||
settings.autoReloadAssemblies = true;
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Xunit.Runner.Wpf.Persistence
|
||||
|
||||
private static IsolatedStorageFile GetStorageFile() => IsolatedStorageFile.GetUserStoreForDomain();
|
||||
|
||||
public static XmlTextReader OpenXmlFile(string fileName)
|
||||
public static XmlTextReader? OpenXmlFile(string fileName)
|
||||
{
|
||||
var storage = GetStorageFile();
|
||||
if (!storage.FileExists(fileName))
|
||||
|
||||
@@ -5,9 +5,9 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
public class AssemblyAndConfigFile
|
||||
{
|
||||
public string AssemblyFileName { get; }
|
||||
public string ConfigFileName { get; }
|
||||
public string? ConfigFileName { get; }
|
||||
|
||||
public AssemblyAndConfigFile(string assemblyFileName, string configFileName)
|
||||
public AssemblyAndConfigFile(string assemblyFileName, string? configFileName)
|
||||
{
|
||||
this.AssemblyFileName = Path.GetFullPath(assemblyFileName);
|
||||
if (configFileName != null)
|
||||
|
||||
@@ -25,34 +25,50 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
private readonly Settings settings;
|
||||
|
||||
private readonly ITestUtil testUtil;
|
||||
private readonly ITestAssemblyWatcher assemblyWatcher;
|
||||
private readonly HashSet<string> allTestCaseUniqueIDs = new HashSet<string>();
|
||||
private readonly ObservableCollection<TestCaseViewModel> allTestCases = new ObservableCollection<TestCaseViewModel>();
|
||||
private readonly TraitCollectionView traitCollectionView = new TraitCollectionView();
|
||||
private readonly HashSet<string> runningTestSet = new HashSet<string>();
|
||||
|
||||
private CancellationTokenSource filterCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
private CancellationTokenSource cancellationTokenSource;
|
||||
private CancellationTokenSource? cancellationTokenSource;
|
||||
private bool isBusy;
|
||||
private SearchQuery searchQuery = new SearchQuery();
|
||||
private bool autoReloadAssemblies;
|
||||
|
||||
public ObservableCollection<TestAssemblyViewModel> Assemblies { get; } = new ObservableCollection<TestAssemblyViewModel>();
|
||||
public FilteredCollectionView<TestCaseViewModel, SearchQuery> FilteredTestCases { get; }
|
||||
public ObservableCollection<TraitViewModel> Traits => this.traitCollectionView.Collection;
|
||||
public bool AutoReloadAssemblies
|
||||
{
|
||||
get => autoReloadAssemblies;
|
||||
set
|
||||
{
|
||||
var oldVal = autoReloadAssemblies;
|
||||
autoReloadAssemblies = value;
|
||||
RaisePropertyChanged(nameof(AutoReloadAssemblies), oldVal, autoReloadAssemblies);
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<RecentAssemblyViewModel> RecentAssemblies { get; } = new ObservableCollection<RecentAssemblyViewModel>();
|
||||
|
||||
private ImmutableList<TestCaseViewModel> runningTests;
|
||||
private ImmutableList<TestCaseViewModel>? testsToRun;
|
||||
|
||||
public ICommand ExitCommand { get; }
|
||||
public ICommand WindowLoadedCommand { get; }
|
||||
public RelayCommand<CancelEventArgs> WindowClosingCommand { get; }
|
||||
public RelayCommand RunCommand { get; }
|
||||
public RelayCommand RunAllCommand { get; }
|
||||
public RelayCommand RunSelectedCommand { get; }
|
||||
public RelayCommand CancelCommand { get; }
|
||||
public ICommand TraitCheckedChangedCommand { 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 ICommand AutoReloadAssembliesCommand { get; }
|
||||
|
||||
public CommandBindingCollection CommandBindings { get; }
|
||||
|
||||
@@ -68,6 +84,7 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
CommandBindings = CreateCommandBindings();
|
||||
|
||||
this.testUtil = new Xunit.Runner.Wpf.Impl.RemoteTestUtil(Dispatcher.CurrentDispatcher);
|
||||
this.assemblyWatcher = new Impl.TestAssemblyWatcher(Dispatcher.CurrentDispatcher);
|
||||
this.TestCasesCaption = "Test Cases (0)";
|
||||
|
||||
this.FilteredTestCases = new FilteredCollectionView<TestCaseViewModel, SearchQuery>(
|
||||
@@ -78,7 +95,8 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
this.ExitCommand = new RelayCommand(OnExecuteExit);
|
||||
this.WindowLoadedCommand = new RelayCommand(OnExecuteWindowLoaded);
|
||||
this.WindowClosingCommand = new RelayCommand<CancelEventArgs>(OnExecuteWindowClosing);
|
||||
this.RunCommand = new RelayCommand(OnExecuteRun, CanExecuteRun);
|
||||
this.RunAllCommand = new RelayCommand(OnExecuteRunAll, CanExecuteRunAll);
|
||||
this.RunSelectedCommand = new RelayCommand(OnExecuteRunSelected, CanExecuteRunSelected);
|
||||
this.CancelCommand = new RelayCommand(OnExecuteCancel, CanExecuteCancel);
|
||||
this.TraitCheckedChangedCommand = new RelayCommand<TraitViewModel>(OnExecuteTraitCheckedChanged);
|
||||
this.TraitsClearCommand = new RelayCommand(OnExecuteTraitsClear);
|
||||
@@ -86,8 +104,11 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
this.AssemblyReloadAllCommand = new RelayCommand(OnExecuteAssemblyReloadAll);
|
||||
this.AssemblyRemoveCommand = new RelayCommand(OnExecuteAssemblyRemove, CanExecuteAssemblyRemove);
|
||||
this.AssemblyRemoveAllCommand = new RelayCommand(OnExecuteAssemblyRemoveAll);
|
||||
this.AutoReloadAssembliesCommand = new RelayCommand(OnToggleAutoReloadAssemblies);
|
||||
|
||||
RebuildRecentAssembliesMenu();
|
||||
AutoReloadAssemblies = this.settings.GetAutoReloadAssemblies();
|
||||
UpdateAutoReloadStatus();
|
||||
}
|
||||
|
||||
private void RebuildRecentAssembliesMenu()
|
||||
@@ -133,10 +154,13 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -157,8 +181,32 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
|
||||
private void TestCases_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
TestCasesCaption = $"Test Cases ({FilteredTestCases.Count:#,0})";
|
||||
MaximumProgress = FilteredTestCases.Count;
|
||||
UpdateTestCaseInfo(useSelected: false);
|
||||
ClearSelectionFlags();
|
||||
}
|
||||
|
||||
private void ClearSelectionFlags()
|
||||
{
|
||||
foreach (var test in this.allTestCases)
|
||||
{
|
||||
test.IsSelected = false;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateTestCaseInfo(bool useSelected)
|
||||
{
|
||||
var count = FilteredTestCases.Count;
|
||||
if (useSelected)
|
||||
{
|
||||
var selected = FilteredTestCases.Count(tc => tc.IsSelected);
|
||||
if (selected > 0)
|
||||
{
|
||||
count = selected;
|
||||
}
|
||||
}
|
||||
|
||||
TestCasesCaption = $"Test Cases ({count:#,0})";
|
||||
MaximumProgress = count;
|
||||
}
|
||||
|
||||
public List<TestAssemblyViewModel> SelectedAssemblies
|
||||
@@ -166,8 +214,8 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
get { return Assemblies.Where(x => x.IsSelected).ToList(); }
|
||||
}
|
||||
|
||||
private string testCasesCaption;
|
||||
public string TestCasesCaption
|
||||
private string? testCasesCaption;
|
||||
public string? TestCasesCaption
|
||||
{
|
||||
get { return testCasesCaption; }
|
||||
private set { Set(ref testCasesCaption, value); }
|
||||
@@ -177,14 +225,32 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
public int TestsCompleted
|
||||
{
|
||||
get { return testsCompleted; }
|
||||
set { Set(ref testsCompleted, value);
|
||||
|
||||
if (TaskbarManager.IsPlatformSupported)
|
||||
{
|
||||
var tb = TaskbarManager.Instance;
|
||||
tb.SetProgressState(GetTaskBarState());
|
||||
tb.SetProgressValue(value, this.MaximumProgress);
|
||||
}
|
||||
set
|
||||
{
|
||||
Set(ref testsCompleted, value);
|
||||
UpdateProgress();
|
||||
}
|
||||
}
|
||||
|
||||
private TestCaseViewModel? selectedTest;
|
||||
public TestCaseViewModel? SelectedTestCase
|
||||
{
|
||||
get { return selectedTest; }
|
||||
|
||||
set
|
||||
{
|
||||
Set(ref selectedTest, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateProgress()
|
||||
{
|
||||
if (TaskbarManager.IsPlatformSupported)
|
||||
{
|
||||
var tb = TaskbarManager.Instance;
|
||||
tb.SetProgressState(GetTaskBarState());
|
||||
tb.SetProgressValue(this.TestsCompleted, this.MaximumProgress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,6 +267,13 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
private int testsRunning = 0;
|
||||
public int TestsRunning
|
||||
{
|
||||
get { return testsRunning; }
|
||||
set { Set(ref testsRunning, value); }
|
||||
}
|
||||
|
||||
private int testsPassed = 0;
|
||||
public int TestsPassed
|
||||
{
|
||||
@@ -226,14 +299,24 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
public int MaximumProgress
|
||||
{
|
||||
get { return maximumProgress; }
|
||||
set { Set(ref maximumProgress, value); }
|
||||
|
||||
set
|
||||
{
|
||||
Set(ref maximumProgress, value);
|
||||
UpdateProgress();
|
||||
}
|
||||
}
|
||||
|
||||
private TestState currentRunState;
|
||||
public TestState CurrentRunState
|
||||
{
|
||||
get { return currentRunState; }
|
||||
set { Set(ref currentRunState, value); }
|
||||
|
||||
set
|
||||
{
|
||||
Set(ref currentRunState, value);
|
||||
UpdateProgress();
|
||||
}
|
||||
}
|
||||
|
||||
private string output = string.Empty;
|
||||
@@ -317,7 +400,7 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
var taskList = new List<Task>();
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
taskList.Add(this.testUtil.Discover(assembly.AssemblyFileName, this.OnTestsDiscovered, this.cancellationTokenSource.Token));
|
||||
taskList.Add(this.testUtil.Discover(assembly.AssemblyFileName, this.OnTestsDiscovered, CancellationToken));
|
||||
|
||||
var assemblyViewModel = new TestAssemblyViewModel(assembly);
|
||||
|
||||
@@ -336,12 +419,31 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
foreach (var assemblyViewModel in newAssemblyViewModels)
|
||||
{
|
||||
assemblyViewModel.State = AssemblyState.Ready;
|
||||
assemblyWatcher.AddAssembly(assemblyViewModel.FileName);
|
||||
}
|
||||
|
||||
RebuildRecentAssembliesMenu();
|
||||
}
|
||||
}
|
||||
|
||||
private CancellationToken CancellationToken
|
||||
=> cancellationTokenSource == null
|
||||
? CancellationToken.None
|
||||
: cancellationTokenSource.Token;
|
||||
|
||||
public bool ReloadAssemblies(IEnumerable<string> assemblies)
|
||||
{
|
||||
if (IsBusy)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var testAssemblies = Assemblies.Where(assembly => assemblies.Contains(assembly.FileName));
|
||||
Application.Current.Dispatcher.InvokeAsync(() => ReloadAssemblies(testAssemblies));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task ReloadAssemblies(IEnumerable<TestAssemblyViewModel> assemblies)
|
||||
{
|
||||
try
|
||||
@@ -356,7 +458,7 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
var assemblyFileName = assemblyViewModel.FileName;
|
||||
RemoveAssemblyTestCases(assemblyFileName);
|
||||
|
||||
taskList.Add(this.testUtil.Discover(assemblyFileName, OnTestsDiscovered, cancellationTokenSource.Token));
|
||||
taskList.Add(this.testUtil.Discover(assemblyFileName, OnTestsDiscovered, CancellationToken));
|
||||
}
|
||||
|
||||
return taskList;
|
||||
@@ -377,6 +479,7 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
{
|
||||
foreach (var assembly in assemblies.ToList())
|
||||
{
|
||||
assemblyWatcher.RemoveAssembly(assembly.FileName);
|
||||
RemoveAssemblyTestCases(assembly.FileName);
|
||||
Assemblies.Remove(assembly);
|
||||
}
|
||||
@@ -391,6 +494,7 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
{
|
||||
if (string.Compare(this.allTestCases[i].AssemblyFileName, assemblyPath, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
this.allTestCaseUniqueIDs.Remove(this.allTestCases[i].UniqueID);
|
||||
this.allTestCases.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
@@ -401,10 +505,10 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
}
|
||||
|
||||
/// <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
|
||||
/// 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
|
||||
/// way to
|
||||
/// </summary>
|
||||
private void RebuildTraits()
|
||||
{
|
||||
@@ -421,7 +525,8 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
set
|
||||
{
|
||||
isBusy = value;
|
||||
RunCommand.RaiseCanExecuteChanged();
|
||||
RunAllCommand.RaiseCanExecuteChanged();
|
||||
RunSelectedCommand.RaiseCanExecuteChanged();
|
||||
CancelCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
@@ -448,7 +553,7 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
var assemblyFileName = enumerable.First();
|
||||
enumerable = enumerable.Skip(1);
|
||||
|
||||
var configFileName = (string)null;
|
||||
var configFileName = (string?)null;
|
||||
if (IsConfigFile(enumerable.FirstOrDefault()))
|
||||
{
|
||||
configFileName = enumerable.First();
|
||||
@@ -463,61 +568,87 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
=> (fileName?.EndsWith(".config", StringComparison.OrdinalIgnoreCase) ?? false) ||
|
||||
(fileName?.EndsWith(".json", StringComparison.OrdinalIgnoreCase) ?? false);
|
||||
|
||||
private bool CanExecuteRun()
|
||||
private bool CanExecuteRunAll()
|
||||
=> !IsBusy && FilteredTestCases.Any();
|
||||
|
||||
private async void OnExecuteRun()
|
||||
private bool CanExecuteRunSelected()
|
||||
=> !IsBusy && SelectedTestCase != null;
|
||||
|
||||
private async void OnExecuteRunAll()
|
||||
{
|
||||
Debug.Assert(this.runningTests == null);
|
||||
Debug.Assert(this.testsToRun == null);
|
||||
UpdateTestCaseInfo(useSelected: false);
|
||||
|
||||
await ExecuteTestSessionOperation(RunTests);
|
||||
await ExecuteTestSessionOperation(RunFilteredTests);
|
||||
|
||||
this.runningTests = null;
|
||||
this.testsToRun = null;
|
||||
}
|
||||
|
||||
private List<Task> RunTests()
|
||||
private List<Task> RunFilteredTests()
|
||||
{
|
||||
return RunTests(FilteredTestCases.ToImmutableList());
|
||||
}
|
||||
|
||||
private async void OnExecuteRunSelected()
|
||||
{
|
||||
Debug.Assert(this.testsToRun == null);
|
||||
Debug.Assert(this.SelectedTestCase != null);
|
||||
UpdateTestCaseInfo(useSelected: true);
|
||||
|
||||
await ExecuteTestSessionOperation(RunSelectedTests);
|
||||
|
||||
this.testsToRun = null;
|
||||
}
|
||||
|
||||
private List<Task> RunSelectedTests()
|
||||
{
|
||||
return RunTests(ImmutableList.CreateRange(this.FilteredTestCases.Where(tc => tc.IsSelected)));
|
||||
}
|
||||
|
||||
private List<Task> RunTests(ImmutableList<TestCaseViewModel> 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 = FilteredTestCases.ToImmutableList();
|
||||
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<Task>();
|
||||
|
||||
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, CancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
var builder = ImmutableArray.CreateBuilder<string>();
|
||||
|
||||
foreach (var testCase in this.runningTests)
|
||||
foreach (var testCase in this.testsToRun)
|
||||
{
|
||||
if (testCase.AssemblyFileName == assemblyFileName)
|
||||
{
|
||||
builder.Add(testCase.DisplayName);
|
||||
builder.Add(testCase.UniqueID);
|
||||
}
|
||||
}
|
||||
|
||||
task = this.testUtil.RunSpecific(assemblyFileName, builder.ToImmutable(), OnTestsFinished, this.cancellationTokenSource.Token);
|
||||
task = this.testUtil.RunSpecific(assemblyFileName, builder.ToImmutable(), OnTestStateChange, CancellationToken);
|
||||
}
|
||||
|
||||
testSessionList.Add(task);
|
||||
@@ -557,6 +688,9 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
|
||||
foreach (var testCase in testCases)
|
||||
{
|
||||
if (this.allTestCaseUniqueIDs.Contains(testCase.UniqueID))
|
||||
continue;
|
||||
|
||||
traitWorkerList.Clear();
|
||||
|
||||
// Get or create traits.
|
||||
@@ -579,6 +713,7 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
|
||||
var testCaseViewModel = new TestCaseViewModel(
|
||||
testCase.DisplayName,
|
||||
testCase.UniqueID,
|
||||
testCase.SkipReason,
|
||||
testCase.AssemblyPath,
|
||||
traitWorkerList);
|
||||
@@ -588,32 +723,48 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
TestsSkipped++;
|
||||
}
|
||||
|
||||
this.allTestCaseUniqueIDs.Add(testCase.UniqueID);
|
||||
this.allTestCases.Add(testCaseViewModel);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTestsFinished(IEnumerable<TestResultData> testResultData)
|
||||
private void OnTestStateChange(IEnumerable<TestResultData> testResultData)
|
||||
{
|
||||
Debug.Assert(this.runningTests != null);
|
||||
Debug.Assert(this.testsToRun != null);
|
||||
|
||||
foreach (var result in testResultData)
|
||||
{
|
||||
var testCase = this.runningTests.Single(x => x.DisplayName == result.TestCaseDisplayName);
|
||||
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)
|
||||
@@ -631,7 +782,7 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
private void OnExecuteCancel()
|
||||
{
|
||||
Debug.Assert(CanExecuteCancel());
|
||||
this.cancellationTokenSource.Cancel();
|
||||
cancellationTokenSource?.Cancel();
|
||||
}
|
||||
|
||||
private void OnExecuteTraitCheckedChanged(TraitViewModel trait)
|
||||
@@ -677,6 +828,41 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
{
|
||||
RemoveAssemblies(Assemblies.ToArray());
|
||||
}
|
||||
private void OnToggleAutoReloadAssemblies()
|
||||
{
|
||||
ToggleReloadAssemblies();
|
||||
}
|
||||
|
||||
private void ToggleReloadAssemblies()
|
||||
{
|
||||
this.settings.ToggleAutoReloadAssemblies();
|
||||
AutoReloadAssemblies = this.settings.GetAutoReloadAssemblies();
|
||||
UpdateAutoReloadStatus();
|
||||
}
|
||||
|
||||
private void UpdateAutoReloadStatus()
|
||||
{
|
||||
if (AutoReloadAssemblies)
|
||||
{
|
||||
assemblyWatcher.EnableWatch(ReloadAssemblies);
|
||||
}
|
||||
else
|
||||
{
|
||||
assemblyWatcher.DisableWatch();
|
||||
}
|
||||
}
|
||||
|
||||
public bool FilterRunningTests
|
||||
{
|
||||
get { return searchQuery.FilterRunningTests; }
|
||||
set
|
||||
{
|
||||
if (Set(ref searchQuery.FilterRunningTests, value))
|
||||
{
|
||||
FilterAfterDelay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool FilterPassedTests
|
||||
{
|
||||
@@ -720,7 +906,17 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
public static TestComparer Instance { get; } = new TestComparer();
|
||||
|
||||
public int Compare(TestCaseViewModel x, TestCaseViewModel y)
|
||||
=> StringComparer.Ordinal.Compare(x.DisplayName, y.DisplayName);
|
||||
{
|
||||
int result = StringComparer.OrdinalIgnoreCase.Compare(x.DisplayName, y.DisplayName);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
result = StringComparer.Ordinal.Compare(x.DisplayName, y.DisplayName);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
return StringComparer.Ordinal.Compare(x.UniqueID, y.UniqueID);
|
||||
}
|
||||
|
||||
private TestComparer() { }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -10,9 +10,11 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
private TestState _state = TestState.NotRun;
|
||||
|
||||
public string DisplayName { get; }
|
||||
public string UniqueID { get; }
|
||||
public string SkipReason { get; }
|
||||
public string AssemblyFileName { get; }
|
||||
public ImmutableArray<TraitViewModel> Traits { get; }
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public bool HasSkipReason => !string.IsNullOrEmpty(this.SkipReason);
|
||||
|
||||
@@ -22,9 +24,10 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
set { Set(ref _state, value); }
|
||||
}
|
||||
|
||||
public TestCaseViewModel(string displayName, string skipReason, string assemblyFileName, IEnumerable<TraitViewModel> traits)
|
||||
public TestCaseViewModel(string displayName, string uniqueID, string skipReason, string assemblyFileName, IEnumerable<TraitViewModel> traits)
|
||||
{
|
||||
this.DisplayName = displayName;
|
||||
this.UniqueID = uniqueID;
|
||||
this.SkipReason = skipReason;
|
||||
this.AssemblyFileName = assemblyFileName;
|
||||
this.Traits = traits.ToImmutableArray();
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
{
|
||||
public partial class TraitViewModel : ViewModelBase
|
||||
{
|
||||
private readonly TraitViewModel _parent;
|
||||
private readonly TraitViewModel? _parent;
|
||||
private bool? _isChecked;
|
||||
private bool _isExpanded;
|
||||
private string _text;
|
||||
@@ -19,7 +19,7 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
{
|
||||
}
|
||||
|
||||
private TraitViewModel(TraitViewModel parent, string text)
|
||||
private TraitViewModel(TraitViewModel? parent, string text)
|
||||
{
|
||||
this._parent = parent;
|
||||
this._isChecked = false;
|
||||
|
||||
@@ -3,16 +3,17 @@
|
||||
<package id="CommonServiceLocator" version="1.3" targetFramework="net452" />
|
||||
<package id="MvvmLight" version="5.3.0.0" targetFramework="net46" />
|
||||
<package id="MvvmLightLibs" version="5.3.0.0" targetFramework="net46" />
|
||||
<package id="NuGet.CommandLine" version="3.4.3" targetFramework="net46" developmentDependency="true" />
|
||||
<package id="System.Collections" version="4.0.10" targetFramework="net46" />
|
||||
<package id="System.Collections.Immutable" version="1.1.37" targetFramework="net452" />
|
||||
<package id="System.Diagnostics.Debug" version="4.0.10" targetFramework="net46" />
|
||||
<package id="System.Globalization" version="4.0.10" targetFramework="net46" />
|
||||
<package id="System.Linq" version="4.0.0" targetFramework="net46" />
|
||||
<package id="System.Resources.ResourceManager" version="4.0.0" targetFramework="net46" />
|
||||
<package id="System.Runtime" version="4.0.20" targetFramework="net46" />
|
||||
<package id="System.Runtime.Extensions" version="4.0.10" targetFramework="net46" />
|
||||
<package id="System.Threading" version="4.0.10" targetFramework="net46" />
|
||||
<package id="NuGet.CommandLine" version="2.8.3" targetFramework="net46" />
|
||||
<package id="System.Collections" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Collections.Immutable" version="1.4.0-preview1-25305-02" targetFramework="net46" />
|
||||
<package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Globalization" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Linq" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Runtime" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Threading" version="4.3.0" targetFramework="net46" />
|
||||
<package id="Tvl.NuGet.BuildTasks" version="1.0.0-alpha002" targetFramework="net46" developmentDependency="true" />
|
||||
<package id="WindowsAPICodePack-Core" version="1.1.2" targetFramework="net46" />
|
||||
<package id="WindowsAPICodePack-Shell" version="1.1.1" targetFramework="net46" />
|
||||
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.props" Condition="Exists('..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
@@ -68,10 +69,10 @@
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Collections.Immutable, Version=1.1.37.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Collections.Immutable.1.1.37\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="System.Collections.Immutable, Version=1.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Collections.Immutable.1.4.0-preview1-25305-02\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ComponentModel.Composition" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MvvmLightLibs.5.3.0.0\lib\net45\System.Windows.Interactivity.dll</HintPath>
|
||||
@@ -106,6 +107,8 @@
|
||||
<Compile Include="Impl\RemoteTestUtil.Connection.cs" />
|
||||
<Compile Include="Impl\RemoteTestUtil.BackgroundRunner.cs" />
|
||||
<Compile Include="Impl\RemoteTestUtil.cs" />
|
||||
<Compile Include="Impl\TestAssemblyWatcher.cs" />
|
||||
<Compile Include="ITestAssemblyWatcher.cs" />
|
||||
<Compile Include="ITestUtil.cs" />
|
||||
<Compile Include="Persistence\Settings.cs" />
|
||||
<Compile Include="Persistence\Storage.cs" />
|
||||
@@ -149,10 +152,17 @@
|
||||
</EmbeddedResource>
|
||||
<None Include="packages.config" />
|
||||
<AppDesigner Include="Properties\" />
|
||||
<None Include="xunit.runner.wpf.nuspec">
|
||||
<None Include="xunit.runner.wpf.targets">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<NuGetManifest Include="xunit.runner.wpf.nuspec">
|
||||
<Version>$(NuPkgVersion)</Version>
|
||||
<PackageAnalysis>False</PackageAnalysis>
|
||||
</NuGetManifest>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
@@ -192,10 +202,19 @@
|
||||
<ItemGroup>
|
||||
<Resource Include="Artwork\Application.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Artwork\Running_large.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Artwork\Running_small.png" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="AfterBuild">
|
||||
<Exec Command=""$(SolutionDir)packages\NuGet.CommandLine.3.4.3\tools\NuGet.exe" pack xunit.runner.wpf.nuspec -Version $(NuPkgVersion) -OutputDirectory ." WorkingDirectory="$(OutDir)" LogStandardErrorAsError="true" ConsoleToMSBuild="true">
|
||||
<Output TaskParameter="ConsoleOutput" PropertyName="OutputOfExec" />
|
||||
</Exec>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.props'))" />
|
||||
<Error Condition="!Exists('..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
<Import Project="..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.targets" Condition="Exists('..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.targets')" />
|
||||
</Project>
|
||||
@@ -12,8 +12,9 @@
|
||||
<tags>XUnit Gui test runner</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="*.dll" target="tools\"/>
|
||||
<file src="*.exe" target="tools\" exclude="*vshost*"/>
|
||||
<file src="*.config" target="tools\" exclude="*vshost*"/>
|
||||
<file src="bin\$Configuration$\*.dll" target="tools\"/>
|
||||
<file src="xunit.runner.wpf.targets" target="build\net45"/>
|
||||
<file src="bin\$Configuration$\*.exe" target="tools\" exclude="**\*vshost*"/>
|
||||
<file src="bin\$Configuration$\*.config" target="tools\" exclude="**\*vshost*"/>
|
||||
</files>
|
||||
</package>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<StartAction Condition="'$(StartActions)' == ''">Program</StartAction>
|
||||
<StartProgram Condition="'$(StartProgram)' == ''">$(MSBuildThisFileDirectory)..\tools\xunit.runner.wpf.exe</StartProgram>
|
||||
<StartArguments Condition="'$(StartArguments)' == ''">$(AssemblyName).dll</StartArguments>
|
||||
<StartWorkingDirectory Condition="'$(StartWorkingDirectory)' == ''">$(OutputDirectory)</StartWorkingDirectory>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user