Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fd72605d9d | |||
| cb39c7af29 | |||
| 6aa5a6c0cd | |||
| 90643fbf8f | |||
| 0d96b29a7a | |||
| d861f4a6ee | |||
| 3f894e0e7c | |||
| 4371a4e837 | |||
| 65adde358c | |||
| ae5e46d772 | |||
| 9934a70f52 | |||
| 13afb6eea5 | |||
| e3a17c5308 | |||
| 89be98bebc | |||
| 9e5ac70234 | |||
| b35da545d6 | |||
| 3a7d01b87e | |||
| 0e24227de0 | |||
| 65f3fc970e | |||
| 06f1c8c703 | |||
| 29aa127230 |
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\xunit.core.2.1.0-beta4-build3109\build\portable-net45+netcore45+wp8+wpa81\xunit.core.props" Condition="Exists('..\packages\xunit.core.2.1.0-beta4-build3109\build\portable-net45+netcore45+wp8+wpa81\xunit.core.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
@@ -45,12 +44,16 @@
|
||||
<HintPath>..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="xunit.assert, Version=2.1.0.3109, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.assert.2.1.0-beta4-build3109\lib\dotnet\xunit.assert.dll</HintPath>
|
||||
<Reference Include="xunit.assert, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="xunit.core, Version=2.1.0.3109, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.extensibility.core.2.1.0-beta4-build3109\lib\dotnet\xunit.core.dll</HintPath>
|
||||
<Reference Include="xunit.core, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="xunit.execution.desktop, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
@@ -62,12 +65,6 @@
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<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\xunit.core.2.1.0-beta4-build3109\build\portable-net45+netcore45+wp8+wpa81\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.1.0-beta4-build3109\build\portable-net45+netcore45+wp8+wpa81\xunit.core.props'))" />
|
||||
</Target>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="xunit" version="2.1.0-beta4-build3109" targetFramework="net452" />
|
||||
<package id="xunit" version="2.1.0" targetFramework="net452" />
|
||||
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
|
||||
<package id="xunit.assert" version="2.1.0-beta4-build3109" targetFramework="net452" />
|
||||
<package id="xunit.core" version="2.1.0-beta4-build3109" targetFramework="net452" />
|
||||
<package id="xunit.extensibility.core" version="2.1.0-beta4-build3109" targetFramework="net452" />
|
||||
<package id="xunit.assert" version="2.1.0" targetFramework="net452" />
|
||||
<package id="xunit.core" version="2.1.0" targetFramework="net452" />
|
||||
<package id="xunit.extensibility.core" version="2.1.0" targetFramework="net452" />
|
||||
<package id="xunit.extensibility.execution" version="2.1.0" targetFramework="net452" />
|
||||
</packages>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
|
||||
</startup>
|
||||
</configuration>
|
||||
</configuration>
|
||||
|
||||
@@ -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);
|
||||
@@ -43,7 +46,7 @@ namespace xunit.runner.worker
|
||||
{
|
||||
using (AssemblyHelper.SubscribeResolve())
|
||||
using (var xunit = new XunitFrontController(
|
||||
useAppDomain: true,
|
||||
AppDomainSupport.IfAvailable,
|
||||
assemblyFileName: fileName,
|
||||
diagnosticMessageSink: new MessageVisitor(),
|
||||
shadowCopy: false))
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using xunit.runner.data;
|
||||
|
||||
namespace xunit.runner.worker
|
||||
{
|
||||
internal sealed class Listener
|
||||
{
|
||||
private readonly string _pipeName;
|
||||
private readonly List<Task> _taskList = new List<Task>();
|
||||
|
||||
internal Listener(string pipeName)
|
||||
{
|
||||
_pipeName = pipeName;
|
||||
}
|
||||
|
||||
internal void Go()
|
||||
{
|
||||
bool success;
|
||||
do
|
||||
{
|
||||
_taskList.RemoveAll(x => x.IsCompleted);
|
||||
success = GoOne();
|
||||
} while (success);
|
||||
|
||||
// Wait for the existing tasks to complete before stopping the listener
|
||||
Task.WaitAll(_taskList.ToArray());
|
||||
}
|
||||
|
||||
private bool GoOne()
|
||||
{
|
||||
try
|
||||
{
|
||||
var namedPipe = new NamedPipeServerStream(_pipeName, PipeDirection.InOut, maxNumberOfServerInstances: NamedPipeServerStream.MaxAllowedServerInstances);
|
||||
namedPipe.WaitForConnection();
|
||||
_taskList.Add(Task.Run(() => ProcessConnection(namedPipe)));
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error creating named pipe {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessConnection(NamedPipeServerStream stream)
|
||||
{
|
||||
Console.WriteLine("Connection established processing");
|
||||
ProcessConnectionCore(stream);
|
||||
Console.WriteLine("Connection completed");
|
||||
}
|
||||
|
||||
private static void ProcessConnectionCore(NamedPipeServerStream stream)
|
||||
{
|
||||
Debug.Assert(stream.IsConnected);
|
||||
|
||||
try
|
||||
{
|
||||
var reader = new ClientReader(stream);
|
||||
var action = reader.ReadString();
|
||||
var argument = reader.ReadString();
|
||||
switch (action)
|
||||
{
|
||||
case Constants.ActionDiscover:
|
||||
Discover(stream, argument);
|
||||
break;
|
||||
case Constants.ActionRunAll:
|
||||
RunAll(stream, argument);
|
||||
break;
|
||||
case Constants.ActionRunSpecific:
|
||||
RunSpecific(stream, argument);
|
||||
break;
|
||||
default:
|
||||
Debug.Fail($"Invalid action {action}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Happens during a rude disconnect by the client
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static void Discover(Stream stream, string assemblyPath)
|
||||
{
|
||||
Console.WriteLine($"discover started: {assemblyPath}");
|
||||
DiscoverUtil.Go(assemblyPath, stream);
|
||||
Console.WriteLine("discover ended");
|
||||
}
|
||||
|
||||
private static void RunAll(Stream stream, string assemblyPath)
|
||||
{
|
||||
Console.WriteLine($"run all started: {assemblyPath}");
|
||||
RunUtil.RunAll(assemblyPath, stream);
|
||||
Console.WriteLine("run all ended");
|
||||
}
|
||||
|
||||
private static void RunSpecific(Stream stream, string assemblyPath)
|
||||
{
|
||||
Console.WriteLine($"run specific started: {assemblyPath}");
|
||||
RunUtil.RunSpecific(assemblyPath, stream);
|
||||
Console.WriteLine("run specific ended");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Linq;
|
||||
@@ -16,83 +17,32 @@ namespace xunit.runner.worker
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
if (args.Length < 3)
|
||||
if (args.Length < 2)
|
||||
{
|
||||
Usage();
|
||||
return ExitError;
|
||||
}
|
||||
|
||||
string pipeName = args[0];
|
||||
string action = args[1];
|
||||
string argument = args[2];
|
||||
|
||||
try
|
||||
var pipeName = args[0];
|
||||
var parentPid = Int32.Parse(args[1]);
|
||||
var process = Process.GetProcessById(parentPid);
|
||||
if (process == null)
|
||||
{
|
||||
using (var connection = CreateConnection(pipeName))
|
||||
{
|
||||
connection.WaitForClientConnect();
|
||||
|
||||
var stream = connection.Stream;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case Constants.ActionDiscover:
|
||||
Discover(stream, argument);
|
||||
break;
|
||||
case Constants.ActionRunAll:
|
||||
RunAll(stream, argument);
|
||||
break;
|
||||
case Constants.ActionRunSpecific:
|
||||
RunSpecific(stream, argument);
|
||||
break;
|
||||
default:
|
||||
Usage();
|
||||
return ExitError;
|
||||
}
|
||||
|
||||
connection.WaitForClientDone();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Errors will happen during a rude shut down from the client. Print out to the screen
|
||||
// for diagnostics and continue on.
|
||||
Console.Error.WriteLine(ex.Message);
|
||||
Console.WriteLine($"Invalid parent pid {parentPid}");
|
||||
return ExitError;
|
||||
}
|
||||
|
||||
Task.Run(() => WaitForParentExit(process));
|
||||
|
||||
var listener = new Listener(pipeName);
|
||||
listener.Go();
|
||||
return ExitSuccess;
|
||||
}
|
||||
|
||||
private static Connection CreateConnection(string pipeName)
|
||||
private static void WaitForParentExit(Process process)
|
||||
{
|
||||
if (pipeName == "test")
|
||||
{
|
||||
return new TestConnection();
|
||||
}
|
||||
|
||||
return new NamedPipeConnection(pipeName);
|
||||
}
|
||||
|
||||
private static void Discover(Stream stream, string assemblyPath)
|
||||
{
|
||||
Console.WriteLine($"discover started: {assemblyPath}");
|
||||
DiscoverUtil.Go(assemblyPath, stream);
|
||||
Console.WriteLine("discover ended");
|
||||
}
|
||||
|
||||
private static void RunAll(Stream stream, string assemblyPath)
|
||||
{
|
||||
Console.WriteLine($"run all started: {assemblyPath}");
|
||||
RunUtil.RunAll(assemblyPath, stream);
|
||||
Console.WriteLine("run all ended");
|
||||
}
|
||||
|
||||
private static void RunSpecific(Stream stream, string assemblyPath)
|
||||
{
|
||||
Console.WriteLine($"run specific started: {assemblyPath}");
|
||||
RunUtil.RunSpecific(assemblyPath, stream);
|
||||
Console.WriteLine("run specific ended");
|
||||
process.WaitForExit();
|
||||
Environment.Exit(ExitSuccess);
|
||||
}
|
||||
|
||||
private static void Usage()
|
||||
|
||||
@@ -122,8 +122,8 @@ namespace xunit.runner.worker
|
||||
{
|
||||
using (AssemblyHelper.SubscribeResolve())
|
||||
using (var xunit = new XunitFrontController(
|
||||
AppDomainSupport.IfAvailable,
|
||||
assemblyFileName: assemblyPath,
|
||||
useAppDomain: true,
|
||||
shadowCopy: false,
|
||||
diagnosticMessageSink: new MessageVisitor()))
|
||||
using (var writer = new ClientWriter(stream))
|
||||
@@ -139,8 +139,8 @@ namespace xunit.runner.worker
|
||||
{
|
||||
using (AssemblyHelper.SubscribeResolve())
|
||||
using (var xunit = new XunitFrontController(
|
||||
AppDomainSupport.IfAvailable,
|
||||
assemblyFileName: assemblyPath,
|
||||
useAppDomain: true,
|
||||
shadowCopy: false,
|
||||
diagnosticMessageSink: new MessageVisitor()))
|
||||
using (var writer = new ClientWriter(stream))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
|
||||
<package id="xunit.runner.utility" version="2.1.0-beta4-build3109" targetFramework="net452" />
|
||||
<package id="xunit.runner.utility" version="2.1.0" targetFramework="net46" />
|
||||
</packages>
|
||||
@@ -9,9 +9,10 @@
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>xunit.runner.worker</RootNamespace>
|
||||
<AssemblyName>xunit.runner.worker</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
@@ -44,13 +45,15 @@
|
||||
<Reference Include="xunit.abstractions">
|
||||
<HintPath>..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="xunit.runner.utility.desktop">
|
||||
<HintPath>..\packages\xunit.runner.utility.2.1.0-beta4-build3109\lib\net35\xunit.runner.utility.desktop.dll</HintPath>
|
||||
<Reference Include="xunit.runner.utility.desktop, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.runner.utility.2.1.0\lib\net35\xunit.runner.utility.desktop.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Connection.cs" />
|
||||
<Compile Include="DiscoverUtil.cs" />
|
||||
<Compile Include="Listener.cs" />
|
||||
<Compile Include="MessageVisitor.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
|
||||
</startup>
|
||||
</configuration>
|
||||
</configuration>
|
||||
|
||||
|
Before Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 703 B |
|
After Width: | Height: | Size: 401 B |
|
Before Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 659 B |
|
After Width: | Height: | Size: 402 B |
|
Before Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 523 B |
|
After Width: | Height: | Size: 298 B |
@@ -1,27 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using xunit.runner.data;
|
||||
using xunit.runner.wpf.ViewModel;
|
||||
|
||||
namespace xunit.runner.wpf.Converters
|
||||
{
|
||||
public class TestStateConverter : IValueConverter
|
||||
{
|
||||
private static ImageSource passedSource;
|
||||
private static ImageSource failedSource;
|
||||
private static ImageSource passedSource;
|
||||
private static ImageSource skippedSource;
|
||||
|
||||
static TestStateConverter()
|
||||
{
|
||||
passedSource = LoadResourceImage("Passed.ico");
|
||||
failedSource = LoadResourceImage("Failed.ico");
|
||||
skippedSource = LoadResourceImage("Skipped.ico");
|
||||
failedSource = LoadResourceImage("Failed_small.png");
|
||||
passedSource = LoadResourceImage("Passed_small.png");
|
||||
skippedSource = LoadResourceImage("Skipped_small.png");
|
||||
}
|
||||
|
||||
private static BitmapImage LoadResourceImage(string resourceName)
|
||||
@@ -42,10 +38,10 @@ namespace xunit.runner.wpf.Converters
|
||||
{
|
||||
case TestState.Failed:
|
||||
return Brushes.Red;
|
||||
case TestState.Skipped:
|
||||
return Brushes.Yellow;
|
||||
case TestState.Passed:
|
||||
return Brushes.Green;
|
||||
case TestState.Skipped:
|
||||
return Brushes.Yellow;
|
||||
default:
|
||||
return Brushes.Gray;
|
||||
}
|
||||
@@ -56,10 +52,10 @@ namespace xunit.runner.wpf.Converters
|
||||
{
|
||||
case TestState.Failed:
|
||||
return failedSource;
|
||||
case TestState.Skipped:
|
||||
return skippedSource;
|
||||
case TestState.Passed:
|
||||
return passedSource;
|
||||
case TestState.Skipped:
|
||||
return skippedSource;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -54,26 +54,29 @@ namespace xunit.runner.wpf.Impl
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility for reading a collection of <see cref="{T}"/> values from the given
|
||||
/// <see cref="ClientReader"/> value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
private sealed class BackgroundReader<T> where T : class
|
||||
{
|
||||
private readonly ConcurrentQueue<T> _queue;
|
||||
private readonly ClientReader _reader;
|
||||
private readonly Func<ClientReader, T> _readValue;
|
||||
private readonly CancellationToken _cancellationToken;
|
||||
|
||||
internal ClientReader Reader => _reader;
|
||||
|
||||
internal BackgroundReader(ConcurrentQueue<T> queue, ClientReader reader, Func<ClientReader, T> readValue, CancellationToken cancellationToken)
|
||||
internal BackgroundReader(ConcurrentQueue<T> queue, ClientReader reader, Func<ClientReader, T> readValue)
|
||||
{
|
||||
_queue = queue;
|
||||
_reader = reader;
|
||||
_readValue = readValue;
|
||||
_cancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
internal Task ReadAsync()
|
||||
internal Task ReadAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return Task.Run(() => GoOnBackground(), _cancellationToken);
|
||||
return Task.Run(() => GoOnBackground(cancellationToken), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -81,9 +84,9 @@ namespace xunit.runner.wpf.Impl
|
||||
/// named pipe client stream.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private void GoOnBackground()
|
||||
private void GoOnBackground(CancellationToken cancellationToken)
|
||||
{
|
||||
while (!_cancellationToken.IsCancellationRequested)
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -116,7 +119,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 +129,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 +162,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();
|
||||
}
|
||||
|
||||
@@ -19,47 +19,37 @@ namespace xunit.runner.wpf.Impl
|
||||
private sealed class Connection : IDisposable
|
||||
{
|
||||
private NamedPipeClientStream _stream;
|
||||
private Process _process;
|
||||
private ClientReader _reader;
|
||||
|
||||
internal NamedPipeClientStream Stream => _stream;
|
||||
|
||||
internal ClientReader Reader => _reader;
|
||||
|
||||
internal Connection(NamedPipeClientStream stream, Process process)
|
||||
internal Connection(NamedPipeClientStream stream)
|
||||
{
|
||||
_stream = stream;
|
||||
_process = process;
|
||||
_reader = new ClientReader(stream);
|
||||
}
|
||||
|
||||
internal void Dispose()
|
||||
{
|
||||
if (_process != null)
|
||||
if (_stream == null)
|
||||
{
|
||||
Debug.Assert(_stream != null);
|
||||
|
||||
try
|
||||
{
|
||||
_stream.WriteAsync(new byte[] { 0 }, 0, 1);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Signal to server we are done with the connection. Okay to fail because
|
||||
// it means the server isn't listening anymore.
|
||||
}
|
||||
|
||||
_stream.Close();
|
||||
|
||||
try
|
||||
{
|
||||
_process.Kill();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Inherent race condition shutting down the process.
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_stream.WriteAsync(new byte[] { 0 }, 0, 1);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Signal to server we are done with the connection. Okay to fail because
|
||||
// it means the server isn't listening anymore.
|
||||
}
|
||||
|
||||
_stream.Close();
|
||||
_stream = null;
|
||||
}
|
||||
|
||||
void IDisposable.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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,68 +17,126 @@ namespace xunit.runner.wpf.Impl
|
||||
{
|
||||
internal sealed partial class RemoteTestUtil : ITestUtil
|
||||
{
|
||||
private struct ProcessInfo
|
||||
{
|
||||
internal readonly string PipeName;
|
||||
internal readonly Process Process;
|
||||
|
||||
internal ProcessInfo(string pipeName, Process process)
|
||||
{
|
||||
PipeName = pipeName;
|
||||
Process = process;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dispatcher _dispatcher;
|
||||
private ProcessInfo? _processInfo;
|
||||
|
||||
internal RemoteTestUtil(Dispatcher dispatcher)
|
||||
{
|
||||
_dispatcher = dispatcher;
|
||||
_processInfo = StartWorkerProcess();
|
||||
}
|
||||
|
||||
private static Connection StartWorkerProcess(string action, string argument)
|
||||
private async Task<Connection> CreateConnection(string action, string argument)
|
||||
{
|
||||
var pipeName = $"xunit.runner.wpf.pipe.{Guid.NewGuid()}";
|
||||
var processStartInfo = new ProcessStartInfo();
|
||||
processStartInfo.FileName = typeof(xunit.runner.worker.Program).Assembly.Location;
|
||||
processStartInfo.Arguments = $"{pipeName} {action} {argument}";
|
||||
processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
var pipeName = GetPipeName();
|
||||
|
||||
var process = Process.Start(processStartInfo);
|
||||
try
|
||||
{
|
||||
var stream = new NamedPipeClientStream(pipeName);
|
||||
stream.Connect();
|
||||
return new Connection(stream, process);
|
||||
await stream.ConnectAsync();
|
||||
|
||||
var writer = new ClientWriter(stream);
|
||||
writer.Write(action);
|
||||
writer.Write(argument);
|
||||
|
||||
return new Connection(stream);
|
||||
}
|
||||
catch
|
||||
{
|
||||
process.Kill();
|
||||
try
|
||||
{
|
||||
_processInfo?.Process.Kill();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Inherent race condition here. Just need to make sure the process is
|
||||
// dead as it can't even handle new connections.
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private DiscoverSession Discover(string assemblyPath, CancellationToken cancellationToken)
|
||||
private string GetPipeName()
|
||||
{
|
||||
var connection = StartWorkerProcess(Constants.ActionDiscover, assemblyPath);
|
||||
return new DiscoverSession(connection, _dispatcher, cancellationToken);
|
||||
var process = _processInfo?.Process;
|
||||
if (process != null && !process.HasExited)
|
||||
{
|
||||
return _processInfo.Value.PipeName;
|
||||
}
|
||||
|
||||
_processInfo = StartWorkerProcess();
|
||||
return _processInfo.Value.PipeName;
|
||||
}
|
||||
|
||||
private RunSession RunAll(string assemblyPath, CancellationToken cancellationToken)
|
||||
private static ProcessInfo StartWorkerProcess()
|
||||
{
|
||||
var connection = StartWorkerProcess(Constants.ActionRunAll, assemblyPath);
|
||||
return new RunSession(connection, _dispatcher, ImmutableArray<string>.Empty, cancellationToken);
|
||||
var pipeName = $"xunit.runner.wpf.pipe.{Guid.NewGuid()}";
|
||||
var processStartInfo = new ProcessStartInfo();
|
||||
processStartInfo.FileName = typeof(xunit.runner.worker.Program).Assembly.Location;
|
||||
processStartInfo.Arguments = $"{pipeName} {Process.GetCurrentProcess().Id}";
|
||||
processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
var process = Process.Start(processStartInfo);
|
||||
return new ProcessInfo(pipeName, process);
|
||||
}
|
||||
|
||||
private RunSession RunSpecific(string assemblyPath, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken)
|
||||
private async Task Discover(string assemblyPath, Action<TestCaseData> callback, CancellationToken cancellationToken)
|
||||
{
|
||||
var connection = StartWorkerProcess(Constants.ActionRunSpecific, assemblyPath);
|
||||
return new RunSession(connection, _dispatcher, testCaseDisplayNames, cancellationToken);
|
||||
var connection = await CreateConnection(Constants.ActionDiscover, assemblyPath);
|
||||
await ProcessResultsCore(connection, r => r.ReadTestCaseData(), callback, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task RunCore(string actionName, string assemblyPath, ImmutableArray<string> testCaseDisplayNames, Action<TestResultData> callback, CancellationToken cancellationToken)
|
||||
{
|
||||
var connection = await CreateConnection(actionName, assemblyPath);
|
||||
|
||||
if (!testCaseDisplayNames.IsDefaultOrEmpty)
|
||||
{
|
||||
var backgroundWriter = new BackgroundWriter<string>(new ClientWriter(connection.Stream), testCaseDisplayNames, (w, s) => w.Write(s), cancellationToken);
|
||||
await backgroundWriter.WriteAsync();
|
||||
}
|
||||
|
||||
await ProcessResultsCore(connection, r => r.ReadTestResultData(), callback, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task ProcessResultsCore<T>(Connection connection, Func<ClientReader, T> readValue, Action<T> callback, CancellationToken cancellationToken)
|
||||
where T : class
|
||||
{
|
||||
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);
|
||||
|
||||
await backgroundReader.ReadAsync(cancellationToken);
|
||||
await backgroundProducer.Task;
|
||||
}
|
||||
|
||||
#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
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
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"
|
||||
@@ -62,6 +63,7 @@
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="2*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="auto" />
|
||||
@@ -80,6 +82,7 @@
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label Content="Search:"
|
||||
Grid.Row="0" />
|
||||
<TextBox Grid.Row="1"
|
||||
@@ -95,10 +98,47 @@
|
||||
<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>
|
||||
|
||||
@@ -127,7 +167,7 @@
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<GroupBox Header="{Binding MethodsCaption}"
|
||||
<GroupBox Header="{Binding TestCasesCaption}"
|
||||
Margin="3"
|
||||
Grid.Row="0">
|
||||
<Grid>
|
||||
@@ -136,42 +176,57 @@
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ToggleButton IsChecked="{Binding IncludePassedTests}"
|
||||
Margin="3">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Artwork\Passed.ico"
|
||||
Height="16" />
|
||||
<TextBlock Margin="4,0,0,0"
|
||||
Text="{Binding TestsPassed}" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
<ToggleButton IsChecked="{Binding IncludeFailedTests}"
|
||||
Margin="3"
|
||||
Grid.Column="1">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Artwork\Failed.ico"
|
||||
Height="16" />
|
||||
<TextBlock Margin="4,0,0,0"
|
||||
Text="{Binding TestsFailed}" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
<ToggleButton IsChecked="{Binding IncludeSkippedTests}"
|
||||
Margin="3"
|
||||
Grid.Column="2">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Artwork\Skipped.ico"
|
||||
Height="16" />
|
||||
<TextBlock Margin="4,0,0,0"
|
||||
Text="{Binding TestsSkipped}" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
</Grid>
|
||||
<!--<ToolBarTray Grid.Row="0"
|
||||
ToolBarTray.IsLocked="True"
|
||||
Background="Transparent"
|
||||
RenderOptions.BitmapScalingMode="NearestNeighbor">
|
||||
<ToolBar>-->
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ToggleButton IsChecked="{Binding IncludePassedTests}"
|
||||
BorderThickness="0"
|
||||
Background="Transparent"
|
||||
Margin="0,4,2,4"
|
||||
Grid.Column="0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Artwork\Passed_large.png" />
|
||||
<TextBlock Margin="4,0"
|
||||
FontSize="16"
|
||||
Text="{Binding TestsPassed, StringFormat={}{0:#\,0}}"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
<ToggleButton IsChecked="{Binding IncludeFailedTests}"
|
||||
BorderThickness="0"
|
||||
Background="Transparent"
|
||||
Margin="2,4"
|
||||
Grid.Column="1">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Artwork\Failed_large.png" />
|
||||
<TextBlock Margin="4,0"
|
||||
FontSize="16"
|
||||
Text="{Binding TestsFailed, StringFormat={}{0:#\,0}}"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
<ToggleButton IsChecked="{Binding IncludeSkippedTests}"
|
||||
BorderThickness="0"
|
||||
Background="Transparent"
|
||||
Margin="2,4,0,4"
|
||||
Grid.Column="2">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Artwork\Skipped_large.png" />
|
||||
<TextBlock Margin="4,0"
|
||||
FontSize="16"
|
||||
Text="{Binding TestsSkipped, StringFormat={}{0:#\,0}}"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
<!--</ToolBar>
|
||||
</ToolBarTray>-->
|
||||
|
||||
<ListBox ItemsSource="{Binding TestCases}"
|
||||
SelectionMode="Extended"
|
||||
Grid.Row="1">
|
||||
@@ -184,10 +239,11 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Image Width="16"
|
||||
Margin="2"
|
||||
Margin="0,0,2,0"
|
||||
Source="{Binding Path=State, Mode=OneWay, Converter={StaticResource TestStateConverter}}"
|
||||
Grid.Column="0" />
|
||||
<TextBlock Text="{Binding DisplayName}"
|
||||
VerticalAlignment="Center"
|
||||
Grid.Column="1" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
@@ -220,8 +276,45 @@
|
||||
BorderThickness="1"
|
||||
Margin="3" />
|
||||
<StatusBar>
|
||||
<StatusBarItem>
|
||||
<Label />
|
||||
<StatusBar.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="16"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
</Grid>
|
||||
</ItemsPanelTemplate>
|
||||
</StatusBar.ItemsPanel>
|
||||
|
||||
<StatusBarItem Grid.Column="1">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Tests passed: "
|
||||
Foreground="DarkGreen"/>
|
||||
|
||||
<TextBlock Text="{Binding TestsPassed, StringFormat={}{0:#\,0}}"/>
|
||||
</StackPanel>
|
||||
</StatusBarItem>
|
||||
|
||||
<StatusBarItem Grid.Column="2">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Tests failed: "
|
||||
Foreground="DarkRed"/>
|
||||
|
||||
<TextBlock Text="{Binding TestsFailed, StringFormat={}{0:#\,0}}"/>
|
||||
</StackPanel>
|
||||
</StatusBarItem>
|
||||
|
||||
<StatusBarItem Grid.Column="3">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Tests skipped: "
|
||||
Foreground="DarkGoldenrod"/>
|
||||
|
||||
<TextBlock Text="{Binding TestsSkipped, StringFormat={}{0:#\,0}}"/>
|
||||
</StackPanel>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</StackPanel>
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace xunit.runner.wpf.Properties
|
||||
{
|
||||
|
||||
|
||||
namespace xunit.runner.wpf.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
@@ -22,48 +22,40 @@ namespace xunit.runner.wpf.Properties
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources
|
||||
{
|
||||
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources()
|
||||
{
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager
|
||||
{
|
||||
get
|
||||
{
|
||||
if ((resourceMan == null))
|
||||
{
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("xunit.runner.wpf.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture
|
||||
{
|
||||
get
|
||||
{
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set
|
||||
{
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,21 +8,17 @@
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace xunit.runner.wpf.Properties
|
||||
{
|
||||
|
||||
|
||||
namespace xunit.runner.wpf.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
|
||||
{
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,6 @@ using System.Collections.Specialized;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Pipes;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using xunit.runner.data;
|
||||
using System.Windows.Threading;
|
||||
|
||||
@@ -24,6 +21,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;
|
||||
@@ -39,7 +37,7 @@ namespace xunit.runner.wpf.ViewModel
|
||||
|
||||
CommandBindings = CreateCommandBindings();
|
||||
this.testUtil = new xunit.runner.wpf.Impl.RemoteTestUtil(Dispatcher.CurrentDispatcher);
|
||||
this.MethodsCaption = "Methods (0)";
|
||||
this.TestCasesCaption = "Test Cases (0)";
|
||||
|
||||
TestCases = new FilteredCollectionView<TestCaseViewModel, SearchQuery>(
|
||||
allTestCases, TestCaseMatches, searchQuery, TestComparer.Instance);
|
||||
@@ -48,6 +46,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 +61,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:
|
||||
@@ -79,7 +101,7 @@ namespace xunit.runner.wpf.ViewModel
|
||||
|
||||
private void TestCases_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
MethodsCaption = $"Methods ({TestCases.Count})";
|
||||
TestCasesCaption = $"Test Cases ({TestCases.Count:#,0})";
|
||||
MaximumProgress = TestCases.Count;
|
||||
}
|
||||
|
||||
@@ -87,14 +109,25 @@ 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; }
|
||||
|
||||
private string methodsCaption;
|
||||
public string MethodsCaption
|
||||
public List<TestAssemblyViewModel> SelectedAssemblies
|
||||
{
|
||||
get { return methodsCaption; }
|
||||
private set { Set(ref methodsCaption, value); }
|
||||
get { return Assemblies.Where(x => x.IsSelected).ToList(); }
|
||||
}
|
||||
|
||||
private string testCasesCaption;
|
||||
public string TestCasesCaption
|
||||
{
|
||||
get { return testCasesCaption; }
|
||||
private set { Set(ref testCasesCaption, value); }
|
||||
}
|
||||
|
||||
private int testsCompleted = 0;
|
||||
@@ -189,12 +222,14 @@ 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)
|
||||
{
|
||||
var fileDialog = new OpenFileDialog
|
||||
{
|
||||
Filter = "Unit Test Assemblies|*.dll",
|
||||
Multiselect = true
|
||||
};
|
||||
|
||||
if (fileDialog.ShowDialog(Application.Current.MainWindow) != true)
|
||||
@@ -202,8 +237,8 @@ namespace xunit.runner.wpf.ViewModel
|
||||
return;
|
||||
}
|
||||
|
||||
var fileName = fileDialog.FileName;
|
||||
await AddAssemblies(new[] { new AssemblyAndConfigFile(fileName, configFileName: null) });
|
||||
var assemblies = fileDialog.FileNames.Select(x => new AssemblyAndConfigFile(x, configFileName: null));
|
||||
await AddAssemblies(assemblies);
|
||||
}
|
||||
|
||||
private async Task AddAssemblies(IEnumerable<AssemblyAndConfigFile> assemblies)
|
||||
@@ -218,18 +253,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 +270,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 +390,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 +407,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 +423,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 +442,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 +457,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 +503,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; }
|
||||
|
||||
@@ -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,7 +3,15 @@
|
||||
<package id="CommonServiceLocator" version="1.3" targetFramework="net452" />
|
||||
<package id="MvvmLight" version="5.1.1.0" targetFramework="net452" />
|
||||
<package id="MvvmLightLibs" version="5.1.1.0" targetFramework="net452" />
|
||||
<package id="System.Collections" version="4.0.0" targetFramework="net46" />
|
||||
<package id="System.Collections.Immutable" version="1.1.37" targetFramework="net452" />
|
||||
<package id="System.Diagnostics.Debug" version="4.0.0" targetFramework="net46" />
|
||||
<package id="System.Globalization" version="4.0.0" 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.0" targetFramework="net46" />
|
||||
<package id="System.Runtime.Extensions" version="4.0.0" targetFramework="net46" />
|
||||
<package id="System.Threading" version="4.0.0" targetFramework="net46" />
|
||||
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
|
||||
<package id="xunit.runner.utility" version="2.1.0-beta4-build3109" targetFramework="net452" />
|
||||
<package id="xunit.runner.utility" version="2.1.0" targetFramework="net46" />
|
||||
</packages>
|
||||
@@ -9,13 +9,14 @@
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>xunit.runner.wpf</RootNamespace>
|
||||
<AssemblyName>xunit.runner.wpf</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
@@ -75,6 +76,10 @@
|
||||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
<Reference Include="xunit.runner.utility.desktop, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.runner.utility.2.1.0\lib\net35\xunit.runner.utility.desktop.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="App.xaml">
|
||||
@@ -86,19 +91,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>
|
||||
@@ -147,9 +153,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Artwork\xunit-dot-net-small-logo.png" />
|
||||
<Resource Include="Artwork\Passed.ico" />
|
||||
<Resource Include="Artwork\Skipped.ico" />
|
||||
<Resource Include="Artwork\Failed.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SampleTestAssembly\SampleTestAssembly.csproj">
|
||||
@@ -167,7 +170,22 @@
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<WCFMetadata Include="Service References\" />
|
||||
<Resource Include="Artwork\Passed_small.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Artwork\Passed_large.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Artwork\Failed_small.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Artwork\Failed_large.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Artwork\Skipped_small.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Artwork\Skipped_large.png" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
|
||||