Compare commits
129 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 41c9282023 | |||
| b7293a15f7 | |||
| 8e6a0ea916 | |||
| 709b166870 | |||
| 2251487778 | |||
| 5e3aa442b8 | |||
| c28059ca4c | |||
| d93c706a3d | |||
| 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 | |||
| 5de08e12a3 | |||
| c7c33af554 | |||
| fcff5239c9 | |||
| cf72130426 | |||
| c25bc6fe0e | |||
| 9ff7831bff | |||
| 59139518da | |||
| a5b8c5506d | |||
| ada4e79e6d | |||
| ed5a4b83ba | |||
| b7632c7294 | |||
| 3e5e987161 | |||
| b77a0cc857 | |||
| 7b292eee4d | |||
| 50eb75cff6 | |||
| 4bc641e575 | |||
| ba48e2cb24 | |||
| cdf37c40ad | |||
| fece9168ca | |||
| d4122f3a0c | |||
| 4f1e23d036 | |||
| dbbb6b3fad | |||
| 67184a4069 | |||
| 89d919dceb | |||
| f1879e1a06 | |||
| 8ceecdcf03 | |||
| d9667f4da0 | |||
| a2636ff762 | |||
| d5d832535d | |||
| db0ca68d65 | |||
| 135eccb795 | |||
| bb5668e555 | |||
| 6a8f13dda2 | |||
| 1b6ef5fd9a | |||
| 6f0ae76ad4 | |||
| 96428a7a96 | |||
| 1a347531d9 | |||
| cd64e95e73 | |||
| e7cb648b28 | |||
| 8ff92c76d3 | |||
| 58e14e7b3d | |||
| 3128c8d5b5 | |||
| 0137b1a60b | |||
| 5d1a11eec7 | |||
| 836a4f59d6 | |||
| cf33c99ea3 | |||
| 045fec126a | |||
| e9735dfb2b | |||
| 77ab0cbffe | |||
| bb1a2206b4 | |||
| b232c55813 | |||
| 95dc906281 | |||
| 984beba184 | |||
| 8b0fdadc3f | |||
| 0cc8f3aba8 | |||
| fd72605d9d | |||
| cb39c7af29 | |||
| 6aa5a6c0cd | |||
| 90643fbf8f | |||
| 0d96b29a7a | |||
| d861f4a6ee | |||
| 3f894e0e7c | |||
| 4371a4e837 | |||
| 65adde358c | |||
| ae5e46d772 | |||
| 9934a70f52 | |||
| 13afb6eea5 | |||
| e3a17c5308 | |||
| 89be98bebc | |||
| 9e5ac70234 | |||
| b35da545d6 | |||
| 3a7d01b87e | |||
| 0e24227de0 | |||
| 65f3fc970e | |||
| 06f1c8c703 | |||
| 29aa127230 | |||
| 0e20339f7b |
@@ -214,3 +214,4 @@ FakesAssemblies/
|
|||||||
**/*.Server/GeneratedArtifacts
|
**/*.Server/GeneratedArtifacts
|
||||||
**/*.Server/ModelManifest.xml
|
**/*.Server/ModelManifest.xml
|
||||||
_Pvt_Extensions
|
_Pvt_Extensions
|
||||||
|
/Settings.XamlStyle
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# xunit.runner.wpf
|
||||||
|
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)
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
using System.Reflection;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
// General Information about an assembly is controlled through the following
|
|
||||||
// set of attributes. Change these attribute values to modify the information
|
|
||||||
// associated with an assembly.
|
|
||||||
[assembly: AssemblyTitle("SampleTestAssembly")]
|
|
||||||
[assembly: AssemblyDescription("")]
|
|
||||||
[assembly: AssemblyConfiguration("")]
|
|
||||||
[assembly: AssemblyCompany("")]
|
|
||||||
[assembly: AssemblyProduct("SampleTestAssembly")]
|
|
||||||
[assembly: AssemblyCopyright("Copyright © 2015")]
|
|
||||||
[assembly: AssemblyTrademark("")]
|
|
||||||
[assembly: AssemblyCulture("")]
|
|
||||||
|
|
||||||
// Setting ComVisible to false makes the types in this assembly not visible
|
|
||||||
// to COM components. If you need to access a type in this assembly from
|
|
||||||
// COM, set the ComVisible attribute to true on that type.
|
|
||||||
[assembly: ComVisible(false)]
|
|
||||||
|
|
||||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
|
||||||
[assembly: Guid("bdafb5dd-ffb3-4a94-a312-dfb080010846")]
|
|
||||||
|
|
||||||
// Version information for an assembly consists of the following four values:
|
|
||||||
//
|
|
||||||
// Major Version
|
|
||||||
// Minor Version
|
|
||||||
// Build Number
|
|
||||||
// Revision
|
|
||||||
//
|
|
||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
|
||||||
// by using the '*' as shown below:
|
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
|
||||||
[assembly: AssemblyVersion("1.0.0.0")]
|
|
||||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
||||||
@@ -1,78 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<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>
|
<PropertyGroup>
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
|
||||||
<ProjectGuid>{BDAFB5DD-FFB3-4A94-A312-DFB080010846}</ProjectGuid>
|
|
||||||
<OutputType>Library</OutputType>
|
|
||||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
|
||||||
<RootNamespace>SampleTestAssembly</RootNamespace>
|
|
||||||
<AssemblyName>SampleTestAssembly</AssemblyName>
|
|
||||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
|
||||||
<FileAlignment>512</FileAlignment>
|
|
||||||
<NuGetPackageImportStamp>
|
|
||||||
</NuGetPackageImportStamp>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
|
||||||
<DebugSymbols>true</DebugSymbols>
|
|
||||||
<DebugType>full</DebugType>
|
|
||||||
<Optimize>false</Optimize>
|
|
||||||
<OutputPath>bin\Debug\</OutputPath>
|
|
||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
|
||||||
<DebugType>pdbonly</DebugType>
|
|
||||||
<Optimize>true</Optimize>
|
|
||||||
<OutputPath>bin\Release\</OutputPath>
|
|
||||||
<DefineConstants>TRACE</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="System" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<Reference Include="System.Core" />
|
|
||||||
<Reference Include="System.Xml.Linq" />
|
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
|
||||||
<Reference Include="Microsoft.CSharp" />
|
|
||||||
<Reference Include="System.Data" />
|
|
||||||
<Reference Include="System.Net.Http" />
|
|
||||||
<Reference Include="System.Xml" />
|
|
||||||
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
<Private>True</Private>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="Class1.cs" />
|
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<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">
|
|
||||||
</Target>
|
|
||||||
<Target Name="AfterBuild">
|
|
||||||
</Target>
|
|
||||||
-->
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<packages>
|
|
||||||
<package id="xunit" version="2.1.0-beta4-build3109" 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" />
|
|
||||||
</packages>
|
|
||||||
@@ -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
|
||||||
|
After Width: | Height: | Size: 127 KiB |
@@ -1,11 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace xunit.runner.data
|
namespace Xunit.Runner.Data
|
||||||
{
|
{
|
||||||
public sealed class ClientReader : IDisposable
|
public sealed class ClientReader : IDisposable
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace xunit.runner.data
|
namespace Xunit.Runner.Data
|
||||||
{
|
{
|
||||||
public sealed class ClientWriter : IDisposable
|
public sealed class ClientWriter : IDisposable
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
using System;
|
using System.Text;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace xunit.runner.data
|
namespace Xunit.Runner.Data
|
||||||
{
|
{
|
||||||
public static class Constants
|
public static class Constants
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
using System.Reflection;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
// General Information about an assembly is controlled through the following
|
|
||||||
// set of attributes. Change these attribute values to modify the information
|
|
||||||
// associated with an assembly.
|
|
||||||
[assembly: AssemblyTitle("xunit.runner.data")]
|
|
||||||
[assembly: AssemblyDescription("")]
|
|
||||||
[assembly: AssemblyConfiguration("")]
|
|
||||||
[assembly: AssemblyCompany("")]
|
|
||||||
[assembly: AssemblyProduct("xunit.runner.data")]
|
|
||||||
[assembly: AssemblyCopyright("Copyright © 2015")]
|
|
||||||
[assembly: AssemblyTrademark("")]
|
|
||||||
[assembly: AssemblyCulture("")]
|
|
||||||
|
|
||||||
// Setting ComVisible to false makes the types in this assembly not visible
|
|
||||||
// to COM components. If you need to access a type in this assembly from
|
|
||||||
// COM, set the ComVisible attribute to true on that type.
|
|
||||||
[assembly: ComVisible(false)]
|
|
||||||
|
|
||||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
|
||||||
[assembly: Guid("a1f579f4-443e-4f64-bc55-998ab86ff293")]
|
|
||||||
|
|
||||||
// Version information for an assembly consists of the following four values:
|
|
||||||
//
|
|
||||||
// Major Version
|
|
||||||
// Minor Version
|
|
||||||
// Build Number
|
|
||||||
// Revision
|
|
||||||
//
|
|
||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
|
||||||
// by using the '*' as shown below:
|
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
|
||||||
[assembly: AssemblyVersion("1.0.0.0")]
|
|
||||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
||||||
@@ -1,38 +1,69 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace xunit.runner.data
|
namespace Xunit.Runner.Data
|
||||||
{
|
{
|
||||||
public sealed class TestCaseData
|
public sealed class TestCaseData
|
||||||
{
|
{
|
||||||
public string SerializedForm { get; set; }
|
|
||||||
public string DisplayName { get; set; }
|
public string DisplayName { get; set; }
|
||||||
|
public string UniqueID { get; set; }
|
||||||
|
public string SkipReason { get; set; }
|
||||||
public string AssemblyPath { 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 displayName, string uniqueID, string skipReason, string assemblyPath, Dictionary<string, List<string>> traitMap)
|
||||||
{
|
{
|
||||||
SerializedForm = serializedForm;
|
|
||||||
DisplayName = displayName;
|
DisplayName = displayName;
|
||||||
|
UniqueID = uniqueID;
|
||||||
|
SkipReason = skipReason;
|
||||||
AssemblyPath = assemblyPath;
|
AssemblyPath = assemblyPath;
|
||||||
|
TraitMap = traitMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TestCaseData ReadFrom(BinaryReader reader)
|
public static TestCaseData ReadFrom(BinaryReader reader)
|
||||||
{
|
{
|
||||||
var serializedForm = reader.ReadString();
|
|
||||||
var displayName = reader.ReadString();
|
var displayName = reader.ReadString();
|
||||||
|
var uniqueID = reader.ReadString();
|
||||||
|
var skipReason = reader.ReadString();
|
||||||
var assemblyPath = reader.ReadString();
|
var assemblyPath = reader.ReadString();
|
||||||
return new TestCaseData(serializedForm, displayName, assemblyPath);
|
var count = reader.ReadInt32();
|
||||||
|
var traitMap = new Dictionary<string, List<string>>(count);
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var key = reader.ReadString();
|
||||||
|
var valueCount = reader.ReadInt32();
|
||||||
|
var values = new List<string>(valueCount);
|
||||||
|
|
||||||
|
for (int j = 0; j < valueCount; j++)
|
||||||
|
{
|
||||||
|
values.Add(reader.ReadString());
|
||||||
|
}
|
||||||
|
|
||||||
|
traitMap.Add(key, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TestCaseData(displayName, uniqueID, skipReason, assemblyPath, traitMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteTo(BinaryWriter writer)
|
public void WriteTo(BinaryWriter writer)
|
||||||
{
|
{
|
||||||
writer.Write(SerializedForm);
|
|
||||||
writer.Write(DisplayName);
|
writer.Write(DisplayName);
|
||||||
|
writer.Write(UniqueID);
|
||||||
|
writer.Write(SkipReason ?? string.Empty);
|
||||||
writer.Write(AssemblyPath);
|
writer.Write(AssemblyPath);
|
||||||
|
writer.Write(TraitMap.Count);
|
||||||
|
|
||||||
|
foreach (var pair in TraitMap)
|
||||||
|
{
|
||||||
|
writer.Write(pair.Key);
|
||||||
|
writer.Write(pair.Value.Count);
|
||||||
|
|
||||||
|
foreach (var value in pair.Value)
|
||||||
|
{
|
||||||
|
writer.Write(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
using System;
|
namespace Xunit.Runner.Data
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace xunit.runner.data
|
|
||||||
{
|
{
|
||||||
public enum TestDataKind
|
public enum TestDataKind
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
using System;
|
using System.IO;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace xunit.runner.data
|
namespace Xunit.Runner.Data
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Note: More severe states are higher numbers.
|
/// Note: More severe states are higher numbers.
|
||||||
@@ -15,6 +10,7 @@ namespace xunit.runner.data
|
|||||||
{
|
{
|
||||||
All = 0,
|
All = 0,
|
||||||
NotRun,
|
NotRun,
|
||||||
|
Running,
|
||||||
Passed,
|
Passed,
|
||||||
Skipped,
|
Skipped,
|
||||||
Failed,
|
Failed,
|
||||||
@@ -23,12 +19,14 @@ namespace xunit.runner.data
|
|||||||
public sealed class TestResultData
|
public sealed class TestResultData
|
||||||
{
|
{
|
||||||
public string TestCaseDisplayName { get; set; }
|
public string TestCaseDisplayName { get; set; }
|
||||||
|
public string TestCaseUniqueID { get; set; }
|
||||||
public TestState TestState { get; set; }
|
public TestState TestState { get; set; }
|
||||||
public string Output { 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;
|
TestCaseDisplayName = displayName;
|
||||||
|
TestCaseUniqueID = uniqueID;
|
||||||
TestState = state;
|
TestState = state;
|
||||||
Output = output;
|
Output = output;
|
||||||
}
|
}
|
||||||
@@ -36,14 +34,16 @@ namespace xunit.runner.data
|
|||||||
public static TestResultData ReadFrom(BinaryReader reader)
|
public static TestResultData ReadFrom(BinaryReader reader)
|
||||||
{
|
{
|
||||||
var displayName = reader.ReadString();
|
var displayName = reader.ReadString();
|
||||||
|
var uniqueID = reader.ReadString();
|
||||||
var state = (TestState)reader.ReadInt32();
|
var state = (TestState)reader.ReadInt32();
|
||||||
var output = reader.ReadString();
|
var output = reader.ReadString();
|
||||||
return new TestResultData(displayName, state, output);
|
return new TestResultData(displayName, uniqueID, state, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteTo(BinaryWriter writer)
|
public void WriteTo(BinaryWriter writer)
|
||||||
{
|
{
|
||||||
writer.Write(TestCaseDisplayName);
|
writer.Write(TestCaseDisplayName);
|
||||||
|
writer.Write(TestCaseUniqueID);
|
||||||
writer.Write((int)TestState);
|
writer.Write((int)TestState);
|
||||||
writer.Write(Output);
|
writer.Write(Output);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,59 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
<RootNamespace>Xunit.Runner.Data</RootNamespace>
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<ProjectGuid>{A1F579F4-443E-4F64-BC55-998AB86FF293}</ProjectGuid>
|
|
||||||
<OutputType>Library</OutputType>
|
|
||||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
|
||||||
<RootNamespace>xunit.runner.data</RootNamespace>
|
|
||||||
<AssemblyName>xunit.runner.data</AssemblyName>
|
|
||||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
|
||||||
<FileAlignment>512</FileAlignment>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
|
||||||
<DebugSymbols>true</DebugSymbols>
|
|
||||||
<DebugType>full</DebugType>
|
|
||||||
<Optimize>false</Optimize>
|
|
||||||
<OutputPath>bin\Debug\</OutputPath>
|
|
||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
|
||||||
<DebugType>pdbonly</DebugType>
|
|
||||||
<Optimize>true</Optimize>
|
|
||||||
<OutputPath>bin\Release\</OutputPath>
|
|
||||||
<DefineConstants>TRACE</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="System" />
|
|
||||||
<Reference Include="System.Core" />
|
|
||||||
<Reference Include="System.Xml.Linq" />
|
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
|
||||||
<Reference Include="Microsoft.CSharp" />
|
|
||||||
<Reference Include="System.Data" />
|
|
||||||
<Reference Include="System.Net.Http" />
|
|
||||||
<Reference Include="System.Xml" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="ClientReader.cs" />
|
|
||||||
<Compile Include="ClientWriter.cs" />
|
|
||||||
<Compile Include="Constants.cs" />
|
|
||||||
<Compile Include="TestCaseData.cs" />
|
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
|
||||||
<Compile Include="TestDataKind.cs" />
|
|
||||||
<Compile Include="TestResultData.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
|
||||||
<!-- 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">
|
|
||||||
</Target>
|
|
||||||
<Target Name="AfterBuild">
|
|
||||||
</Target>
|
|
||||||
-->
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<startup>
|
<startup>
|
||||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
|
||||||
</startup>
|
</startup>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
#if NETFRAMEWORK
|
||||||
|
|
||||||
|
// Taken from https://github.com/xunit/xunit/blob/master/src/common/AssemblyResolution/AssemblyHelper_Desktop.cs
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
|
namespace Xunit
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class provides assistance with assembly resolution for missing assemblies.
|
||||||
|
/// </summary>
|
||||||
|
class AssemblyHelper : LongLivedMarshalByRefObject, IDisposable
|
||||||
|
{
|
||||||
|
static readonly string[] Extensions = { ".dll", ".exe" };
|
||||||
|
|
||||||
|
readonly string directory;
|
||||||
|
readonly IMessageSink internalDiagnosticsMessageSink;
|
||||||
|
readonly Dictionary<string, Assembly> lookupCache = new Dictionary<string, Assembly>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs an instance using the given <paramref name="directory"/> for resolution.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="directory">The directory to use for resolving assemblies.</param>
|
||||||
|
public AssemblyHelper(string directory) : this(directory, null) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs an instance using the given <paramref name="directory"/> for resolution.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="directory">The directory to use for resolving assemblies.</param>
|
||||||
|
/// <param name="internalDiagnosticsMessageSink">The message sink to send internal diagnostics messages to</param>
|
||||||
|
public AssemblyHelper(string directory, IMessageSink internalDiagnosticsMessageSink)
|
||||||
|
{
|
||||||
|
this.directory = directory;
|
||||||
|
this.internalDiagnosticsMessageSink = internalDiagnosticsMessageSink;
|
||||||
|
|
||||||
|
AppDomain.CurrentDomain.AssemblyResolve += Resolve;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
=> AppDomain.CurrentDomain.AssemblyResolve -= Resolve;
|
||||||
|
|
||||||
|
Assembly LoadAssembly(AssemblyName assemblyName)
|
||||||
|
{
|
||||||
|
if (lookupCache.TryGetValue(assemblyName.Name, out var result))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
var path = Path.Combine(directory, assemblyName.Name);
|
||||||
|
result = ResolveAndLoadAssembly(path, out var resolvedAssemblyPath);
|
||||||
|
|
||||||
|
if (internalDiagnosticsMessageSink != null)
|
||||||
|
{
|
||||||
|
if (result == null)
|
||||||
|
internalDiagnosticsMessageSink.OnMessage(new DiagnosticMessage($"[AssemblyHelper_Desktop.LoadAssembly] Resolution for '{assemblyName.Name}' failed, passed down to next resolver"));
|
||||||
|
else
|
||||||
|
internalDiagnosticsMessageSink.OnMessage(new DiagnosticMessage($"[AssemblyHelper_Desktop.LoadAssembly] Resolved '{assemblyName.Name}' to '{resolvedAssemblyPath}'"));
|
||||||
|
}
|
||||||
|
|
||||||
|
lookupCache[assemblyName.Name] = result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assembly Resolve(object sender, ResolveEventArgs args)
|
||||||
|
=> LoadAssembly(new AssemblyName(args.Name));
|
||||||
|
|
||||||
|
Assembly ResolveAndLoadAssembly(string pathWithoutExtension, out string resolvedAssemblyPath)
|
||||||
|
{
|
||||||
|
foreach (var extension in Extensions)
|
||||||
|
{
|
||||||
|
resolvedAssemblyPath = pathWithoutExtension + extension;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(resolvedAssemblyPath))
|
||||||
|
return Assembly.LoadFrom(resolvedAssemblyPath);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvedAssemblyPath = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribes to the appropriate assembly resolution event, to provide automatic assembly resolution for
|
||||||
|
/// an assembly and any of its dependencies. Depending on the target platform, this may include the use
|
||||||
|
/// of the .deps.json file generated during the build process.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An object which, when disposed, un-subscribes.</returns>
|
||||||
|
public static IDisposable SubscribeResolveForAssembly(string assemblyFileName, IMessageSink internalDiagnosticsMessageSink = null)
|
||||||
|
=> new AssemblyHelper(Path.GetDirectoryName(Path.GetFullPath(assemblyFileName)), internalDiagnosticsMessageSink);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribes to the appropriate assembly resolution event, to provide automatic assembly resolution for
|
||||||
|
/// an assembly and any of its dependencies. Depending on the target platform, this may include the use
|
||||||
|
/// of the .deps.json file generated during the build process.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An object which, when disposed, un-subscribes.</returns>
|
||||||
|
public static IDisposable SubscribeResolveForAssembly(Type typeInAssembly, IMessageSink internalDiagnosticsMessageSink = null)
|
||||||
|
=> new AssemblyHelper(Path.GetDirectoryName(typeInAssembly.Assembly.Location), internalDiagnosticsMessageSink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,12 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Pipes;
|
using System.IO.Pipes;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace xunit.runner.worker
|
namespace Xunit.Runner.Worker
|
||||||
{
|
{
|
||||||
internal abstract class Connection : IDisposable
|
internal abstract class Connection : IDisposable
|
||||||
{
|
{
|
||||||
@@ -56,7 +52,7 @@ namespace xunit.runner.worker
|
|||||||
|
|
||||||
protected override void DisposeCore()
|
protected override void DisposeCore()
|
||||||
{
|
{
|
||||||
_stream.Close();
|
_stream.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void WaitForClientConnect()
|
internal override void WaitForClientConnect()
|
||||||
|
|||||||
@@ -1,59 +1,53 @@
|
|||||||
using System;
|
using System.IO;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Xunit;
|
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
using xunit.runner.data;
|
using Xunit.Runner.Data;
|
||||||
|
using Xunit.Runner.Worker.MessageSinks;
|
||||||
|
|
||||||
namespace xunit.runner.worker
|
namespace Xunit.Runner.Worker
|
||||||
{
|
{
|
||||||
internal sealed class DiscoverUtil
|
internal sealed class DiscoverUtil : XunitUtil
|
||||||
{
|
{
|
||||||
private sealed class Impl : TestMessageVisitor<IDiscoveryCompleteMessage>
|
private sealed class TestDiscoverySink : BaseTestDiscoverySink
|
||||||
{
|
{
|
||||||
private readonly ITestFrameworkDiscoverer _discoverer;
|
|
||||||
private readonly ClientWriter _writer;
|
private readonly ClientWriter _writer;
|
||||||
|
|
||||||
internal Impl(ITestFrameworkDiscoverer discoverer, ClientWriter writer)
|
internal TestDiscoverySink(ClientWriter writer)
|
||||||
{
|
{
|
||||||
_discoverer = discoverer;
|
|
||||||
_writer = writer;
|
_writer = writer;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool Visit(ITestCaseDiscoveryMessage testCaseDiscovered)
|
protected override bool ShouldContinue => _writer.IsConnected;
|
||||||
|
|
||||||
|
protected override void OnTestDiscovered(ITestCaseDiscoveryMessage testCaseDiscovered)
|
||||||
{
|
{
|
||||||
var testCase = testCaseDiscovered.TestCase;
|
var testCase = testCaseDiscovered.TestCase;
|
||||||
var testCaseData = new TestCaseData(
|
var testCaseData = new TestCaseData(
|
||||||
_discoverer.Serialize(testCase),
|
|
||||||
testCase.DisplayName,
|
testCase.DisplayName,
|
||||||
testCaseDiscovered.TestAssembly.Assembly.AssemblyPath);
|
testCase.UniqueID,
|
||||||
|
testCase.SkipReason,
|
||||||
|
testCaseDiscovered.TestAssembly.Assembly.AssemblyPath,
|
||||||
|
testCase.Traits);
|
||||||
|
|
||||||
Console.WriteLine(testCase.DisplayName);
|
|
||||||
_writer.Write(TestDataKind.Value);
|
_writer.Write(TestDataKind.Value);
|
||||||
_writer.Write(testCaseData);
|
_writer.Write(testCaseData);
|
||||||
|
|
||||||
return _writer.IsConnected;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void Go(string fileName, Stream stream)
|
internal static void Go(string assemblyFileName, Stream stream)
|
||||||
{
|
{
|
||||||
using (AssemblyHelper.SubscribeResolve())
|
Go(assemblyFileName, stream, AppDomainSupport.IfAvailable,
|
||||||
using (var xunit = new XunitFrontController(
|
(xunit, configuration, writer) =>
|
||||||
useAppDomain: true,
|
{
|
||||||
assemblyFileName: fileName,
|
using (var sink = new TestDiscoverySink(writer))
|
||||||
diagnosticMessageSink: new MessageVisitor(),
|
{
|
||||||
shadowCopy: false))
|
xunit.Find(includeSourceInformation: false, messageSink: sink,
|
||||||
using (var writer = new ClientWriter(stream))
|
discoveryOptions: TestFrameworkOptions.ForDiscovery(configuration));
|
||||||
using (var impl = new Impl(xunit, writer))
|
|
||||||
{
|
sink.Finished.WaitOne();
|
||||||
xunit.Find(includeSourceInformation: false, messageSink: impl, discoveryOptions: TestFrameworkOptions.ForDiscovery());
|
|
||||||
impl.Finished.WaitOne();
|
writer.Write(TestDataKind.EndOfData);
|
||||||
writer.Write(TestDataKind.EndOfData);
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
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: -1);
|
||||||
|
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 assemblyFileName = reader.ReadString();
|
||||||
|
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case Constants.ActionDiscover:
|
||||||
|
Discover(stream, assemblyFileName);
|
||||||
|
break;
|
||||||
|
case Constants.ActionRunAll:
|
||||||
|
RunAll(stream, assemblyFileName);
|
||||||
|
break;
|
||||||
|
case Constants.ActionRunSpecific:
|
||||||
|
RunSpecific(stream, assemblyFileName);
|
||||||
|
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 assemblyFileName)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"discover started: {assemblyFileName}");
|
||||||
|
DiscoverUtil.Go(assemblyFileName, stream);
|
||||||
|
Console.WriteLine("discover ended");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RunAll(Stream stream, string assemblyFileName)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"run all started: {assemblyFileName}");
|
||||||
|
RunUtil.RunAll(assemblyFileName, stream);
|
||||||
|
Console.WriteLine("run all ended");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RunSpecific(Stream stream, string assemblyFileName)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"run specific started: {assemblyFileName}");
|
||||||
|
RunUtil.RunSpecific(assemblyFileName, stream);
|
||||||
|
Console.WriteLine("run specific ended");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
|
namespace Xunit.Runner.Worker.MessageSinks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An Xunit <see cref="IMessageSink"/> implementation without the dispatch overhead of <see cref="TestMessageVisitor"/>
|
||||||
|
/// and <see cref="TestMessageVisitor{TCompleteMessage}"/>.
|
||||||
|
/// </summary>
|
||||||
|
internal abstract class BaseMessageSink : LongLivedMarshalByRefObject, IMessageSink, IDisposable
|
||||||
|
{
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
protected BaseMessageSink()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~BaseMessageSink()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void DisposeCore(bool disposing)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposeCore(disposing);
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract bool OnMessage(IMessageSinkMessage message);
|
||||||
|
|
||||||
|
bool IMessageSink.OnMessage(IMessageSinkMessage message)
|
||||||
|
{
|
||||||
|
return OnMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
namespace Xunit.Runner.Worker.MessageSinks
|
||||||
|
{
|
||||||
|
internal abstract class BaseTestDiscoverySink : BaseMessageSink
|
||||||
|
{
|
||||||
|
public ManualResetEvent Finished { get; }
|
||||||
|
|
||||||
|
protected BaseTestDiscoverySink()
|
||||||
|
{
|
||||||
|
Finished = new ManualResetEvent(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DisposeCore(bool disposing)
|
||||||
|
{
|
||||||
|
Finished.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMessage(IMessageSinkMessage message)
|
||||||
|
{
|
||||||
|
var discoveryMessage = message as ITestCaseDiscoveryMessage;
|
||||||
|
if (discoveryMessage != null)
|
||||||
|
{
|
||||||
|
OnTestDiscovered(discoveryMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message is IDiscoveryCompleteMessage)
|
||||||
|
{
|
||||||
|
Finished.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ShouldContinue;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual bool ShouldContinue => true;
|
||||||
|
|
||||||
|
protected abstract void OnTestDiscovered(ITestCaseDiscoveryMessage testCaseDiscovered);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
namespace Xunit.Runner.Worker.MessageSinks
|
||||||
|
{
|
||||||
|
internal abstract class BaseTestRunSink : BaseMessageSink
|
||||||
|
{
|
||||||
|
public ManualResetEvent Finished { get; }
|
||||||
|
|
||||||
|
protected BaseTestRunSink()
|
||||||
|
{
|
||||||
|
Finished = new ManualResetEvent(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DisposeCore(bool disposing)
|
||||||
|
{
|
||||||
|
Finished.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMessage(IMessageSinkMessage message)
|
||||||
|
{
|
||||||
|
var testStarted = message as ITestStarting;
|
||||||
|
if (testStarted != null)
|
||||||
|
{
|
||||||
|
OnTestStarted(testStarted);
|
||||||
|
}
|
||||||
|
|
||||||
|
var testFailed = message as ITestFailed;
|
||||||
|
if (testFailed != null)
|
||||||
|
{
|
||||||
|
OnTestFailed(testFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
var testPassed = message as ITestPassed;
|
||||||
|
if (testPassed != null)
|
||||||
|
{
|
||||||
|
OnTestPassed(testPassed);
|
||||||
|
}
|
||||||
|
|
||||||
|
var testSkipped = message as ITestSkipped;
|
||||||
|
if (testSkipped != null)
|
||||||
|
{
|
||||||
|
OnTestSkipped(testSkipped);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message is ITestAssemblyFinished)
|
||||||
|
{
|
||||||
|
Finished.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ShouldContinue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
namespace Xunit.Runner.Worker.MessageSinks
|
||||||
|
{
|
||||||
|
internal class DiagnosticSink : BaseMessageSink
|
||||||
|
{
|
||||||
|
protected override bool OnMessage(IMessageSinkMessage message)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Xunit;
|
|
||||||
using Xunit.Abstractions;
|
|
||||||
|
|
||||||
namespace xunit.runner.worker
|
|
||||||
{
|
|
||||||
internal sealed class MessageVisitor : TestMessageVisitor
|
|
||||||
{
|
|
||||||
public override bool OnMessage(IMessageSinkMessage message)
|
|
||||||
{
|
|
||||||
return base.OnMessage(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,8 @@
|
|||||||
using System;
|
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 System.Threading.Tasks;
|
||||||
using xunit.runner.data;
|
|
||||||
|
|
||||||
namespace xunit.runner.worker
|
namespace Xunit.Runner.Worker
|
||||||
{
|
{
|
||||||
public static class Program
|
public static class Program
|
||||||
{
|
{
|
||||||
@@ -16,91 +11,34 @@ namespace xunit.runner.worker
|
|||||||
|
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
{
|
{
|
||||||
if (args.Length < 3)
|
if (args.Length < 2)
|
||||||
{
|
{
|
||||||
Usage();
|
Usage();
|
||||||
return ExitError;
|
return ExitError;
|
||||||
}
|
}
|
||||||
|
|
||||||
string pipeName = args[0];
|
var pipeName = args[0];
|
||||||
string action = args[1];
|
var parentPid = Int32.Parse(args[1]);
|
||||||
string argument = args[2];
|
var process = Process.GetProcessById(parentPid);
|
||||||
|
if (process == null)
|
||||||
try
|
|
||||||
{
|
{
|
||||||
using (var connection = CreateConnection(pipeName))
|
Console.WriteLine($"Invalid parent pid {parentPid}");
|
||||||
{
|
|
||||||
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);
|
|
||||||
return ExitError;
|
return ExitError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Task.WaitAny(
|
||||||
|
Task.Run(() => process.WaitForExit()),
|
||||||
|
Task.Run(() => new Listener(pipeName).Go()));
|
||||||
|
|
||||||
return ExitSuccess;
|
return ExitSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Connection CreateConnection(string pipeName)
|
|
||||||
{
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void Usage()
|
private static void Usage()
|
||||||
{
|
{
|
||||||
Console.WriteLine("xunit.runner.worker [pipe name] [action] [assembly path]");
|
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("\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("\taction: Action performed by the worker (run or discover tests)");
|
||||||
Console.WriteLine("\assembly path: Path of assembly to perform the action against");
|
Console.WriteLine("\tassembly path: Path of assembly to perform the action against");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
using System.Reflection;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
// General Information about an assembly is controlled through the following
|
|
||||||
// set of attributes. Change these attribute values to modify the information
|
|
||||||
// associated with an assembly.
|
|
||||||
[assembly: AssemblyTitle("xunit.runner.worker")]
|
|
||||||
[assembly: AssemblyDescription("")]
|
|
||||||
[assembly: AssemblyConfiguration("")]
|
|
||||||
[assembly: AssemblyCompany("")]
|
|
||||||
[assembly: AssemblyProduct("xunit.runner.worker")]
|
|
||||||
[assembly: AssemblyCopyright("Copyright © 2015")]
|
|
||||||
[assembly: AssemblyTrademark("")]
|
|
||||||
[assembly: AssemblyCulture("")]
|
|
||||||
|
|
||||||
// Setting ComVisible to false makes the types in this assembly not visible
|
|
||||||
// to COM components. If you need to access a type in this assembly from
|
|
||||||
// COM, set the ComVisible attribute to true on that type.
|
|
||||||
[assembly: ComVisible(false)]
|
|
||||||
|
|
||||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
|
||||||
[assembly: Guid("9df97a2b-0eb5-4b12-9f81-69dfac979814")]
|
|
||||||
|
|
||||||
// Version information for an assembly consists of the following four values:
|
|
||||||
//
|
|
||||||
// Major Version
|
|
||||||
// Minor Version
|
|
||||||
// Build Number
|
|
||||||
// Revision
|
|
||||||
//
|
|
||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
|
||||||
// by using the '*' as shown below:
|
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
|
||||||
[assembly: AssemblyVersion("1.0.0.0")]
|
|
||||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
||||||
@@ -1,36 +1,41 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using Xunit.Runner.Data;
|
||||||
using xunit.runner.data;
|
|
||||||
using Xunit;
|
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
using Xunit.Runner.Worker.MessageSinks;
|
||||||
|
|
||||||
namespace xunit.runner.worker
|
namespace Xunit.Runner.Worker
|
||||||
{
|
{
|
||||||
internal sealed class RunUtil
|
internal sealed class RunUtil : XunitUtil
|
||||||
{
|
{
|
||||||
private sealed class TestRunVisitor : TestMessageVisitor<ITestAssemblyFinished>
|
private sealed class TestRunSink : BaseTestRunSink
|
||||||
{
|
{
|
||||||
private readonly ClientWriter _writer;
|
private readonly ClientWriter _writer;
|
||||||
|
|
||||||
public TestRunVisitor(ClientWriter writer)
|
public TestRunSink(ClientWriter writer)
|
||||||
{
|
{
|
||||||
_writer = writer;
|
_writer = writer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Process(string displayName, TestState state, string output = "")
|
protected override bool ShouldContinue => _writer.IsConnected;
|
||||||
|
|
||||||
|
private void Process(string displayName, string uniqueID, TestState state, string output = "")
|
||||||
{
|
{
|
||||||
Console.WriteLine($"{state} - {displayName}");
|
System.Diagnostics.Trace.WriteLine($"{state} - {displayName}");
|
||||||
var result = new TestResultData(displayName, state, output);
|
var result = new TestResultData(displayName, uniqueID, state, output);
|
||||||
|
|
||||||
_writer.Write(TestDataKind.Value);
|
_writer.Write(TestDataKind.Value);
|
||||||
_writer.Write(result);
|
_writer.Write(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool Visit(ITestFailed testFailed)
|
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;
|
var displayName = testFailed.TestCase.DisplayName;
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
@@ -43,53 +48,48 @@ namespace xunit.runner.worker
|
|||||||
builder.AppendLine($"\tException stacktrace");
|
builder.AppendLine($"\tException stacktrace");
|
||||||
builder.AppendLine(testFailed.StackTraces[i]);
|
builder.AppendLine(testFailed.StackTraces[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AppendLine();
|
builder.AppendLine();
|
||||||
|
|
||||||
Process(testFailed.TestCase.DisplayName, TestState.Failed, builder.ToString());
|
Process(testFailed.TestCase.DisplayName, testFailed.TestCase.UniqueID, TestState.Failed, builder.ToString());
|
||||||
|
|
||||||
return _writer.IsConnected;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool Visit(ITestPassed testPassed)
|
protected override void OnTestPassed(ITestPassed testPassed)
|
||||||
{
|
{
|
||||||
Process(testPassed.TestCase.DisplayName, TestState.Passed);
|
Process(testPassed.TestCase.DisplayName, testPassed.TestCase.UniqueID, TestState.Passed);
|
||||||
return _writer.IsConnected;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool Visit(ITestSkipped testSkipped)
|
protected override void OnTestSkipped(ITestSkipped testSkipped)
|
||||||
{
|
{
|
||||||
Process(testSkipped.TestCase.DisplayName, TestState.Skipped);
|
Process(testSkipped.TestCase.DisplayName, testSkipped.TestCase.UniqueID, TestState.Skipped);
|
||||||
return _writer.IsConnected;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class TestCaseDiscoverer : TestMessageVisitor<IDiscoveryCompleteMessage>
|
private sealed class TestDiscoverySink : BaseTestDiscoverySink
|
||||||
{
|
{
|
||||||
private readonly HashSet<string> _testCaseDisplayNameSet;
|
private readonly HashSet<string> _testCaseUniqueIDSet;
|
||||||
private readonly List<ITestCase> _testCaseList;
|
private readonly List<ITestCase> _testCaseList;
|
||||||
|
|
||||||
internal TestCaseDiscoverer(HashSet<string> testCaseDisplayNameSet, List<ITestCase> testCaseList)
|
internal TestDiscoverySink(HashSet<string> testCaseUniqueIDSet, List<ITestCase> testCaseList)
|
||||||
{
|
{
|
||||||
_testCaseDisplayNameSet = testCaseDisplayNameSet;
|
_testCaseUniqueIDSet = testCaseUniqueIDSet;
|
||||||
_testCaseList = testCaseList;
|
_testCaseList = testCaseList;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool Visit(ITestCaseDiscoveryMessage testCaseDiscovered)
|
protected override void OnTestDiscovered(ITestCaseDiscoveryMessage testCaseDiscovered)
|
||||||
{
|
{
|
||||||
var testCase = testCaseDiscovered.TestCase;
|
var testCase = testCaseDiscovered.TestCase;
|
||||||
if (_testCaseDisplayNameSet.Contains(testCase.DisplayName))
|
if (_testCaseUniqueIDSet.Contains(testCase.UniqueID))
|
||||||
{
|
{
|
||||||
_testCaseList.Add(testCaseDiscovered.TestCase);
|
_testCaseList.Add(testCaseDiscovered.TestCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Read out the set of test case display names to run.
|
/// Read out the set of test case unique IDs to run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static List<string> ReadTestCaseDisplayNames(Stream stream)
|
private static List<string> ReadTestCaseUniqueIDs(Stream stream)
|
||||||
{
|
{
|
||||||
using (var reader = new ClientReader(stream))
|
using (var reader = new ClientReader(stream))
|
||||||
{
|
{
|
||||||
@@ -103,54 +103,58 @@ namespace xunit.runner.worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<ITestCase> GetTestCaseList(XunitFrontController xunit, Stream stream)
|
private static List<ITestCase> GetTestCaseList(XunitFrontController xunit, TestAssemblyConfiguration configuration, HashSet<string> testCaseNameSet)
|
||||||
{
|
{
|
||||||
var testCaseDisplayNames = ReadTestCaseDisplayNames(stream);
|
|
||||||
var testCaseDisplayNameSet = new HashSet<string>(testCaseDisplayNames, StringComparer.Ordinal);
|
|
||||||
var testCaseList = new List<ITestCase>();
|
var testCaseList = new List<ITestCase>();
|
||||||
|
|
||||||
using (var discoverer = new TestCaseDiscoverer(testCaseDisplayNameSet, testCaseList))
|
using (var sink = new TestDiscoverySink(testCaseNameSet, testCaseList))
|
||||||
{
|
{
|
||||||
xunit.Find(includeSourceInformation: false, messageSink: discoverer, discoveryOptions: TestFrameworkOptions.ForDiscovery());
|
xunit.Find(includeSourceInformation: false, messageSink: sink,
|
||||||
discoverer.Finished.WaitOne();
|
discoveryOptions: TestFrameworkOptions.ForDiscovery(configuration));
|
||||||
|
|
||||||
|
sink.Finished.WaitOne();
|
||||||
}
|
}
|
||||||
|
|
||||||
return testCaseList;
|
return testCaseList;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void RunAll(string assemblyPath, Stream stream)
|
internal static void RunAll(string assemblyFileName, Stream stream)
|
||||||
{
|
{
|
||||||
using (AssemblyHelper.SubscribeResolve())
|
Go(assemblyFileName, stream, AppDomainSupport.IfAvailable,
|
||||||
using (var xunit = new XunitFrontController(
|
(xunit, configuration, writer) =>
|
||||||
assemblyFileName: assemblyPath,
|
{
|
||||||
useAppDomain: true,
|
using (var sink = new TestRunSink(writer))
|
||||||
shadowCopy: false,
|
{
|
||||||
diagnosticMessageSink: new MessageVisitor()))
|
xunit.RunAll(sink,
|
||||||
using (var writer = new ClientWriter(stream))
|
discoveryOptions: TestFrameworkOptions.ForDiscovery(configuration),
|
||||||
using (var testRunVisitor = new TestRunVisitor(writer))
|
executionOptions: TestFrameworkOptions.ForExecution(configuration));
|
||||||
{
|
|
||||||
xunit.RunAll(testRunVisitor, TestFrameworkOptions.ForDiscovery(), TestFrameworkOptions.ForExecution());
|
sink.Finished.WaitOne();
|
||||||
testRunVisitor.Finished.WaitOne();
|
|
||||||
writer.Write(TestDataKind.EndOfData);
|
writer.Write(TestDataKind.EndOfData);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void RunSpecific(string assemblyPath, Stream stream)
|
internal static void RunSpecific(string assemblyFileName, Stream stream)
|
||||||
{
|
{
|
||||||
using (AssemblyHelper.SubscribeResolve())
|
var testCaseUniqueIDSet = new HashSet<string>(ReadTestCaseUniqueIDs(stream), StringComparer.Ordinal);
|
||||||
using (var xunit = new XunitFrontController(
|
|
||||||
assemblyFileName: assemblyPath,
|
Go(assemblyFileName, stream, AppDomainSupport.IfAvailable,
|
||||||
useAppDomain: true,
|
(xunit, configuration, writer) =>
|
||||||
shadowCopy: false,
|
{
|
||||||
diagnosticMessageSink: new MessageVisitor()))
|
using (var sink = new TestRunSink(writer))
|
||||||
using (var writer = new ClientWriter(stream))
|
{
|
||||||
using (var testRunVisitor = new TestRunVisitor(writer))
|
var testCaseList = GetTestCaseList(xunit, configuration, testCaseUniqueIDSet);
|
||||||
{
|
|
||||||
var testCaseList = GetTestCaseList(xunit, stream);
|
xunit.RunTests(testCaseList, sink,
|
||||||
xunit.RunTests(testCaseList, testRunVisitor, TestFrameworkOptions.ForExecution());
|
executionOptions: TestFrameworkOptions.ForExecution(configuration));
|
||||||
testRunVisitor.Finished.WaitOne();
|
|
||||||
writer.Write(TestDataKind.EndOfData);
|
sink.Finished.WaitOne();
|
||||||
}
|
|
||||||
|
writer.Write(TestDataKind.EndOfData);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Xunit.Runner.Data;
|
||||||
|
|
||||||
|
namespace Xunit.Runner.Worker
|
||||||
|
{
|
||||||
|
internal abstract class XunitUtil
|
||||||
|
{
|
||||||
|
protected static void Go(string assemblyFileName, Stream stream, AppDomainSupport appDomainSupport,
|
||||||
|
Action<XunitFrontController, TestAssemblyConfiguration, ClientWriter> action)
|
||||||
|
{
|
||||||
|
using (AssemblyHelper.SubscribeResolveForAssembly(assemblyFileName))
|
||||||
|
using (var xunit = new XunitFrontController(appDomainSupport, assemblyFileName, shadowCopy: false))
|
||||||
|
using (var writer = new ClientWriter(stream))
|
||||||
|
{
|
||||||
|
var configuration = ConfigReader.Load(assemblyFileName);
|
||||||
|
action(xunit, configuration, writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?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" />
|
|
||||||
</packages>
|
|
||||||
@@ -1,77 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
|
||||||
<ProjectGuid>{9DF97A2B-0EB5-4B12-9F81-69DFAC979814}</ProjectGuid>
|
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
<RootNamespace>Xunit.Runner.Worker</RootNamespace>
|
||||||
<RootNamespace>xunit.runner.worker</RootNamespace>
|
<TargetFramework>net472</TargetFramework>
|
||||||
<AssemblyName>xunit.runner.worker</AssemblyName>
|
|
||||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
|
||||||
<FileAlignment>512</FileAlignment>
|
|
||||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
|
||||||
<DebugSymbols>true</DebugSymbols>
|
|
||||||
<DebugType>full</DebugType>
|
|
||||||
<Optimize>false</Optimize>
|
|
||||||
<OutputPath>bin\Debug\</OutputPath>
|
|
||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
|
||||||
<DebugType>pdbonly</DebugType>
|
|
||||||
<Optimize>true</Optimize>
|
|
||||||
<OutputPath>bin\Release\</OutputPath>
|
|
||||||
<DefineConstants>TRACE</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="System" />
|
<PackageReference Include="xunit.runner.utility" Version="2.4.1" />
|
||||||
<Reference Include="System.Core" />
|
<ProjectReference Include="..\xunit.runner.data\xunit.runner.data.csproj" />
|
||||||
<Reference Include="System.Xml.Linq" />
|
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
|
||||||
<Reference Include="Microsoft.CSharp" />
|
|
||||||
<Reference Include="System.Data" />
|
|
||||||
<Reference Include="System.Net.Http" />
|
|
||||||
<Reference Include="System.Xml" />
|
|
||||||
<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>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="Connection.cs" />
|
|
||||||
<Compile Include="DiscoverUtil.cs" />
|
|
||||||
<Compile Include="MessageVisitor.cs" />
|
|
||||||
<Compile Include="Program.cs" />
|
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
|
||||||
<Compile Include="RunUtil.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="App.config" />
|
|
||||||
<None Include="packages.config" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\xunit.runner.data\xunit.runner.data.csproj">
|
|
||||||
<Project>{a1f579f4-443e-4f64-bc55-998ab86ff293}</Project>
|
|
||||||
<Name>xunit.runner.data</Name>
|
|
||||||
</ProjectReference>
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
|
||||||
<!-- 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">
|
|
||||||
</Target>
|
|
||||||
<Target Name="AfterBuild">
|
|
||||||
</Target>
|
|
||||||
-->
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 14
|
# Visual Studio 15
|
||||||
VisualStudioVersion = 14.0.23107.0
|
VisualStudioVersion = 15.0.26430.4
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
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}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xunit.runner.wpf", "xunit.runner.wpf\xunit.runner.wpf.csproj", "{34FB519C-FB49-4B31-ACA2-7F7879311BCF}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
|
||||||
<configuration>
|
|
||||||
<startup>
|
|
||||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
|
|
||||||
</startup>
|
|
||||||
</configuration>
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<Application x:Class="xunit.runner.wpf.App"
|
<Application x:Class="Xunit.Runner.Wpf.App"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="clr-namespace:xunit.runner.wpf"
|
xmlns:local="clr-namespace:Xunit.Runner.Wpf"
|
||||||
StartupUri="MainWindow.xaml"
|
StartupUri="MainWindow.xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
d1p1:Ignorable="d"
|
d1p1:Ignorable="d"
|
||||||
@@ -9,6 +9,6 @@
|
|||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
<vm:ViewModelLocator x:Key="Locator"
|
<vm:ViewModelLocator x:Key="Locator"
|
||||||
d:IsDataSource="True"
|
d:IsDataSource="True"
|
||||||
xmlns:vm="clr-namespace:xunit.runner.wpf.ViewModel" />
|
xmlns:vm="clr-namespace:Xunit.Runner.Wpf.ViewModel" />
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
</Application>
|
</Application>
|
||||||
@@ -1,16 +1,7 @@
|
|||||||
using System;
|
using System.Windows;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Configuration;
|
|
||||||
using System.Data;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
|
||||||
|
|
||||||
namespace xunit.runner.wpf
|
namespace Xunit.Runner.Wpf
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Interaction logic for App.xaml
|
|
||||||
/// </summary>
|
|
||||||
public partial class App : Application
|
public partial class App : Application
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 266 KiB |
|
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 |
|
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 |
|
Before Width: | Height: | Size: 6.4 KiB |
@@ -1,12 +1,7 @@
|
|||||||
using System;
|
using System.Windows;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
|
||||||
namespace xunit.runner.wpf
|
namespace Xunit.Runner.Wpf
|
||||||
{
|
{
|
||||||
class CommandBindings
|
class CommandBindings
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using xunit.runner.data;
|
using Xunit.Runner.Data;
|
||||||
using xunit.runner.wpf.ViewModel;
|
|
||||||
|
|
||||||
namespace xunit.runner.wpf.Converters
|
namespace Xunit.Runner.Wpf.Converters
|
||||||
{
|
{
|
||||||
public class TestStateConverter : IValueConverter
|
public class TestStateConverter : IValueConverter
|
||||||
{
|
{
|
||||||
private static ImageSource passedSource;
|
private static ImageSource runningSource;
|
||||||
private static ImageSource failedSource;
|
private static ImageSource failedSource;
|
||||||
|
private static ImageSource passedSource;
|
||||||
private static ImageSource skippedSource;
|
private static ImageSource skippedSource;
|
||||||
|
|
||||||
|
private static SolidColorBrush skippedBrush = new SolidColorBrush(Color.FromRgb(0xEB, 0xCA, 0x00));
|
||||||
|
|
||||||
static TestStateConverter()
|
static TestStateConverter()
|
||||||
{
|
{
|
||||||
passedSource = LoadResourceImage("Passed.ico");
|
runningSource = LoadResourceImage("Running_small.png");
|
||||||
failedSource = LoadResourceImage("Failed.ico");
|
failedSource = LoadResourceImage("Failed_small.png");
|
||||||
skippedSource = LoadResourceImage("Skipped.ico");
|
passedSource = LoadResourceImage("Passed_small.png");
|
||||||
|
skippedSource = LoadResourceImage("Skipped_small.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BitmapImage LoadResourceImage(string resourceName)
|
private static BitmapImage LoadResourceImage(string resourceName)
|
||||||
@@ -40,12 +40,14 @@ namespace xunit.runner.wpf.Converters
|
|||||||
{
|
{
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
|
case TestState.Running:
|
||||||
|
return Brushes.Blue;
|
||||||
case TestState.Failed:
|
case TestState.Failed:
|
||||||
return Brushes.Red;
|
return Brushes.Red;
|
||||||
case TestState.Skipped:
|
|
||||||
return Brushes.Yellow;
|
|
||||||
case TestState.Passed:
|
case TestState.Passed:
|
||||||
return Brushes.Green;
|
return Brushes.Green;
|
||||||
|
case TestState.Skipped:
|
||||||
|
return skippedBrush;
|
||||||
default:
|
default:
|
||||||
return Brushes.Gray;
|
return Brushes.Gray;
|
||||||
}
|
}
|
||||||
@@ -54,12 +56,14 @@ namespace xunit.runner.wpf.Converters
|
|||||||
{
|
{
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
|
case TestState.Running:
|
||||||
|
return runningSource;
|
||||||
case TestState.Failed:
|
case TestState.Failed:
|
||||||
return failedSource;
|
return failedSource;
|
||||||
case TestState.Skipped:
|
|
||||||
return skippedSource;
|
|
||||||
case TestState.Passed:
|
case TestState.Passed:
|
||||||
return passedSource;
|
return passedSource;
|
||||||
|
case TestState.Skipped:
|
||||||
|
return skippedSource;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Xunit.Runner.Wpf
|
||||||
|
{
|
||||||
|
public static partial class Extensions
|
||||||
|
{
|
||||||
|
private class FuncComparer<T> : IComparer<T>
|
||||||
|
{
|
||||||
|
private readonly Func<T, T, int> _comparison;
|
||||||
|
|
||||||
|
public FuncComparer(Func<T, T, int> comparison)
|
||||||
|
{
|
||||||
|
_comparison = comparison;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Compare(T x, T y) => _comparison(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace xunit.runner.wpf
|
namespace Xunit.Runner.Wpf
|
||||||
{
|
{
|
||||||
public static class Extensions
|
public static partial class Extensions
|
||||||
{
|
{
|
||||||
public static void AddRange<TList, TEnumerable>(this IList<TList> list, IEnumerable<TEnumerable> items) where TEnumerable : TList
|
public static void AddRange<TList, TEnumerable>(this ICollection<TList> list, IEnumerable<TEnumerable> items) where TEnumerable : TList
|
||||||
{
|
{
|
||||||
foreach (var i in items)
|
foreach (var i in items)
|
||||||
{
|
{
|
||||||
@@ -24,5 +21,80 @@ namespace xunit.runner.wpf
|
|||||||
list.Add(i);
|
list.Add(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int BinarySearch<T, TValue>(this ObservableCollection<T> collection, int index, int length, TValue value, IComparer<TValue> comparer, Func<T, TValue> selector)
|
||||||
|
{
|
||||||
|
comparer = comparer ?? Comparer<TValue>.Default;
|
||||||
|
|
||||||
|
var low = index;
|
||||||
|
var high = (index + length) - 1;
|
||||||
|
|
||||||
|
while (low <= high)
|
||||||
|
{
|
||||||
|
var mid = low + ((high - low) / 2);
|
||||||
|
var comp = comparer.Compare(selector(collection[mid]), value);
|
||||||
|
|
||||||
|
if (comp == 0)
|
||||||
|
{
|
||||||
|
return mid;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comp < 0)
|
||||||
|
{
|
||||||
|
low = mid + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
high = mid - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ~low;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int BinarySearch<T, TValue>(this ObservableCollection<T> collection, TValue value, IComparer<TValue> comparer, Func<T, TValue> selector)
|
||||||
|
{
|
||||||
|
return collection.BinarySearch(0, collection.Count, value, comparer, selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int BinarySearch<T, TValue>(this ObservableCollection<T> collection, int index, int length, TValue value, Func<TValue, TValue, int> comparison, Func<T, TValue> selector)
|
||||||
|
{
|
||||||
|
return collection.BinarySearch(index, length, value, new FuncComparer<TValue>(comparison), selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int BinarySearch<T, TValue>(this ObservableCollection<T> collection, TValue value, Func<TValue, TValue, int> comparison, Func<T, TValue> selector)
|
||||||
|
{
|
||||||
|
return collection.BinarySearch(0, collection.Count, value, new FuncComparer<TValue>(comparison), selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int BinarySearch<T, TValue>(this ObservableCollection<T> collection, TValue value, Func<T, TValue> selector)
|
||||||
|
{
|
||||||
|
return collection.BinarySearch(0, collection.Count, value, comparer: null, selector: selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int BinarySearch<T>(this ObservableCollection<T> collection, int index, int length, T value, IComparer<T> comparer)
|
||||||
|
{
|
||||||
|
return collection.BinarySearch(index, length, value, comparer, x => x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int BinarySearch<T>(this ObservableCollection<T> collection, T value, IComparer<T> comparer)
|
||||||
|
{
|
||||||
|
return collection.BinarySearch(0, collection.Count, value, comparer, x => x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int BinarySearch<T>(this ObservableCollection<T> collection, T value)
|
||||||
|
{
|
||||||
|
return collection.BinarySearch(0, collection.Count, value, Comparer<T>.Default, x => x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int BinarySearch<T>(this ObservableCollection<T> collection, int index, int length, T value, Func<T, T, int> comparison)
|
||||||
|
{
|
||||||
|
return collection.BinarySearch(index, length, value, new FuncComparer<T>(comparison), x => x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int BinarySearch<T>(this ObservableCollection<T> collection, T value, Func<T, T, int> comparison)
|
||||||
|
{
|
||||||
|
return collection.BinarySearch(0, collection.Count, value, new FuncComparer<T>(comparison), x => x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ using System.Collections.Specialized;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace xunit.runner.wpf
|
namespace Xunit.Runner.Wpf
|
||||||
{
|
{
|
||||||
public class FilteredCollectionView<T, TFilterArg> : IList<T>, IList, INotifyCollectionChanged, IDisposable
|
public class FilteredCollectionView<T, TFilterArg> : IList<T>, IList, INotifyCollectionChanged, IDisposable
|
||||||
{
|
{
|
||||||
private readonly ObservableCollection<T> dataSource;
|
private readonly ObservableCollection<T> dataSource;
|
||||||
private readonly List<T> filteredList;
|
private readonly List<T> filteredList;
|
||||||
private readonly Func<T, TFilterArg, bool> filter;
|
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)
|
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.filter = filter;
|
||||||
this.filterArgument = filterArgument;
|
this.filterArgument = filterArgument;
|
||||||
this.filteredList = new List<T>();
|
this.filteredList = new List<T>();
|
||||||
|
this.sort = sort;
|
||||||
|
|
||||||
this.dataSource.CollectionChanged += this.dataSource_CollectionChanged;
|
this.dataSource.CollectionChanged += this.dataSource_CollectionChanged;
|
||||||
|
|
||||||
@@ -45,11 +47,7 @@ namespace xunit.runner.wpf
|
|||||||
|
|
||||||
protected virtual void OnItemChanged(T sender, PropertyChangedEventArgs args)
|
protected virtual void OnItemChanged(T sender, PropertyChangedEventArgs args)
|
||||||
{
|
{
|
||||||
var itemChanged = this.ItemChanged;
|
this.ItemChanged?.Invoke(sender, args);
|
||||||
if (itemChanged != null)
|
|
||||||
{
|
|
||||||
itemChanged(sender, args);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TFilterArg filterArgument;
|
private TFilterArg filterArgument;
|
||||||
@@ -75,6 +73,7 @@ namespace xunit.runner.wpf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.filteredList.Sort(sort);
|
||||||
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +118,7 @@ namespace xunit.runner.wpf
|
|||||||
{
|
{
|
||||||
if (this.filter(item, this.filterArgument))
|
if (this.filter(item, this.filterArgument))
|
||||||
{
|
{
|
||||||
int index = this.filteredList.IndexOf(item);
|
int index = this.filteredList.BinarySearch(item, sort);
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
{
|
{
|
||||||
this.filteredList.Insert(~index, item);
|
this.filteredList.Insert(~index, item);
|
||||||
@@ -142,7 +141,7 @@ namespace xunit.runner.wpf
|
|||||||
observable.PropertyChanged -= this.dataSource_ItemChanged;
|
observable.PropertyChanged -= this.dataSource_ItemChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
int index = this.filteredList.IndexOf(item);
|
int index = this.filteredList.BinarySearch(item, sort);
|
||||||
if (index >= 0)
|
if (index >= 0)
|
||||||
{
|
{
|
||||||
this.filteredList.RemoveAt(index);
|
this.filteredList.RemoveAt(index);
|
||||||
@@ -153,7 +152,7 @@ namespace xunit.runner.wpf
|
|||||||
private void dataSource_ItemChanged(object sender, PropertyChangedEventArgs e)
|
private void dataSource_ItemChanged(object sender, PropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
var item = (T)sender;
|
var item = (T)sender;
|
||||||
int index = this.filteredList.IndexOf(item);
|
int index = this.filteredList.BinarySearch(item, sort);
|
||||||
if (this.filter(item, this.FilterArgument))
|
if (this.filter(item, this.FilterArgument))
|
||||||
{
|
{
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
@@ -187,11 +186,7 @@ namespace xunit.runner.wpf
|
|||||||
|
|
||||||
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
|
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
|
||||||
{
|
{
|
||||||
var collectionChanged = this.CollectionChanged;
|
this.CollectionChanged?.Invoke(this, args);
|
||||||
if (collectionChanged != null)
|
|
||||||
{
|
|
||||||
collectionChanged(this, args);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(T item)
|
public void Add(T item)
|
||||||
@@ -204,7 +199,7 @@ namespace xunit.runner.wpf
|
|||||||
throw new NotSupportedException();
|
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)
|
public void CopyTo(T[] array, int arrayIndex)
|
||||||
{
|
{
|
||||||
@@ -224,7 +219,14 @@ namespace xunit.runner.wpf
|
|||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
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)
|
public void Insert(int index, T item)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,91 +1,37 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Threading;
|
using Xunit.Runner.Data;
|
||||||
using xunit.runner.data;
|
|
||||||
using xunit.runner.wpf.ViewModel;
|
|
||||||
|
|
||||||
namespace xunit.runner.wpf
|
namespace Xunit.Runner.Wpf
|
||||||
{
|
{
|
||||||
internal interface ITestUtil
|
internal interface ITestUtil
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Discover the list of test cases which are available in the specified assembly.
|
/// Discover the list of test cases which are available in the specified assembly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ITestDiscoverSession Discover(string assemblyPath, CancellationToken cancellationToken = default(CancellationToken));
|
Task Discover(
|
||||||
|
string assebmlyFileName,
|
||||||
|
Action<IEnumerable<TestCaseData>> testsDiscovered,
|
||||||
|
CancellationToken cancellationToken = default(CancellationToken));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Begin a run of all unit tests for the given assembly.
|
/// Begin a run of all unit tests for the given assembly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ITestRunSession RunAll(string assemblyPath, CancellationToken cancellationToken = default(CancellationToken));
|
Task RunAll(
|
||||||
|
string assemblyFileName,
|
||||||
|
Action<IEnumerable<TestResultData>> testsFinished,
|
||||||
|
CancellationToken cancellationToken = default(CancellationToken));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Begin a run of specific unit tests for the given assembly.
|
/// Begin a run of specific unit tests for the given assembly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ITestRunSession RunSpecific(string assemblyPath, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken = default(CancellationToken));
|
Task RunSpecific(
|
||||||
|
string assemblyFileName,
|
||||||
|
ImmutableArray<string> testCasesToRun,
|
||||||
|
Action<IEnumerable<TestResultData>> testsFinished,
|
||||||
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,12 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
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;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
using xunit.runner.data;
|
using Xunit.Runner.Data;
|
||||||
using xunit.runner.wpf.ViewModel;
|
|
||||||
|
|
||||||
namespace xunit.runner.wpf.Impl
|
namespace Xunit.Runner.Wpf.Impl
|
||||||
{
|
{
|
||||||
internal partial class RemoteTestUtil
|
internal partial class RemoteTestUtil
|
||||||
{
|
{
|
||||||
@@ -54,26 +48,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 sealed class BackgroundReader<T> where T : class
|
||||||
{
|
{
|
||||||
private readonly ConcurrentQueue<T> _queue;
|
private readonly ConcurrentQueue<T> _queue;
|
||||||
private readonly ClientReader _reader;
|
private readonly ClientReader _reader;
|
||||||
private readonly Func<ClientReader, T> _readValue;
|
private readonly Func<ClientReader, T> _readValue;
|
||||||
private readonly CancellationToken _cancellationToken;
|
|
||||||
|
|
||||||
internal ClientReader Reader => _reader;
|
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;
|
_queue = queue;
|
||||||
_reader = reader;
|
_reader = reader;
|
||||||
_readValue = readValue;
|
_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>
|
/// <summary>
|
||||||
@@ -81,9 +78,9 @@ namespace xunit.runner.wpf.Impl
|
|||||||
/// named pipe client stream.
|
/// named pipe client stream.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private void GoOnBackground()
|
private void GoOnBackground(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
while (!_cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -135,7 +132,7 @@ namespace xunit.runner.wpf.Impl
|
|||||||
_maxPerTick = maxResultPerTick;
|
_maxPerTick = maxResultPerTick;
|
||||||
_callback = callback;
|
_callback = callback;
|
||||||
_timer = new DispatcherTimer(
|
_timer = new DispatcherTimer(
|
||||||
interval ?? TimeSpan.FromMilliseconds(100),
|
interval ?? TimeSpan.FromMilliseconds(500),
|
||||||
DispatcherPriority.Normal,
|
DispatcherPriority.Normal,
|
||||||
OnTimerTick,
|
OnTimerTick,
|
||||||
dispatcher);
|
dispatcher);
|
||||||
@@ -159,16 +156,12 @@ namespace xunit.runner.wpf.Impl
|
|||||||
list.Add(value);
|
list.Add(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.Count > 0)
|
_callback(list);
|
||||||
{
|
|
||||||
_callback(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDone)
|
if (isDone)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_callback(null);
|
|
||||||
_timer.Stop();
|
_timer.Stop();
|
||||||
_connection.Dispose();
|
_connection.Dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,65 +1,45 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.IO.Pipes;
|
using System.IO.Pipes;
|
||||||
using System.Linq;
|
using Xunit.Runner.Data;
|
||||||
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
|
namespace Xunit.Runner.Wpf.Impl
|
||||||
{
|
{
|
||||||
internal sealed partial class RemoteTestUtil : ITestUtil
|
internal sealed partial class RemoteTestUtil : ITestUtil
|
||||||
{
|
{
|
||||||
private sealed class Connection : IDisposable
|
private sealed class Connection : IDisposable
|
||||||
{
|
{
|
||||||
private NamedPipeClientStream _stream;
|
private NamedPipeClientStream _stream;
|
||||||
private Process _process;
|
|
||||||
private ClientReader _reader;
|
private ClientReader _reader;
|
||||||
|
|
||||||
internal NamedPipeClientStream Stream => _stream;
|
internal NamedPipeClientStream Stream => _stream;
|
||||||
|
|
||||||
internal ClientReader Reader => _reader;
|
internal ClientReader Reader => _reader;
|
||||||
|
|
||||||
internal Connection(NamedPipeClientStream stream, Process process)
|
internal Connection(NamedPipeClientStream stream)
|
||||||
{
|
{
|
||||||
_stream = stream;
|
_stream = stream;
|
||||||
_process = process;
|
|
||||||
_reader = new ClientReader(stream);
|
_reader = new ClientReader(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Dispose()
|
internal void Dispose()
|
||||||
{
|
{
|
||||||
if (_process != null)
|
if (_stream == null)
|
||||||
{
|
{
|
||||||
Debug.Assert(_stream != null);
|
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();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_process.Kill();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Inherent race condition shutting down the process.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,82 +3,151 @@ using System.Collections.Concurrent;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
|
||||||
using System.IO.Pipes;
|
using System.IO.Pipes;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
using xunit.runner.data;
|
using Xunit.Runner.Data;
|
||||||
using xunit.runner.wpf.ViewModel;
|
|
||||||
|
|
||||||
namespace xunit.runner.wpf.Impl
|
namespace Xunit.Runner.Wpf.Impl
|
||||||
{
|
{
|
||||||
internal sealed partial class RemoteTestUtil : ITestUtil
|
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 readonly Dispatcher _dispatcher;
|
||||||
|
private ProcessInfo? _processInfo;
|
||||||
|
|
||||||
internal RemoteTestUtil(Dispatcher dispatcher)
|
internal RemoteTestUtil(Dispatcher dispatcher)
|
||||||
{
|
{
|
||||||
_dispatcher = dispatcher;
|
_dispatcher = dispatcher;
|
||||||
|
_processInfo = StartWorkerProcess();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Connection StartWorkerProcess(string action, string argument)
|
private async Task<Connection> CreateConnection(string action, string argument, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var pipeName = $"xunit.runner.wpf.pipe.{Guid.NewGuid()}";
|
var pipeName = GetPipeName();
|
||||||
var processStartInfo = new ProcessStartInfo();
|
|
||||||
processStartInfo.FileName = typeof(xunit.runner.worker.Program).Assembly.Location;
|
|
||||||
processStartInfo.Arguments = $"{pipeName} {action} {argument}";
|
|
||||||
processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
|
||||||
|
|
||||||
var process = Process.Start(processStartInfo);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var stream = new NamedPipeClientStream(pipeName);
|
var stream = new NamedPipeClientStream(pipeName);
|
||||||
stream.Connect();
|
await stream.ConnectAsync(cancellationToken);
|
||||||
return new Connection(stream, process);
|
|
||||||
|
var writer = new ClientWriter(stream);
|
||||||
|
writer.Write(action);
|
||||||
|
writer.Write(argument);
|
||||||
|
|
||||||
|
return new Connection(stream);
|
||||||
}
|
}
|
||||||
catch
|
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;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DiscoverSession Discover(string assemblyPath, CancellationToken cancellationToken)
|
private string GetPipeName()
|
||||||
{
|
{
|
||||||
var connection = StartWorkerProcess(Constants.ActionDiscover, assemblyPath);
|
var process = _processInfo?.Process;
|
||||||
return new DiscoverSession(connection, _dispatcher, cancellationToken);
|
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);
|
var pipeName = $"xunit.runner.wpf.pipe.{Guid.NewGuid()}";
|
||||||
return new RunSession(connection, _dispatcher, ImmutableArray<string>.Empty, cancellationToken);
|
var processStartInfo = new ProcessStartInfo();
|
||||||
|
processStartInfo.FileName = typeof(Xunit.Runner.Worker.Program).Assembly.Location;
|
||||||
|
processStartInfo.Arguments = $"{pipeName} {Process.GetCurrentProcess().Id}";
|
||||||
|
processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||||
|
processStartInfo.UseShellExecute = false;
|
||||||
|
processStartInfo.CreateNoWindow = true;
|
||||||
|
var process = Process.Start(processStartInfo);
|
||||||
|
return new ProcessInfo(pipeName, process);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RunSession RunSpecific(string assemblyPath, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken)
|
private void RecycleProcess()
|
||||||
{
|
{
|
||||||
var connection = StartWorkerProcess(Constants.ActionRunSpecific, assemblyPath);
|
var process = _processInfo?.Process;
|
||||||
return new RunSession(connection, _dispatcher, testCaseDisplayNames, cancellationToken);
|
if (process != null && !process.HasExited)
|
||||||
|
{
|
||||||
|
process.Kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
_processInfo = StartWorkerProcess();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Discover(string assemblyPath, Action<List<TestCaseData>> callback, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var connection = await CreateConnection(Constants.ActionDiscover, assemblyPath, cancellationToken);
|
||||||
|
await ProcessResultsCore(connection, r => r.ReadTestCaseData(), callback, cancellationToken);
|
||||||
|
|
||||||
|
RecycleProcess();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RunCore(string actionName, string assemblyPath, ImmutableArray<string> testCaseDisplayNames, Action<List<TestResultData>> callback, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var connection = await CreateConnection(actionName, assemblyPath, cancellationToken);
|
||||||
|
|
||||||
|
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<List<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
|
#region ITestUtil
|
||||||
|
|
||||||
ITestDiscoverSession ITestUtil.Discover(string assemblyPath, CancellationToken cancellationToken)
|
Task ITestUtil.Discover(string assemblyFileName, Action<IEnumerable<TestCaseData>> testsDiscovered, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Discover(assemblyPath, cancellationToken);
|
return Discover(assemblyFileName, testsDiscovered, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
ITestRunSession ITestUtil.RunAll(string assemblyPath, CancellationToken cancellationToken)
|
Task ITestUtil.RunAll(string assemblyFileName, Action<IEnumerable<TestResultData>> testsFinished, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return RunAll(assemblyPath, cancellationToken);
|
return RunCore(Constants.ActionRunAll, assemblyFileName, ImmutableArray<string>.Empty, testsFinished, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
ITestRunSession ITestUtil.RunSpecific(string assemblyPath, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken)
|
Task ITestUtil.RunSpecific(string assemblyFileName, ImmutableArray<string> testCases, Action<IEnumerable<TestResultData>> testsFinished, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return RunSpecific(assemblyPath, testCaseDisplayNames, cancellationToken);
|
return RunCore(Constants.ActionRunSpecific, assemblyFileName, testCases, testsFinished, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<Window x:Class="xunit.runner.wpf.LoadingDialog"
|
|
||||||
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"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
Title="Discovering Tests"
|
|
||||||
SizeToContent="WidthAndHeight"
|
|
||||||
ResizeMode="NoResize"
|
|
||||||
ShowInTaskbar="False"
|
|
||||||
MinHeight="100"
|
|
||||||
MinWidth="300"
|
|
||||||
WindowStartupLocation="CenterOwner">
|
|
||||||
<Grid>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<TextBlock Text="Discovering tests in:"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Grid.Row="0"/>
|
|
||||||
<TextBlock Text="{Binding Path=AssemblyFileName, RelativeSource={RelativeSource AncestorType=Window}}"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Grid.Row="1"/>
|
|
||||||
</Grid>
|
|
||||||
</Window>
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using System.Windows.Data;
|
|
||||||
using System.Windows.Documents;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using System.Windows.Media;
|
|
||||||
using System.Windows.Media.Imaging;
|
|
||||||
using System.Windows.Shapes;
|
|
||||||
|
|
||||||
namespace xunit.runner.wpf
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Interaction logic for LoadingDialog.xaml
|
|
||||||
/// </summary>
|
|
||||||
public partial class LoadingDialog : Window
|
|
||||||
{
|
|
||||||
public LoadingDialog()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public string AssemblyFileName
|
|
||||||
{
|
|
||||||
get { return (string)GetValue(AssemblyFileNameProperty); }
|
|
||||||
set { SetValue(AssemblyFileNameProperty, value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly DependencyProperty AssemblyFileNameProperty =
|
|
||||||
DependencyProperty.Register(nameof(AssemblyFileName), typeof(string), typeof(LoadingDialog));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +1,34 @@
|
|||||||
<Window x:Class="xunit.runner.wpf.MainWindow"
|
<Window
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
x:Class="Xunit.Runner.Wpf.MainWindow"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform"
|
||||||
xmlns:local="clr-namespace:xunit.runner.wpf"
|
xmlns:converters="clr-namespace:Xunit.Runner.Wpf.Converters"
|
||||||
xmlns:converters="clr-namespace:xunit.runner.wpf.Converters"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:vm="clr-namespace:xunit.runner.wpf.ViewModel"
|
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
|
||||||
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
|
xmlns:local="clr-namespace:Xunit.Runner.Wpf"
|
||||||
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
mc:Ignorable="d"
|
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||||
DataContext="{Binding Main, Source={StaticResource Locator}}"
|
xmlns:vm="clr-namespace:Xunit.Runner.Wpf.ViewModel"
|
||||||
Title="xUnit.net Test Runner"
|
Name="Main"
|
||||||
Icon="Artwork\xunit-dot-net-small-logo.png"
|
Title="xUnit.net Test Runner"
|
||||||
ResizeMode="CanResizeWithGrip"
|
Width="525"
|
||||||
Height="600"
|
Height="600"
|
||||||
Width="525">
|
MinWidth="425"
|
||||||
|
MinHeight="525"
|
||||||
|
DataContext="{Binding Main, Source={StaticResource Locator}}"
|
||||||
|
Icon="Artwork\Application.ico"
|
||||||
|
ResizeMode="CanResizeWithGrip"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<i:Interaction.Triggers>
|
<i:Interaction.Triggers>
|
||||||
<i:EventTrigger EventName="Loaded">
|
<i:EventTrigger EventName="Loaded">
|
||||||
<cmd:EventToCommand Command="{Binding WindowLoadedCommand}" />
|
<cmd:EventToCommand Command="{Binding WindowLoadedCommand}" />
|
||||||
</i:EventTrigger>
|
</i:EventTrigger>
|
||||||
|
|
||||||
|
<i:EventTrigger EventName="Closing">
|
||||||
|
<cmd:EventToCommand Command="{Binding WindowClosingCommand}" PassEventArgsToCommand="True" />
|
||||||
|
</i:EventTrigger>
|
||||||
</i:Interaction.Triggers>
|
</i:Interaction.Triggers>
|
||||||
|
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
@@ -35,16 +44,28 @@
|
|||||||
|
|
||||||
<Menu Grid.Row="0">
|
<Menu Grid.Row="0">
|
||||||
<MenuItem Header="_File">
|
<MenuItem Header="_File">
|
||||||
<MenuItem Header="E_xit"
|
<MenuItem Command="{Binding ExitCommand}" Header="E_xit" />
|
||||||
Command="{Binding ExitCommand}" />
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem Header="_Assembly">
|
<MenuItem Header="_Assembly">
|
||||||
<MenuItem Header="_Open"
|
<MenuItem Command="ApplicationCommands.Open" Header="_Open" />
|
||||||
Command="ApplicationCommands.Open" />
|
<MenuItem Header="R_ecent" ItemsSource="{Binding RecentAssemblies}">
|
||||||
<MenuItem Header="R_ecent" />
|
<MenuItem.ItemContainerStyle>
|
||||||
|
<Style TargetType="MenuItem">
|
||||||
|
<Setter Property="Header" Value="{Binding FilePath}" />
|
||||||
|
<Setter Property="Command" Value="{Binding Command}" />
|
||||||
|
<Setter Property="CommandParameter" Value="{Binding}" />
|
||||||
|
</Style>
|
||||||
|
</MenuItem.ItemContainerStyle>
|
||||||
|
</MenuItem>
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem Header="_Unload" />
|
<MenuItem Command="{Binding AssemblyRemoveAllCommand}" Header="_Unload" />
|
||||||
<MenuItem Header="_Reload" />
|
<MenuItem Command="{Binding AssemblyReloadAllCommand}" Header="_Reload" />
|
||||||
|
<Separator />
|
||||||
|
<MenuItem
|
||||||
|
Command="{Binding AutoReloadAssembliesCommand}"
|
||||||
|
Header="_Auto Reload Test Assemblies"
|
||||||
|
IsCheckable="True"
|
||||||
|
IsChecked="{Binding AutoReloadAssemblies}" />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem Header="_Project">
|
<MenuItem Header="_Project">
|
||||||
<MenuItem Header="_Open" />
|
<MenuItem Header="_Open" />
|
||||||
@@ -59,18 +80,20 @@
|
|||||||
|
|
||||||
<Grid Grid.Row="1">
|
<Grid Grid.Row="1">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" MinWidth="200px" />
|
||||||
<ColumnDefinition Width="2*" />
|
<ColumnDefinition Width="2*" MinWidth="200px" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition Height="auto" />
|
<RowDefinition Height="auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<GroupBox Header="Refinements"
|
<GroupBox
|
||||||
Margin="3"
|
Grid.Row="0"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.Row="0">
|
Margin="3"
|
||||||
|
Header="Refinements">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="auto" />
|
<RowDefinition Height="auto" />
|
||||||
@@ -80,101 +103,214 @@
|
|||||||
<RowDefinition Height="auto" />
|
<RowDefinition Height="auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<Label Content="Search:"
|
|
||||||
Grid.Row="0" />
|
<Label Grid.Row="0" Content="Search:" />
|
||||||
<TextBox Grid.Row="1"
|
<TextBox Grid.Row="1" Text="{Binding FilterString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
Text="{Binding FilterString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
|
||||||
<Label Content="Assemblies:"
|
<Label Grid.Row="2" Content="Assemblies:" />
|
||||||
Grid.Row="2" />
|
<ListBox
|
||||||
<ListBox Height="175"
|
Grid.Row="3"
|
||||||
ItemsSource="{Binding Assemblies}"
|
Height="175"
|
||||||
SelectionMode="Extended"
|
ItemsSource="{Binding Assemblies}"
|
||||||
Grid.Row="3">
|
SelectionMode="Extended">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate DataType="vm:TestAssemblyViewModel">
|
<DataTemplate DataType="vm:TestAssemblyViewModel">
|
||||||
<TextBlock Text="{Binding DisplayName}" />
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock Text="{Binding DisplayName}" />
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
FontStyle="Italic"
|
||||||
|
Foreground="Gray"
|
||||||
|
Text=" Discovering tests...">
|
||||||
|
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding State}" Value="{x:Static vm:AssemblyState.Loading}">
|
||||||
|
<Setter Property="Visibility" Value="Visible" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</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 Command="{Binding AssemblyReloadCommand}" Header="Reload" />
|
||||||
|
<MenuItem Command="{Binding AssemblyReloadAllCommand}" Header="Reload All" />
|
||||||
|
<Separator />
|
||||||
|
<MenuItem Command="{Binding AssemblyRemoveCommand}" Header="Remove" />
|
||||||
|
<MenuItem Command="{Binding AssemblyRemoveAllCommand}" Header="Remove All" />
|
||||||
|
</ContextMenu>
|
||||||
|
</ListBox.ContextMenu>
|
||||||
</ListBox>
|
</ListBox>
|
||||||
<Label Content="Traits:"
|
|
||||||
Grid.Row="4" />
|
<Label Grid.Row="4" Content="Traits:" />
|
||||||
<ListBox Grid.Row="5" />
|
|
||||||
|
<TreeView Grid.Row="5" ItemsSource="{Binding Traits}">
|
||||||
|
|
||||||
|
<TreeView.Resources>
|
||||||
|
<HierarchicalDataTemplate DataType="{x:Type vm:TraitViewModel}" ItemsSource="{Binding Children}">
|
||||||
|
|
||||||
|
<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}" />
|
||||||
|
</i:EventTrigger>
|
||||||
|
|
||||||
|
<i:EventTrigger EventName="Unchecked">
|
||||||
|
<cmd:EventToCommand Command="{Binding DataContext.TraitCheckedChangedCommand, RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter="{Binding}" />
|
||||||
|
</i:EventTrigger>
|
||||||
|
</i:Interaction.Triggers>
|
||||||
|
</CheckBox>
|
||||||
|
|
||||||
|
</HierarchicalDataTemplate>
|
||||||
|
</TreeView.Resources>
|
||||||
|
|
||||||
|
<TreeView.ItemContainerStyle>
|
||||||
|
<Style TargetType="TreeViewItem">
|
||||||
|
<Setter Property="IsExpanded" Value="{Binding IsExpanded}" />
|
||||||
|
</Style>
|
||||||
|
</TreeView.ItemContainerStyle>
|
||||||
|
|
||||||
|
<TreeView.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem Command="{Binding TraitsClearCommand}" Header="Clear" />
|
||||||
|
</ContextMenu>
|
||||||
|
</TreeView.ContextMenu>
|
||||||
|
|
||||||
|
</TreeView>
|
||||||
</Grid>
|
</Grid>
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
|
|
||||||
<Grid Grid.Column="0"
|
<Grid Grid.Row="1" Grid.Column="0">
|
||||||
Grid.Row="1">
|
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<Button Content="_Run All"
|
<Button
|
||||||
Command="{Binding RunCommand}"
|
Grid.Column="0"
|
||||||
Margin="3"
|
Margin="10,0,0,0"
|
||||||
Grid.Column="0" />
|
Command="{Binding RunAllCommand}"
|
||||||
<Button Content="_Cancel"
|
Content="_Run All" />
|
||||||
Command="{Binding CancelCommand}"
|
<Button
|
||||||
Margin="0,3,3,3"
|
Grid.Column="1"
|
||||||
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 Grid.Column="1"
|
<Grid Grid.Row="0" Grid.Column="1">
|
||||||
Grid.Row="0">
|
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" MinHeight="200px" />
|
||||||
<RowDefinition Height="auto" />
|
<RowDefinition Height="auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" MinHeight="200px" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<GroupBox Header="{Binding MethodsCaption}"
|
<GroupBox
|
||||||
Margin="3"
|
Grid.Row="0"
|
||||||
Grid.Row="0">
|
Margin="3"
|
||||||
|
Header="{Binding TestCasesCaption}">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="auto" />
|
<RowDefinition Height="auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<Grid>
|
<StackPanel Orientation="Horizontal">
|
||||||
<Grid.ColumnDefinitions>
|
<ToggleButton
|
||||||
<ColumnDefinition Width="*" />
|
Grid.Column="0"
|
||||||
<ColumnDefinition Width="*" />
|
Margin="0,4,2,4"
|
||||||
<ColumnDefinition Width="*" />
|
Background="Transparent"
|
||||||
</Grid.ColumnDefinitions>
|
BorderThickness="0"
|
||||||
<ToggleButton IsChecked="{Binding IncludePassedTests}"
|
Command="{Binding TestFilterChanged}"
|
||||||
Margin="3">
|
IsChecked="{Binding FilterPassedTests}">
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<Image Source="Artwork\Passed.ico"
|
<Image Source="Artwork\Passed_large.png" />
|
||||||
Height="16" />
|
<TextBlock
|
||||||
<TextBlock Margin="4,0,0,0"
|
Margin="4,0"
|
||||||
Text="{Binding TestsPassed}" />
|
VerticalAlignment="Center"
|
||||||
|
FontSize="16"
|
||||||
|
Text="{Binding TestsPassed, StringFormat={}{0:#\,0}}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
<ToggleButton IsChecked="{Binding IncludeFailedTests}"
|
|
||||||
Margin="3"
|
<ToggleButton
|
||||||
Grid.Column="1">
|
Grid.Column="1"
|
||||||
|
Margin="2,4"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderThickness="0"
|
||||||
|
Command="{Binding TestFilterChanged}"
|
||||||
|
IsChecked="{Binding FilterFailedTests}">
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<Image Source="Artwork\Failed.ico"
|
<Image Source="Artwork\Failed_large.png" />
|
||||||
Height="16" />
|
<TextBlock
|
||||||
<TextBlock Margin="4,0,0,0"
|
Margin="4,0"
|
||||||
Text="{Binding TestsFailed}" />
|
VerticalAlignment="Center"
|
||||||
|
FontSize="16"
|
||||||
|
Text="{Binding TestsFailed, StringFormat={}{0:#\,0}}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
<ToggleButton IsChecked="{Binding IncludeSkippedTests}"
|
|
||||||
Margin="3"
|
<ToggleButton
|
||||||
Grid.Column="2">
|
Grid.Column="2"
|
||||||
|
Margin="2,4,0,4"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderThickness="0"
|
||||||
|
Command="{Binding TestFilterChanged}"
|
||||||
|
IsChecked="{Binding FilterSkippedTests}">
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<Image Source="Artwork\Skipped.ico"
|
<Image Source="Artwork\Skipped_large.png" />
|
||||||
Height="16" />
|
<TextBlock
|
||||||
<TextBlock Margin="4,0,0,0"
|
Margin="4,0"
|
||||||
Text="{Binding TestsSkipped}" />
|
VerticalAlignment="Center"
|
||||||
|
FontSize="16"
|
||||||
|
Text="{Binding TestsSkipped, StringFormat={}{0:#\,0}}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
</Grid>
|
|
||||||
<ListBox ItemsSource="{Binding TestCases}"
|
<ToggleButton
|
||||||
SelectionMode="Extended"
|
Grid.Column="2"
|
||||||
Grid.Row="1">
|
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
|
||||||
|
x:Name="TestCases"
|
||||||
|
Grid.Row="1"
|
||||||
|
ItemsSource="{Binding FilteredTestCases}"
|
||||||
|
SelectedItem="{Binding SelectedTestCase, Mode=TwoWay}"
|
||||||
|
SelectionChanged="TestCases_SelectionChanged"
|
||||||
|
SelectionMode="Extended">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate DataType="vm:TestCaseViewModel">
|
<DataTemplate DataType="vm:TestCaseViewModel">
|
||||||
<Grid>
|
<Grid>
|
||||||
@@ -183,45 +319,108 @@
|
|||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<Image Width="16"
|
<Image
|
||||||
Margin="2"
|
Grid.Column="0"
|
||||||
Source="{Binding Path=State, Mode=OneWay, Converter={StaticResource TestStateConverter}}"
|
Width="16"
|
||||||
Grid.Column="0" />
|
Margin="0,0,2,0"
|
||||||
<TextBlock Text="{Binding DisplayName}"
|
Source="{Binding Path=State, Mode=OneWay, Converter={StaticResource TestStateConverter}}" />
|
||||||
Grid.Column="1" />
|
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="1"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding DisplayName}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</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>
|
</ListBox>
|
||||||
</Grid>
|
</Grid>
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
|
|
||||||
<GridSplitter Grid.Row="1" />
|
<GridSplitter
|
||||||
|
Grid.Row="1"
|
||||||
|
Height="3"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Background="White" />
|
||||||
|
|
||||||
<GroupBox Header="Output"
|
<GroupBox
|
||||||
Margin="3"
|
Grid.Row="2"
|
||||||
Grid.Row="2">
|
Margin="3"
|
||||||
<TextBox IsReadOnly="True"
|
Header="Output">
|
||||||
Text="{Binding Output}"/>
|
<TextBox
|
||||||
|
FontFamily="Consolas"
|
||||||
|
HorizontalScrollBarVisibility="Auto"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Text="{Binding Output}"
|
||||||
|
VerticalScrollBarVisibility="Visible" />
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<ProgressBar Foreground="{Binding Path=CurrentRunState, Converter={StaticResource TestStateConverter}, Mode=OneWay}"
|
<GridSplitter
|
||||||
Minimum="0"
|
Grid.Column="0"
|
||||||
Maximum="{Binding MaximumProgress}"
|
Width="3"
|
||||||
Value="{Binding Path=TestsCompleted}"
|
VerticalAlignment="Stretch"
|
||||||
Grid.Column="1"
|
Background="White" />
|
||||||
Grid.Row="1"
|
|
||||||
Margin="3" />
|
<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>
|
</Grid>
|
||||||
|
|
||||||
<StackPanel Grid.Row="2">
|
<StackPanel Grid.Row="2">
|
||||||
<Border BorderBrush="LightGray"
|
<Border
|
||||||
BorderThickness="1"
|
Margin="3"
|
||||||
Margin="3" />
|
BorderBrush="LightGray"
|
||||||
|
BorderThickness="1" />
|
||||||
<StatusBar>
|
<StatusBar>
|
||||||
<StatusBarItem>
|
<StatusBar.ItemsPanel>
|
||||||
<Label />
|
<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 Foreground="DarkGreen" Text="Tests passed: " />
|
||||||
|
|
||||||
|
<TextBlock Text="{Binding TestsPassed, StringFormat={}{0:#\,0}}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StatusBarItem>
|
||||||
|
|
||||||
|
<StatusBarItem Grid.Column="2">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock Foreground="DarkRed" Text="Tests failed: " />
|
||||||
|
|
||||||
|
<TextBlock Text="{Binding TestsFailed, StringFormat={}{0:#\,0}}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StatusBarItem>
|
||||||
|
|
||||||
|
<StatusBarItem Grid.Column="3">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock Foreground="DarkGoldenrod" Text="Tests skipped: " />
|
||||||
|
|
||||||
|
<TextBlock Text="{Binding TestsSkipped, StringFormat={}{0:#\,0}}" />
|
||||||
|
</StackPanel>
|
||||||
</StatusBarItem>
|
</StatusBarItem>
|
||||||
</StatusBar>
|
</StatusBar>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -1,25 +1,17 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using Xunit.Runner.Wpf.Persistence;
|
||||||
using System.Windows.Data;
|
|
||||||
using System.Windows.Documents;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using System.Windows.Media;
|
|
||||||
using System.Windows.Media.Imaging;
|
|
||||||
using System.Windows.Navigation;
|
|
||||||
using System.Windows.Shapes;
|
|
||||||
|
|
||||||
namespace xunit.runner.wpf
|
namespace Xunit.Runner.Wpf
|
||||||
{
|
{
|
||||||
/// <summary>
|
using GalaSoft.MvvmLight.Command;
|
||||||
/// Interaction logic for MainWindow.xaml
|
using ViewModel;
|
||||||
/// </summary>
|
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
|
public static Window Instance { get; private set; }
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
Instance = this;
|
Instance = this;
|
||||||
@@ -27,6 +19,39 @@ namespace xunit.runner.wpf
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Window Instance { get; private set; }
|
protected override void OnSourceInitialized(EventArgs e)
|
||||||
|
{
|
||||||
|
base.OnSourceInitialized(e);
|
||||||
|
|
||||||
|
Storage.RestoreWindowLayout(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnClosing(CancelEventArgs e)
|
||||||
|
{
|
||||||
|
Storage.SaveWindowLayout(this);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace Xunit.Runner.Wpf.Persistence
|
||||||
|
{
|
||||||
|
internal sealed class Settings
|
||||||
|
{
|
||||||
|
private const string SettingsFileName = "settings.xml";
|
||||||
|
|
||||||
|
private const string RecentAssembliesElementName = "recent_assemblies";
|
||||||
|
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--)
|
||||||
|
{
|
||||||
|
if (StringComparer.OrdinalIgnoreCase.Equals(recentAssemblies[i], filePath))
|
||||||
|
{
|
||||||
|
recentAssemblies.RemoveAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recentAssemblies.Insert(0, filePath);
|
||||||
|
|
||||||
|
if (recentAssemblies.Count > MaxRecentAssemblies)
|
||||||
|
{
|
||||||
|
recentAssemblies.RemoveRange(MaxRecentAssemblies - 1, recentAssemblies.Count - MaxRecentAssemblies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ToggleAutoReloadAssemblies()
|
||||||
|
{
|
||||||
|
autoReloadAssemblies = !autoReloadAssemblies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetAutoReloadAssemblies() => autoReloadAssemblies;
|
||||||
|
|
||||||
|
public ImmutableArray<string> GetRecentAssemblies()
|
||||||
|
{
|
||||||
|
return this.recentAssemblies.ToImmutableArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
using (var xmlFile = Storage.CreateXmlFile(SettingsFileName))
|
||||||
|
{
|
||||||
|
var xml = new XElement(SettingsElementName, new XAttribute(VersionAttributeName, s_latestVersion));
|
||||||
|
|
||||||
|
if (this.recentAssemblies.Count > 0)
|
||||||
|
{
|
||||||
|
var recentAssembliesElement = new XElement(RecentAssembliesElementName);
|
||||||
|
foreach (var recentAssembly in this.recentAssemblies)
|
||||||
|
{
|
||||||
|
recentAssembliesElement.Add(new XElement(RecentAssemblyElementName, recentAssembly));
|
||||||
|
}
|
||||||
|
|
||||||
|
xml.Add(recentAssembliesElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
xml.Add(new XElement(AutoReloadAssembliesElementName, autoReloadAssemblies));
|
||||||
|
|
||||||
|
xml.Save(xmlFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Settings Load()
|
||||||
|
{
|
||||||
|
using (var xmlFile = Storage.OpenXmlFile(SettingsFileName))
|
||||||
|
{
|
||||||
|
var settings = new Settings();
|
||||||
|
|
||||||
|
if (xmlFile == null || xmlFile.EOF)
|
||||||
|
{
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
xmlFile.MoveToContent();
|
||||||
|
}
|
||||||
|
catch (XmlException)
|
||||||
|
{
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
var xml = XElement.Load(xmlFile);
|
||||||
|
|
||||||
|
var recentAssembliesElement = xml.Element(RecentAssembliesElementName);
|
||||||
|
if (recentAssembliesElement != null)
|
||||||
|
{
|
||||||
|
var recentAssemblyElements = recentAssembliesElement.Elements(RecentAssemblyElementName);
|
||||||
|
foreach (var recentAssemblyElement in recentAssemblyElements)
|
||||||
|
{
|
||||||
|
var filePath = (string)recentAssemblyElement;
|
||||||
|
settings.AddRecentAssembly(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Interop;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace Xunit.Runner.Wpf.Persistence
|
||||||
|
{
|
||||||
|
internal static partial class Storage
|
||||||
|
{
|
||||||
|
private static class WindowPlacement
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct RECT
|
||||||
|
{
|
||||||
|
public int left;
|
||||||
|
public int top;
|
||||||
|
public int right;
|
||||||
|
public int bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct POINT
|
||||||
|
{
|
||||||
|
public int x;
|
||||||
|
public int y;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct WINDOWPLACEMENT
|
||||||
|
{
|
||||||
|
public int length;
|
||||||
|
public int flags;
|
||||||
|
public int showCmd;
|
||||||
|
public POINT ptMinPosition;
|
||||||
|
public POINT ptMaxPosition;
|
||||||
|
public RECT rcNormalPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
private const int SW_SHOWNORMAL = 1;
|
||||||
|
private const int SW_SHOWMINIMIZED = 2;
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl);
|
||||||
|
|
||||||
|
private const string WindowPlacementElementName = "window_placement";
|
||||||
|
private const string ShowCommandElementName = "show_command";
|
||||||
|
private const string MinPositionElementName = "min_position";
|
||||||
|
private const string MaxPositionElementName = "max_position";
|
||||||
|
private const string NormalPositionElementName = "normal_position";
|
||||||
|
private const string XAttributeName = "x";
|
||||||
|
private const string YAttributeName = "y";
|
||||||
|
private const string LeftAttributeName = "left";
|
||||||
|
private const string TopAttributeName = "top";
|
||||||
|
private const string RightAttributeName = "right";
|
||||||
|
private const string BottomAttributeName = "bottom";
|
||||||
|
|
||||||
|
public static void Restore(Window window, XElement xml)
|
||||||
|
{
|
||||||
|
var placement = new WINDOWPLACEMENT();
|
||||||
|
|
||||||
|
placement.length = Marshal.SizeOf(typeof(WINDOWPLACEMENT));
|
||||||
|
placement.flags = 0;
|
||||||
|
|
||||||
|
placement.showCmd = (int)xml.Element(ShowCommandElementName);
|
||||||
|
placement.ptMinPosition.x = (int)xml.Element(MinPositionElementName).Attribute(XAttributeName);
|
||||||
|
placement.ptMinPosition.y = (int)xml.Element(MinPositionElementName).Attribute(YAttributeName);
|
||||||
|
placement.ptMaxPosition.x = (int)xml.Element(MaxPositionElementName).Attribute(XAttributeName);
|
||||||
|
placement.ptMaxPosition.y = (int)xml.Element(MaxPositionElementName).Attribute(YAttributeName);
|
||||||
|
placement.rcNormalPosition.left = (int)xml.Element(NormalPositionElementName).Attribute(LeftAttributeName);
|
||||||
|
placement.rcNormalPosition.top = (int)xml.Element(NormalPositionElementName).Attribute(TopAttributeName);
|
||||||
|
placement.rcNormalPosition.right = (int)xml.Element(NormalPositionElementName).Attribute(RightAttributeName);
|
||||||
|
placement.rcNormalPosition.bottom = (int)xml.Element(NormalPositionElementName).Attribute(BottomAttributeName);
|
||||||
|
|
||||||
|
var windowInteropHelper = new WindowInteropHelper(window);
|
||||||
|
SetWindowPlacement(windowInteropHelper.Handle, ref placement);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XElement Save(Window window)
|
||||||
|
{
|
||||||
|
var windowInteropHelper = new WindowInteropHelper(window);
|
||||||
|
var placement = new WINDOWPLACEMENT();
|
||||||
|
GetWindowPlacement(windowInteropHelper.Handle, out placement);
|
||||||
|
|
||||||
|
return
|
||||||
|
new XElement(WindowPlacementElementName,
|
||||||
|
new XElement(ShowCommandElementName, (placement.showCmd == SW_SHOWMINIMIZED ? SW_SHOWNORMAL : placement.showCmd)),
|
||||||
|
new XElement(MinPositionElementName,
|
||||||
|
new XAttribute(XAttributeName, placement.ptMinPosition.x),
|
||||||
|
new XAttribute(YAttributeName, placement.ptMinPosition.y)),
|
||||||
|
new XElement(MaxPositionElementName,
|
||||||
|
new XAttribute(XAttributeName, placement.ptMaxPosition.x),
|
||||||
|
new XAttribute(YAttributeName, placement.ptMaxPosition.y)),
|
||||||
|
new XElement(NormalPositionElementName,
|
||||||
|
new XAttribute(LeftAttributeName, placement.rcNormalPosition.left),
|
||||||
|
new XAttribute(TopAttributeName, placement.rcNormalPosition.top),
|
||||||
|
new XAttribute(RightAttributeName, placement.rcNormalPosition.right),
|
||||||
|
new XAttribute(BottomAttributeName, placement.rcNormalPosition.bottom)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.IsolatedStorage;
|
||||||
|
using System.Text;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace Xunit.Runner.Wpf.Persistence
|
||||||
|
{
|
||||||
|
internal static partial class Storage
|
||||||
|
{
|
||||||
|
private const string WindowLayoutFileName = "window_layout.xml";
|
||||||
|
|
||||||
|
private static string GetWindowLayoutFileName(Window window) => $"{window.Name}_{WindowLayoutFileName}";
|
||||||
|
|
||||||
|
private static IsolatedStorageFile GetStorageFile() => IsolatedStorageFile.GetUserStoreForDomain();
|
||||||
|
|
||||||
|
public static XmlTextReader OpenXmlFile(string fileName)
|
||||||
|
{
|
||||||
|
var storage = GetStorageFile();
|
||||||
|
if (!storage.FileExists(fileName))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileStream = storage.OpenFile(fileName, FileMode.Open, FileAccess.Read);
|
||||||
|
var reader = new XmlTextReader(fileStream);
|
||||||
|
reader.WhitespaceHandling = WhitespaceHandling.None;
|
||||||
|
|
||||||
|
return reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XmlTextWriter CreateXmlFile(string fileName)
|
||||||
|
{
|
||||||
|
var storage = GetStorageFile();
|
||||||
|
var fileStream = storage.CreateFile(fileName);
|
||||||
|
var writer = new XmlTextWriter(fileStream, Encoding.UTF8);
|
||||||
|
writer.Formatting = Formatting.Indented;
|
||||||
|
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RestoreWindowLayout(Window window)
|
||||||
|
{
|
||||||
|
if (window == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(window));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(window.Name))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Name is not set.", nameof(window));
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var windowLayoutReader = OpenXmlFile(GetWindowLayoutFileName(window)))
|
||||||
|
{
|
||||||
|
if (windowLayoutReader != null)
|
||||||
|
{
|
||||||
|
windowLayoutReader.MoveToContent();
|
||||||
|
var xml = XElement.Load(windowLayoutReader);
|
||||||
|
WindowPlacement.Restore(window, xml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SaveWindowLayout(Window window)
|
||||||
|
{
|
||||||
|
if (window == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(window));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(window.Name))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Name is not set.", nameof(window));
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var windowLayoutWriter = CreateXmlFile(GetWindowLayoutFileName(window)))
|
||||||
|
{
|
||||||
|
var xml = WindowPlacement.Save(window);
|
||||||
|
xml.Save(windowLayoutWriter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,4 @@
|
|||||||
using System.Reflection;
|
using System.Windows;
|
||||||
using System.Resources;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Windows;
|
|
||||||
|
|
||||||
// General Information about an assembly is controlled through the following
|
|
||||||
// set of attributes. Change these attribute values to modify the information
|
|
||||||
// associated with an assembly.
|
|
||||||
[assembly: AssemblyTitle("xunit.runner.wpf")]
|
|
||||||
[assembly: AssemblyDescription("")]
|
|
||||||
[assembly: AssemblyConfiguration("")]
|
|
||||||
[assembly: AssemblyCompany("")]
|
|
||||||
[assembly: AssemblyProduct("xunit.runner.wpf")]
|
|
||||||
[assembly: AssemblyCopyright("Copyright © 2015")]
|
|
||||||
[assembly: AssemblyTrademark("")]
|
|
||||||
[assembly: AssemblyCulture("")]
|
|
||||||
|
|
||||||
// Setting ComVisible to false makes the types in this assembly not visible
|
|
||||||
// to COM components. If you need to access a type in this assembly from
|
|
||||||
// COM, set the ComVisible attribute to true on that type.
|
|
||||||
[assembly: ComVisible(false)]
|
|
||||||
|
|
||||||
//In order to begin building localizable applications, set
|
//In order to begin building localizable applications, set
|
||||||
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
|
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
|
||||||
@@ -39,17 +18,3 @@ using System.Windows;
|
|||||||
//(used if a resource is not found in the page,
|
//(used if a resource is not found in the page,
|
||||||
// app, or any theme specific resource dictionaries)
|
// app, or any theme specific resource dictionaries)
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
|
||||||
// Version information for an assembly consists of the following four values:
|
|
||||||
//
|
|
||||||
// Major Version
|
|
||||||
// Minor Version
|
|
||||||
// Build Number
|
|
||||||
// Revision
|
|
||||||
//
|
|
||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
|
||||||
// by using the '*' as shown below:
|
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
|
||||||
[assembly: AssemblyVersion("1.0.0.0")]
|
|
||||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
// </auto-generated>
|
// </auto-generated>
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
namespace xunit.runner.wpf.Properties
|
namespace Xunit.Runner.Wpf.Properties {
|
||||||
{
|
using System;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||||
/// </summary>
|
/// </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.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
internal class Resources
|
internal class Resources {
|
||||||
{
|
|
||||||
|
|
||||||
private static global::System.Resources.ResourceManager resourceMan;
|
private static global::System.Resources.ResourceManager resourceMan;
|
||||||
|
|
||||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||||
|
|
||||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||||
internal Resources()
|
internal Resources() {
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the cached ResourceManager instance used by this class.
|
/// Returns the cached ResourceManager instance used by this class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
internal static global::System.Resources.ResourceManager ResourceManager
|
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||||
{
|
get {
|
||||||
get
|
if (object.ReferenceEquals(resourceMan, null)) {
|
||||||
{
|
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Xunit.Runner.Wpf.Properties.Resources", typeof(Resources).Assembly);
|
||||||
if ((resourceMan == null))
|
|
||||||
{
|
|
||||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("xunit.runner.wpf.Properties.Resources", typeof(Resources).Assembly);
|
|
||||||
resourceMan = temp;
|
resourceMan = temp;
|
||||||
}
|
}
|
||||||
return resourceMan;
|
return resourceMan;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Overrides the current thread's CurrentUICulture property for all
|
/// Overrides the current thread's CurrentUICulture property for all
|
||||||
/// resource lookups using this strongly typed resource class.
|
/// resource lookups using this strongly typed resource class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
internal static global::System.Globalization.CultureInfo Culture
|
internal static global::System.Globalization.CultureInfo Culture {
|
||||||
{
|
get {
|
||||||
get
|
|
||||||
{
|
|
||||||
return resourceCulture;
|
return resourceCulture;
|
||||||
}
|
}
|
||||||
set
|
set {
|
||||||
{
|
|
||||||
resourceCulture = value;
|
resourceCulture = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
//------------------------------------------------------------------------------
|
|
||||||
// <auto-generated>
|
|
||||||
// This code was generated by a tool.
|
|
||||||
// Runtime Version:4.0.30319.42000
|
|
||||||
//
|
|
||||||
// Changes to this file may cause incorrect behavior and will be lost if
|
|
||||||
// the code is regenerated.
|
|
||||||
// </auto-generated>
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
|
|
||||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
|
||||||
|
|
||||||
public static Settings Default
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return defaultInstance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
|
||||||
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
|
|
||||||
<Profiles>
|
|
||||||
<Profile Name="(Default)" />
|
|
||||||
</Profiles>
|
|
||||||
<Settings />
|
|
||||||
</SettingsFile>
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace xunit.runner.wpf.ViewModel
|
namespace Xunit.Runner.Wpf.ViewModel
|
||||||
{
|
{
|
||||||
public class AssemblyAndConfigFile
|
public class AssemblyAndConfigFile
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,75 +1,178 @@
|
|||||||
using GalaSoft.MvvmLight;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Windows;
|
|
||||||
using GalaSoft.MvvmLight.CommandWpf;
|
|
||||||
using Microsoft.Win32;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Diagnostics;
|
using System.Windows;
|
||||||
using System.IO.Pipes;
|
using System.Windows.Input;
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using xunit.runner.data;
|
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
using GalaSoft.MvvmLight;
|
||||||
|
using GalaSoft.MvvmLight.Command;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
using Microsoft.WindowsAPICodePack.Taskbar;
|
||||||
|
using Xunit.Runner.Data;
|
||||||
|
using Xunit.Runner.Wpf.Persistence;
|
||||||
|
|
||||||
namespace xunit.runner.wpf.ViewModel
|
namespace Xunit.Runner.Wpf.ViewModel
|
||||||
{
|
{
|
||||||
public class MainViewModel : ViewModelBase
|
public class MainViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
|
private readonly Settings settings;
|
||||||
|
|
||||||
private readonly ITestUtil testUtil;
|
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 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 filterCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
private CancellationTokenSource cancellationTokenSource;
|
private CancellationTokenSource cancellationTokenSource;
|
||||||
private bool isBusy;
|
private bool isBusy;
|
||||||
private SearchQuery searchQuery = new SearchQuery();
|
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> testsToRun;
|
||||||
|
|
||||||
|
public ICommand ExitCommand { get; }
|
||||||
|
public ICommand WindowLoadedCommand { get; }
|
||||||
|
public RelayCommand<CancelEventArgs> WindowClosingCommand { 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; }
|
||||||
|
|
||||||
public MainViewModel()
|
public MainViewModel()
|
||||||
{
|
{
|
||||||
|
this.settings = Settings.Load();
|
||||||
|
|
||||||
if (IsInDesignMode)
|
if (IsInDesignMode)
|
||||||
{
|
{
|
||||||
this.Assemblies.Add(new TestAssemblyViewModel(new AssemblyAndConfigFile(@"C:\Code\TestAssembly.dll", null)));
|
this.Assemblies.Add(new TestAssemblyViewModel(new AssemblyAndConfigFile(@"C:\Code\TestAssembly.dll", null)));
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandBindings = CreateCommandBindings();
|
CommandBindings = CreateCommandBindings();
|
||||||
this.testUtil = new xunit.runner.wpf.Impl.RemoteTestUtil(Dispatcher.CurrentDispatcher);
|
|
||||||
this.MethodsCaption = "Methods (0)";
|
|
||||||
|
|
||||||
TestCases = new FilteredCollectionView<TestCaseViewModel, SearchQuery>(
|
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>(
|
||||||
allTestCases, TestCaseMatches, searchQuery, TestComparer.Instance);
|
allTestCases, TestCaseMatches, searchQuery, TestComparer.Instance);
|
||||||
|
|
||||||
this.TestCases.CollectionChanged += TestCases_CollectionChanged;
|
this.FilteredTestCases.CollectionChanged += TestCases_CollectionChanged;
|
||||||
|
|
||||||
|
this.ExitCommand = new RelayCommand(OnExecuteExit);
|
||||||
this.WindowLoadedCommand = new RelayCommand(OnExecuteWindowLoaded);
|
this.WindowLoadedCommand = new RelayCommand(OnExecuteWindowLoaded);
|
||||||
this.RunCommand = new RelayCommand(OnExecuteRun, CanExecuteRun);
|
this.WindowClosingCommand = new RelayCommand<CancelEventArgs>(OnExecuteWindowClosing);
|
||||||
|
this.RunAllCommand = new RelayCommand(OnExecuteRunAll, CanExecuteRunAll);
|
||||||
|
this.RunSelectedCommand = new RelayCommand(OnExecuteRunSelected, CanExecuteRunSelected);
|
||||||
this.CancelCommand = new RelayCommand(OnExecuteCancel, CanExecuteCancel);
|
this.CancelCommand = new RelayCommand(OnExecuteCancel, CanExecuteCancel);
|
||||||
|
this.TraitCheckedChangedCommand = new RelayCommand<TraitViewModel>(OnExecuteTraitCheckedChanged);
|
||||||
|
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);
|
||||||
|
this.AutoReloadAssembliesCommand = new RelayCommand(OnToggleAutoReloadAssemblies);
|
||||||
|
|
||||||
|
RebuildRecentAssembliesMenu();
|
||||||
|
AutoReloadAssemblies = this.settings.GetAutoReloadAssemblies();
|
||||||
|
UpdateAutoReloadStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RebuildRecentAssembliesMenu()
|
||||||
|
{
|
||||||
|
this.RecentAssemblies.Clear();
|
||||||
|
|
||||||
|
foreach (var recentAssembly in this.settings.GetRecentAssemblies())
|
||||||
|
{
|
||||||
|
var viewModel = new RecentAssemblyViewModel(recentAssembly, new RelayCommand<RecentAssemblyViewModel>(this.OnExecuteRecentAssembly));
|
||||||
|
this.RecentAssemblies.Add(viewModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnExecuteRecentAssembly(RecentAssemblyViewModel recentAssembly)
|
||||||
|
{
|
||||||
|
var assemblyAndConfig = new AssemblyAndConfigFile(recentAssembly.FilePath, configFileName: null);
|
||||||
|
|
||||||
|
await this.AddAssemblies(new[] { assemblyAndConfig });
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TestCaseMatches(TestCaseViewModel testCase, SearchQuery searchQuery)
|
private static bool TestCaseMatches(TestCaseViewModel testCase, SearchQuery searchQuery)
|
||||||
{
|
{
|
||||||
if (!testCase.DisplayName.Contains(searchQuery.SearchString))
|
if (testCase.DisplayName.IndexOf(searchQuery.SearchString, StringComparison.CurrentCultureIgnoreCase) < 0)
|
||||||
{
|
{
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var noFilter = !(searchQuery.FilterRunningTests | searchQuery.FilterFailedTests | searchQuery.FilterPassedTests | searchQuery.FilterSkippedTests);
|
||||||
|
|
||||||
switch (testCase.State)
|
switch (testCase.State)
|
||||||
{
|
{
|
||||||
|
case TestState.Running:
|
||||||
|
return noFilter || searchQuery.FilterRunningTests;
|
||||||
|
|
||||||
case TestState.Passed:
|
case TestState.Passed:
|
||||||
return searchQuery.IncludePassedTests;
|
return noFilter || searchQuery.FilterPassedTests;
|
||||||
|
|
||||||
case TestState.Skipped:
|
case TestState.Skipped:
|
||||||
return searchQuery.IncludeSkippedTests;
|
return noFilter || searchQuery.FilterSkippedTests;
|
||||||
|
|
||||||
case TestState.Failed:
|
case TestState.Failed:
|
||||||
return searchQuery.IncludeFailedTests;
|
return noFilter || searchQuery.FilterFailedTests;
|
||||||
|
|
||||||
case TestState.NotRun:
|
case TestState.NotRun:
|
||||||
return true;
|
return noFilter;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Debug.Assert(false, "What state is this test case in?");
|
Debug.Assert(false, "What state is this test case in?");
|
||||||
@@ -79,29 +182,97 @@ namespace xunit.runner.wpf.ViewModel
|
|||||||
|
|
||||||
private void TestCases_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
private void TestCases_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
MethodsCaption = $"Methods ({TestCases.Count})";
|
UpdateTestCaseInfo(useSelected: false);
|
||||||
MaximumProgress = TestCases.Count;
|
ClearSelectionFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICommand ExitCommand { get; } = new RelayCommand(OnExecuteExit);
|
private void ClearSelectionFlags()
|
||||||
public ICommand WindowLoadedCommand { get; }
|
|
||||||
public RelayCommand RunCommand { get; }
|
|
||||||
public RelayCommand CancelCommand { get; }
|
|
||||||
|
|
||||||
public CommandBindingCollection CommandBindings { get; }
|
|
||||||
|
|
||||||
private string methodsCaption;
|
|
||||||
public string MethodsCaption
|
|
||||||
{
|
{
|
||||||
get { return methodsCaption; }
|
foreach (var test in this.allTestCases)
|
||||||
private set { Set(ref methodsCaption, value); }
|
{
|
||||||
|
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
|
||||||
|
{
|
||||||
|
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;
|
private int testsCompleted = 0;
|
||||||
public int TestsCompleted
|
public int TestsCompleted
|
||||||
{
|
{
|
||||||
get { return testsCompleted; }
|
get { return testsCompleted; }
|
||||||
set { Set(ref testsCompleted, value); }
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TaskbarProgressBarState GetTaskBarState()
|
||||||
|
{
|
||||||
|
switch (this.CurrentRunState)
|
||||||
|
{
|
||||||
|
case TestState.Failed:
|
||||||
|
return TaskbarProgressBarState.Error;
|
||||||
|
case TestState.Skipped:
|
||||||
|
return TaskbarProgressBarState.Paused;
|
||||||
|
default:
|
||||||
|
return TaskbarProgressBarState.Normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int testsRunning = 0;
|
||||||
|
public int TestsRunning
|
||||||
|
{
|
||||||
|
get { return testsRunning; }
|
||||||
|
set { Set(ref testsRunning, value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
private int testsPassed = 0;
|
private int testsPassed = 0;
|
||||||
@@ -129,14 +300,24 @@ namespace xunit.runner.wpf.ViewModel
|
|||||||
public int MaximumProgress
|
public int MaximumProgress
|
||||||
{
|
{
|
||||||
get { return maximumProgress; }
|
get { return maximumProgress; }
|
||||||
set { Set(ref maximumProgress, value); }
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Set(ref maximumProgress, value);
|
||||||
|
UpdateProgress();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TestState currentRunState;
|
private TestState currentRunState;
|
||||||
public TestState CurrentRunState
|
public TestState CurrentRunState
|
||||||
{
|
{
|
||||||
get { return currentRunState; }
|
get { return currentRunState; }
|
||||||
set { Set(ref currentRunState, value); }
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Set(ref currentRunState, value);
|
||||||
|
UpdateProgress();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string output = string.Empty;
|
private string output = string.Empty;
|
||||||
@@ -169,7 +350,7 @@ namespace xunit.runner.wpf.ViewModel
|
|||||||
.ContinueWith(
|
.ContinueWith(
|
||||||
x =>
|
x =>
|
||||||
{
|
{
|
||||||
TestCases.FilterArgument = searchQuery;
|
FilteredTestCases.FilterArgument = searchQuery;
|
||||||
},
|
},
|
||||||
token,
|
token,
|
||||||
TaskContinuationOptions.None,
|
TaskContinuationOptions.None,
|
||||||
@@ -187,14 +368,12 @@ namespace xunit.runner.wpf.ViewModel
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObservableCollection<TestAssemblyViewModel> Assemblies { get; } = new ObservableCollection<TestAssemblyViewModel>();
|
|
||||||
public FilteredCollectionView<TestCaseViewModel, SearchQuery> TestCases { get; }
|
|
||||||
|
|
||||||
private async void OnExecuteOpen(object sender, ExecutedRoutedEventArgs e)
|
private async void OnExecuteOpen(object sender, ExecutedRoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var fileDialog = new OpenFileDialog
|
var fileDialog = new OpenFileDialog
|
||||||
{
|
{
|
||||||
Filter = "Unit Test Assemblies|*.dll",
|
Filter = "Unit Test Assemblies|*.dll",
|
||||||
|
Multiselect = true
|
||||||
};
|
};
|
||||||
|
|
||||||
if (fileDialog.ShowDialog(Application.Current.MainWindow) != true)
|
if (fileDialog.ShowDialog(Application.Current.MainWindow) != true)
|
||||||
@@ -202,8 +381,8 @@ namespace xunit.runner.wpf.ViewModel
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileName = fileDialog.FileName;
|
var assemblies = fileDialog.FileNames.Select(x => new AssemblyAndConfigFile(x, configFileName: null));
|
||||||
await AddAssemblies(new[] { new AssemblyAndConfigFile(fileName, configFileName: null) });
|
await AddAssemblies(assemblies);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AddAssemblies(IEnumerable<AssemblyAndConfigFile> assemblies)
|
private async Task AddAssemblies(IEnumerable<AssemblyAndConfigFile> assemblies)
|
||||||
@@ -213,28 +392,126 @@ namespace xunit.runner.wpf.ViewModel
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var loadingDialog = new LoadingDialog { Owner = MainWindow.Instance };
|
var newAssemblyViewModels = new List<TestAssemblyViewModel>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ExecuteTestSessionOperation(() =>
|
await this.ExecuteTestSessionOperation(() =>
|
||||||
{
|
{
|
||||||
var testSessionList = new List<ITestSession>();
|
var taskList = new List<Task>();
|
||||||
foreach (var assembly in assemblies)
|
foreach (var assembly in assemblies)
|
||||||
{
|
{
|
||||||
var assemblyPath = assembly.AssemblyFileName;
|
taskList.Add(this.testUtil.Discover(assembly.AssemblyFileName, this.OnTestsDiscovered, this.cancellationTokenSource.Token));
|
||||||
var session = this.testUtil.Discover(assemblyPath, cancellationTokenSource.Token);
|
|
||||||
session.TestDiscovered += OnTestDiscovered;
|
|
||||||
|
|
||||||
testSessionList.Add(session);
|
var assemblyViewModel = new TestAssemblyViewModel(assembly);
|
||||||
Assemblies.Add(new TestAssemblyViewModel(assembly));
|
|
||||||
|
newAssemblyViewModels.Add(assemblyViewModel);
|
||||||
|
this.Assemblies.Add(assemblyViewModel);
|
||||||
|
this.settings.AddRecentAssembly(assembly.AssemblyFileName);
|
||||||
|
|
||||||
|
assemblyViewModel.State = AssemblyState.Loading;
|
||||||
}
|
}
|
||||||
|
|
||||||
return testSessionList;
|
return taskList;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
loadingDialog.Close();
|
foreach (var assemblyViewModel in newAssemblyViewModels)
|
||||||
|
{
|
||||||
|
assemblyViewModel.State = AssemblyState.Ready;
|
||||||
|
assemblyWatcher.AddAssembly(assemblyViewModel.FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
RebuildRecentAssembliesMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
await ExecuteTestSessionOperation(() =>
|
||||||
|
{
|
||||||
|
var taskList = new List<Task>();
|
||||||
|
foreach (var assemblyViewModel in assemblies)
|
||||||
|
{
|
||||||
|
assemblyViewModel.State = AssemblyState.Loading;
|
||||||
|
|
||||||
|
var assemblyFileName = assemblyViewModel.FileName;
|
||||||
|
RemoveAssemblyTestCases(assemblyFileName);
|
||||||
|
|
||||||
|
taskList.Add(this.testUtil.Discover(assemblyFileName, OnTestsDiscovered, cancellationTokenSource.Token));
|
||||||
|
}
|
||||||
|
|
||||||
|
return taskList;
|
||||||
|
});
|
||||||
|
|
||||||
|
RebuildTraits();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
foreach (var assemblyViewModel in assemblies)
|
||||||
|
{
|
||||||
|
assemblyViewModel.State = AssemblyState.Ready;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveAssemblies(IEnumerable<TestAssemblyViewModel> assemblies)
|
||||||
|
{
|
||||||
|
foreach (var assembly in assemblies.ToList())
|
||||||
|
{
|
||||||
|
assemblyWatcher.RemoveAssembly(assembly.FileName);
|
||||||
|
RemoveAssemblyTestCases(assembly.FileName);
|
||||||
|
Assemblies.Remove(assembly);
|
||||||
|
}
|
||||||
|
|
||||||
|
RebuildTraits();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveAssemblyTestCases(string assemblyPath)
|
||||||
|
{
|
||||||
|
var i = 0;
|
||||||
|
while (i < this.allTestCases.Count)
|
||||||
|
{
|
||||||
|
if (string.Compare(this.allTestCases[i].AssemblyFileName, assemblyPath, StringComparison.OrdinalIgnoreCase) == 0)
|
||||||
|
{
|
||||||
|
this.allTestCaseUniqueIDs.Remove(this.allTestCases[i].UniqueID);
|
||||||
|
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.AddRange(testCase.Traits);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +521,8 @@ namespace xunit.runner.wpf.ViewModel
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
isBusy = value;
|
isBusy = value;
|
||||||
RunCommand.RaiseCanExecuteChanged();
|
RunAllCommand.RaiseCanExecuteChanged();
|
||||||
|
RunSelectedCommand.RaiseCanExecuteChanged();
|
||||||
CancelCommand.RaiseCanExecuteChanged();
|
CancelCommand.RaiseCanExecuteChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,6 +537,11 @@ namespace xunit.runner.wpf.ViewModel
|
|||||||
await AddAssemblies(ParseCommandLine(Environment.GetCommandLineArgs().Skip(1)));
|
await AddAssemblies(ParseCommandLine(Environment.GetCommandLineArgs().Skip(1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnExecuteWindowClosing(CancelEventArgs e)
|
||||||
|
{
|
||||||
|
this.settings.Save();
|
||||||
|
}
|
||||||
|
|
||||||
private IEnumerable<AssemblyAndConfigFile> ParseCommandLine(IEnumerable<string> enumerable)
|
private IEnumerable<AssemblyAndConfigFile> ParseCommandLine(IEnumerable<string> enumerable)
|
||||||
{
|
{
|
||||||
while (enumerable.Any())
|
while (enumerable.Any())
|
||||||
@@ -281,60 +564,96 @@ namespace xunit.runner.wpf.ViewModel
|
|||||||
=> (fileName?.EndsWith(".config", StringComparison.OrdinalIgnoreCase) ?? false) ||
|
=> (fileName?.EndsWith(".config", StringComparison.OrdinalIgnoreCase) ?? false) ||
|
||||||
(fileName?.EndsWith(".json", StringComparison.OrdinalIgnoreCase) ?? false);
|
(fileName?.EndsWith(".json", StringComparison.OrdinalIgnoreCase) ?? false);
|
||||||
|
|
||||||
private bool CanExecuteRun()
|
private bool CanExecuteRunAll()
|
||||||
=> !IsBusy && TestCases.Any();
|
=> !IsBusy && FilteredTestCases.Any();
|
||||||
|
|
||||||
private async void OnExecuteRun()
|
private bool CanExecuteRunSelected()
|
||||||
|
=> !IsBusy && SelectedTestCase != null;
|
||||||
|
|
||||||
|
private async void OnExecuteRunAll()
|
||||||
{
|
{
|
||||||
await ExecuteTestSessionOperation(RunTests);
|
Debug.Assert(this.testsToRun == null);
|
||||||
|
UpdateTestCaseInfo(useSelected: false);
|
||||||
|
|
||||||
|
await ExecuteTestSessionOperation(RunFilteredTests);
|
||||||
|
|
||||||
|
this.testsToRun = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ITestSession> 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.isBusy);
|
||||||
Debug.Assert(this.cancellationTokenSource != null);
|
Debug.Assert(this.cancellationTokenSource != null);
|
||||||
|
Debug.Assert(this.testsToRun == null);
|
||||||
|
|
||||||
TestsCompleted = 0;
|
TestsCompleted = 0;
|
||||||
|
TestsRunning = 0;
|
||||||
TestsPassed = 0;
|
TestsPassed = 0;
|
||||||
TestsFailed = 0;
|
TestsFailed = 0;
|
||||||
TestsSkipped = 0;
|
TestsSkipped = 0;
|
||||||
CurrentRunState = TestState.NotRun;
|
CurrentRunState = TestState.NotRun;
|
||||||
Output = string.Empty;
|
Output = string.Empty;
|
||||||
|
|
||||||
foreach (var tc in TestCases)
|
this.testsToRun = tests;
|
||||||
|
|
||||||
|
foreach (var tc in this.testsToRun)
|
||||||
{
|
{
|
||||||
tc.State = TestState.NotRun;
|
tc.State = TestState.NotRun;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Need a way to filter based on traits
|
var runAll = this.testsToRun.Count == this.allTestCases.Count;
|
||||||
|
var testSessionList = new List<Task>();
|
||||||
|
|
||||||
var runAll = TestCases.Count == this.allTestCases.Count;
|
foreach (var assemblyFileName in this.testsToRun.Select(x => x.AssemblyFileName).Distinct())
|
||||||
var testSessionList = new List<ITestSession>();
|
|
||||||
|
|
||||||
foreach (var assemblyPath in TestCases.Select(x => x.AssemblyFileName).Distinct())
|
|
||||||
{
|
{
|
||||||
ITestRunSession session;
|
Task task;
|
||||||
if (runAll)
|
if (runAll)
|
||||||
{
|
{
|
||||||
session = this.testUtil.RunAll(assemblyPath, this.cancellationTokenSource.Token);
|
task = this.testUtil.RunAll(assemblyFileName, OnTestStateChange, this.cancellationTokenSource.Token);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var testCaseDisplayNames = TestCases
|
var builder = ImmutableArray.CreateBuilder<string>();
|
||||||
.Where(x => x.AssemblyFileName == assemblyPath)
|
|
||||||
.Select(x => x.DisplayName)
|
foreach (var testCase in this.testsToRun)
|
||||||
.ToImmutableArray();
|
{
|
||||||
session = this.testUtil.RunSpecific(assemblyPath, testCaseDisplayNames, this.cancellationTokenSource.Token);
|
if (testCase.AssemblyFileName == assemblyFileName)
|
||||||
|
{
|
||||||
|
builder.Add(testCase.UniqueID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task = this.testUtil.RunSpecific(assemblyFileName, builder.ToImmutable(), OnTestStateChange, this.cancellationTokenSource.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
session.TestFinished += OnTestFinished;
|
testSessionList.Add(task);
|
||||||
testSessionList.Add(session);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return testSessionList;
|
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.IsBusy);
|
||||||
Debug.Assert(this.cancellationTokenSource == null);
|
Debug.Assert(this.cancellationTokenSource == null);
|
||||||
@@ -344,8 +663,8 @@ namespace xunit.runner.wpf.ViewModel
|
|||||||
this.IsBusy = true;
|
this.IsBusy = true;
|
||||||
this.cancellationTokenSource = new CancellationTokenSource();
|
this.cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
var testSessionList = operation();
|
var taskList = operation();
|
||||||
await Task.WhenAll(testSessionList.Select(x => x.Task));
|
await Task.WhenAll(taskList);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -359,35 +678,95 @@ namespace xunit.runner.wpf.ViewModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTestDiscovered(object sender, TestCaseDataEventArgs e)
|
private void OnTestsDiscovered(IEnumerable<TestCaseData> testCases)
|
||||||
{
|
{
|
||||||
var t = e.TestCaseData;
|
var traitWorkerList = new List<TraitViewModel>();
|
||||||
allTestCases.Add(new TestCaseViewModel(t.SerializedForm, t.DisplayName, t.AssemblyPath));
|
|
||||||
|
foreach (var testCase in testCases)
|
||||||
|
{
|
||||||
|
if (this.allTestCaseUniqueIDs.Contains(testCase.UniqueID))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
traitWorkerList.Clear();
|
||||||
|
|
||||||
|
// Get or create traits.
|
||||||
|
if (testCase.TraitMap?.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var kvp in testCase.TraitMap)
|
||||||
|
{
|
||||||
|
var name = kvp.Key;
|
||||||
|
var values = kvp.Value;
|
||||||
|
|
||||||
|
var parentTraitViewModel = traitCollectionView.GetOrAdd(name);
|
||||||
|
|
||||||
|
foreach (var value in values)
|
||||||
|
{
|
||||||
|
var traitViewModel = parentTraitViewModel.GetOrAdd(value);
|
||||||
|
traitWorkerList.Add(traitViewModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testCaseViewModel = new TestCaseViewModel(
|
||||||
|
testCase.DisplayName,
|
||||||
|
testCase.UniqueID,
|
||||||
|
testCase.SkipReason,
|
||||||
|
testCase.AssemblyPath,
|
||||||
|
traitWorkerList);
|
||||||
|
|
||||||
|
if (testCaseViewModel.State == TestState.Skipped)
|
||||||
|
{
|
||||||
|
TestsSkipped++;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.allTestCaseUniqueIDs.Add(testCase.UniqueID);
|
||||||
|
this.allTestCases.Add(testCaseViewModel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTestFinished(object sender, TestResultDataEventArgs e)
|
private void OnTestStateChange(IEnumerable<TestResultData> testResultData)
|
||||||
{
|
{
|
||||||
var testCase = TestCases.Single(x => x.DisplayName == e.TestCaseDisplayName);
|
Debug.Assert(this.testsToRun != null);
|
||||||
testCase.State = e.TestState;
|
|
||||||
|
|
||||||
TestsCompleted++;
|
foreach (var result in testResultData)
|
||||||
switch (e.TestState)
|
|
||||||
{
|
{
|
||||||
case TestState.Passed:
|
var testCase = this.testsToRun.Single(x => x.UniqueID == result.TestCaseUniqueID);
|
||||||
TestsPassed++;
|
testCase.State = result.TestState;
|
||||||
break;
|
|
||||||
case TestState.Failed:
|
|
||||||
TestsFailed++;
|
|
||||||
Output = Output + e.Output;
|
|
||||||
break;
|
|
||||||
case TestState.Skipped:
|
|
||||||
TestsSkipped++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.TestState > CurrentRunState)
|
if (result.TestState == TestState.Running)
|
||||||
{
|
{
|
||||||
CurrentRunState = e.TestState;
|
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)
|
||||||
|
{
|
||||||
|
CurrentRunState = result.TestState;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,36 +781,115 @@ namespace xunit.runner.wpf.ViewModel
|
|||||||
this.cancellationTokenSource.Cancel();
|
this.cancellationTokenSource.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IncludePassedTests
|
private void OnExecuteTraitCheckedChanged(TraitViewModel trait)
|
||||||
{
|
{
|
||||||
get { return searchQuery.IncludePassedTests; }
|
this.searchQuery.TraitSet = this.traitCollectionView.GetCheckedTraits();
|
||||||
|
FilterAfterDelay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnExecuteTraitsClear()
|
||||||
|
{
|
||||||
|
foreach (var cur in this.traitCollectionView.Collection)
|
||||||
|
{
|
||||||
|
cur.IsChecked = 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());
|
||||||
|
}
|
||||||
|
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
|
set
|
||||||
{
|
{
|
||||||
if (Set(ref searchQuery.IncludePassedTests, value))
|
if (Set(ref searchQuery.FilterRunningTests, value))
|
||||||
{
|
{
|
||||||
FilterAfterDelay();
|
FilterAfterDelay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IncludeFailedTests
|
public bool FilterPassedTests
|
||||||
{
|
{
|
||||||
get { return searchQuery.IncludeFailedTests; }
|
get { return searchQuery.FilterPassedTests; }
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (Set(ref searchQuery.IncludeFailedTests, value))
|
if (Set(ref searchQuery.FilterPassedTests, value))
|
||||||
{
|
{
|
||||||
FilterAfterDelay();
|
FilterAfterDelay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IncludeSkippedTests
|
public bool FilterFailedTests
|
||||||
{
|
{
|
||||||
get { return searchQuery.IncludeSkippedTests; }
|
get { return searchQuery.FilterFailedTests; }
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (Set(ref searchQuery.IncludeSkippedTests, value))
|
if (Set(ref searchQuery.FilterFailedTests, value))
|
||||||
|
{
|
||||||
|
FilterAfterDelay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FilterSkippedTests
|
||||||
|
{
|
||||||
|
get { return searchQuery.FilterSkippedTests; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Set(ref searchQuery.FilterSkippedTests, value))
|
||||||
{
|
{
|
||||||
FilterAfterDelay();
|
FilterAfterDelay();
|
||||||
}
|
}
|
||||||
@@ -444,7 +902,17 @@ namespace xunit.runner.wpf.ViewModel
|
|||||||
public static TestComparer Instance { get; } = new TestComparer();
|
public static TestComparer Instance { get; } = new TestComparer();
|
||||||
|
|
||||||
public int Compare(TestCaseViewModel x, TestCaseViewModel y)
|
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() { }
|
private TestComparer() { }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Xunit.Runner.Wpf.ViewModel
|
||||||
|
{
|
||||||
|
public class RecentAssemblyViewModel
|
||||||
|
{
|
||||||
|
public string FilePath { get; }
|
||||||
|
public ICommand Command { get; }
|
||||||
|
|
||||||
|
public RecentAssemblyViewModel(string filePath, ICommand command)
|
||||||
|
{
|
||||||
|
this.FilePath = filePath;
|
||||||
|
this.Command = command;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,14 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace xunit.runner.wpf.ViewModel
|
namespace Xunit.Runner.Wpf.ViewModel
|
||||||
{
|
{
|
||||||
public class SearchQuery
|
public class SearchQuery
|
||||||
{
|
{
|
||||||
public bool IncludeFailedTests = true;
|
public bool FilterRunningTests = false;
|
||||||
public bool IncludePassedTests = true;
|
public bool FilterFailedTests = false;
|
||||||
public bool IncludeSkippedTests = true;
|
public bool FilterPassedTests = false;
|
||||||
|
public bool FilterSkippedTests = false;
|
||||||
public string SearchString = string.Empty;
|
public string SearchString = string.Empty;
|
||||||
|
public ISet<TraitViewModel> TraitSet = new HashSet<TraitViewModel>(TraitViewModel.EqualityComparer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,39 @@
|
|||||||
using GalaSoft.MvvmLight;
|
using System.IO;
|
||||||
using System;
|
using GalaSoft.MvvmLight;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace xunit.runner.wpf.ViewModel
|
namespace Xunit.Runner.Wpf.ViewModel
|
||||||
{
|
{
|
||||||
public class TestAssemblyViewModel : ViewModelBase
|
public class TestAssemblyViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
private readonly AssemblyAndConfigFile assembly;
|
private readonly AssemblyAndConfigFile _assembly;
|
||||||
|
private bool _isSelected;
|
||||||
|
private AssemblyState _state;
|
||||||
|
|
||||||
public TestAssemblyViewModel(AssemblyAndConfigFile assembly)
|
public TestAssemblyViewModel(AssemblyAndConfigFile assembly)
|
||||||
{
|
{
|
||||||
this.assembly = assembly;
|
_assembly = assembly;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string FileName => assembly.AssemblyFileName;
|
public string FileName => _assembly.AssemblyFileName;
|
||||||
public string ConfigFileName => Path.GetFileNameWithoutExtension(assembly.ConfigFileName);
|
public string ConfigFileName => Path.GetFileNameWithoutExtension(_assembly.ConfigFileName);
|
||||||
public string DisplayName => Path.GetFileNameWithoutExtension(assembly.AssemblyFileName);
|
public string DisplayName => Path.GetFileNameWithoutExtension(_assembly.AssemblyFileName);
|
||||||
|
|
||||||
|
public bool IsSelected
|
||||||
|
{
|
||||||
|
get { return _isSelected; }
|
||||||
|
set { Set(ref _isSelected, value, nameof(IsSelected)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public AssemblyState State
|
||||||
|
{
|
||||||
|
get { return _state; }
|
||||||
|
set { Set(ref _state, value, nameof(State)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AssemblyState
|
||||||
|
{
|
||||||
|
Ready,
|
||||||
|
Loading
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,41 @@
|
|||||||
using GalaSoft.MvvmLight;
|
using System.Collections.Generic;
|
||||||
using System;
|
using System.Collections.Immutable;
|
||||||
using System.Collections.Generic;
|
using GalaSoft.MvvmLight;
|
||||||
using System.IO;
|
using Xunit.Runner.Data;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using xunit.runner.data;
|
|
||||||
|
|
||||||
namespace xunit.runner.wpf.ViewModel
|
namespace Xunit.Runner.Wpf.ViewModel
|
||||||
{
|
{
|
||||||
public class TestCaseViewModel : ViewModelBase
|
public class TestCaseViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
public TestCaseViewModel(string testCase, string displayName, string assemblyFileName)
|
private TestState _state = TestState.NotRun;
|
||||||
{
|
|
||||||
this.TestCase = testCase;
|
|
||||||
this.DisplayName = displayName;
|
|
||||||
this.AssemblyFileName = assemblyFileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string DisplayName { get; }
|
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);
|
||||||
|
|
||||||
private TestState state = TestState.NotRun;
|
|
||||||
public TestState State
|
public TestState State
|
||||||
{
|
{
|
||||||
get { return state; }
|
get { return _state; }
|
||||||
set { Set(ref state, value); }
|
set { Set(ref _state, value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public string AssemblyFileName { get; }
|
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();
|
||||||
|
|
||||||
public string TestCase { get; }
|
if (!string.IsNullOrEmpty(skipReason))
|
||||||
|
{
|
||||||
|
_state = TestState.Skipped;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Xunit.Runner.Wpf.ViewModel
|
||||||
|
{
|
||||||
|
public sealed partial class TraitCollectionView
|
||||||
|
{
|
||||||
|
public ObservableCollection<TraitViewModel> Collection { get; } = new ObservableCollection<TraitViewModel>();
|
||||||
|
|
||||||
|
public void AddRange(IEnumerable<TraitViewModel> traits)
|
||||||
|
{
|
||||||
|
foreach (var trait in traits)
|
||||||
|
{
|
||||||
|
var index = Collection.BinarySearch(trait, TraitViewModel.Comparer);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
Collection.Insert(~index, trait);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This trait already exists, add more values.
|
||||||
|
var originalTrait = Collection[index];
|
||||||
|
originalTrait.AddValues(trait.Children.Select(x => x.Text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TraitViewModel GetOrAdd(string text)
|
||||||
|
{
|
||||||
|
var index = this.Collection.BinarySearch(text, StringComparer.Ordinal, vm => vm.Text);
|
||||||
|
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
var viewModel = new TraitViewModel(text);
|
||||||
|
this.Collection.Insert(~index, viewModel);
|
||||||
|
return viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.Collection[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISet<TraitViewModel> GetCheckedTraits()
|
||||||
|
{
|
||||||
|
return new HashSet<TraitViewModel>(
|
||||||
|
Collection.SelectMany(x => x.Children).Where(x => x.IsChecked == true),
|
||||||
|
comparer: TraitViewModel.EqualityComparer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Xunit.Runner.Wpf.ViewModel
|
||||||
|
{
|
||||||
|
public partial class TraitViewModel
|
||||||
|
{
|
||||||
|
private static readonly TraitViewModelComparer _comparer = new TraitViewModelComparer();
|
||||||
|
|
||||||
|
internal static IComparer<TraitViewModel> Comparer => _comparer;
|
||||||
|
internal static IEqualityComparer<TraitViewModel> EqualityComparer => _comparer;
|
||||||
|
|
||||||
|
private class TraitViewModelComparer : IEqualityComparer<TraitViewModel>, IComparer<TraitViewModel>
|
||||||
|
{
|
||||||
|
public int Compare(TraitViewModel x, TraitViewModel y) => StringComparer.Ordinal.Compare(x.Text, y.Text);
|
||||||
|
public bool Equals(TraitViewModel x, TraitViewModel y) => StringComparer.Ordinal.Equals(x.Text, y.Text);
|
||||||
|
public int GetHashCode(TraitViewModel obj) => obj.Text.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using GalaSoft.MvvmLight;
|
||||||
|
|
||||||
|
namespace Xunit.Runner.Wpf.ViewModel
|
||||||
|
{
|
||||||
|
public partial class TraitViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly TraitViewModel _parent;
|
||||||
|
private bool? _isChecked;
|
||||||
|
private bool _isExpanded;
|
||||||
|
private string _text;
|
||||||
|
|
||||||
|
public ObservableCollection<TraitViewModel> Children { get; }
|
||||||
|
|
||||||
|
public TraitViewModel(string text)
|
||||||
|
: this(null, text)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private TraitViewModel(TraitViewModel parent, string text)
|
||||||
|
{
|
||||||
|
this._parent = parent;
|
||||||
|
this._isChecked = false;
|
||||||
|
this._isExpanded = true;
|
||||||
|
this._text = text;
|
||||||
|
this.Children = new ObservableCollection<TraitViewModel>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetIsChecked(bool? value, bool updateChildren, bool updateParent)
|
||||||
|
{
|
||||||
|
if (value == this._isChecked)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._isChecked = value;
|
||||||
|
|
||||||
|
if (updateChildren && value != null)
|
||||||
|
{
|
||||||
|
foreach (var child in this.Children)
|
||||||
|
{
|
||||||
|
child.SetIsChecked(value, updateChildren: true, updateParent: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateParent && _parent != null)
|
||||||
|
{
|
||||||
|
_parent.VerifyCheckState();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.RaisePropertyChanged(nameof(IsChecked));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void VerifyCheckState()
|
||||||
|
{
|
||||||
|
bool? state = null;
|
||||||
|
var isFirst = true;
|
||||||
|
|
||||||
|
foreach (var child in this.Children)
|
||||||
|
{
|
||||||
|
if (isFirst)
|
||||||
|
{
|
||||||
|
state = child.IsChecked;
|
||||||
|
isFirst = false;
|
||||||
|
}
|
||||||
|
else if (state != child.IsChecked)
|
||||||
|
{
|
||||||
|
state = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.SetIsChecked(state, updateChildren: false, updateParent: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddValues(IEnumerable<string> values)
|
||||||
|
{
|
||||||
|
foreach (var value in values)
|
||||||
|
{
|
||||||
|
var index = this.Children.BinarySearch(value, StringComparer.Ordinal.Compare, v => v.Text);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
this.Children.Insert(~index, new TraitViewModel(this, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TraitViewModel GetOrAdd(string text)
|
||||||
|
{
|
||||||
|
var index = this.Children.BinarySearch(text, StringComparer.Ordinal, vm => vm.Text);
|
||||||
|
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
var viewModel = new TraitViewModel(this, text);
|
||||||
|
this.Children.Insert(~index, viewModel);
|
||||||
|
return viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.Children[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool? IsChecked
|
||||||
|
{
|
||||||
|
get { return _isChecked; }
|
||||||
|
set { SetIsChecked(value, updateChildren: true, updateParent: true); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsExpanded
|
||||||
|
{
|
||||||
|
get { return _isExpanded; }
|
||||||
|
set { Set(ref _isExpanded, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text
|
||||||
|
{
|
||||||
|
get { return _text; }
|
||||||
|
set { Set(ref _text, value); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,11 +12,10 @@
|
|||||||
See http://www.galasoft.ch/mvvm
|
See http://www.galasoft.ch/mvvm
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using GalaSoft.MvvmLight;
|
using CommonServiceLocator;
|
||||||
using GalaSoft.MvvmLight.Ioc;
|
using GalaSoft.MvvmLight.Ioc;
|
||||||
using Microsoft.Practices.ServiceLocation;
|
|
||||||
|
|
||||||
namespace xunit.runner.wpf.ViewModel
|
namespace Xunit.Runner.Wpf.ViewModel
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class contains static references to all the view models in the
|
/// This class contains static references to all the view models in the
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<packages>
|
|
||||||
<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.Immutable" version="1.1.37" targetFramework="net452" />
|
|
||||||
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
|
|
||||||
<package id="xunit.runner.utility" version="2.1.0-beta4-build3109" targetFramework="net452" />
|
|
||||||
</packages>
|
|
||||||
@@ -1,180 +1,46 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
|
||||||
<ProjectGuid>{34FB519C-FB49-4B31-ACA2-7F7879311BCF}</ProjectGuid>
|
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
<RootNamespace>xunit.runner.wpf</RootNamespace>
|
<UseWpf>true</UseWpf>
|
||||||
<AssemblyName>xunit.runner.wpf</AssemblyName>
|
<RootNamespace>Xunit.Runner.Wpf</RootNamespace>
|
||||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
<NoWarn>NU1701;$(NoWarn)</NoWarn>
|
||||||
<FileAlignment>512</FileAlignment>
|
<ApplicationIcon>Artwork\Application.ico</ApplicationIcon>
|
||||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
<Authors>Pilchie</Authors>
|
||||||
<WarningLevel>4</WarningLevel>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
<PackAsTool>true</PackAsTool>
|
||||||
<NuGetPackageImportStamp>
|
<AssetTargetFallback>net472</AssetTargetFallback>
|
||||||
</NuGetPackageImportStamp>
|
<Description>XUnit Gui written in WPF</Description>
|
||||||
</PropertyGroup>
|
<PackageProjectUrl>https://github.com/Pilchie/xunit.runner.wpf</PackageProjectUrl>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<RepositoryUrl>https://github.com/Pilchie/xunit.runner.wpf</RepositoryUrl>
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PackageTags>XUnit Gui test runner</PackageTags>
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<NeutralLanguage>en-us</NeutralLanguage>
|
||||||
<DebugType>full</DebugType>
|
<PackageVersion Condition="'$(appveyor_build_vzersion)' != ''">$(appveyor_build_version)</PackageVersion>
|
||||||
<Optimize>false</Optimize>
|
<PackageVersion Condition="'$(NuPkgVersion)' == ''">1.0.0-local</PackageVersion>
|
||||||
<OutputPath>bin\Debug\</OutputPath>
|
<AssemblyVersion Condition="'$(appveyor_build_version)' != ''">$(appveyor_build_version)</AssemblyVersion>
|
||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
<FileVersion Condition="'$(appveyor_build_version)' != ''">$(appveyor_build_version)</FileVersion>
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
|
||||||
<DebugType>pdbonly</DebugType>
|
|
||||||
<Optimize>true</Optimize>
|
|
||||||
<OutputPath>bin\Release\</OutputPath>
|
|
||||||
<DefineConstants>TRACE</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="GalaSoft.MvvmLight, Version=5.1.1.35049, Culture=neutral, PublicKeyToken=e7570ab207bcb616, processorArchitecture=MSIL">
|
<PackageReference Include="CommonServiceLocator" Version="2.0.2" />
|
||||||
<HintPath>..\packages\MvvmLightLibs.5.1.1.0\lib\net45\GalaSoft.MvvmLight.dll</HintPath>
|
<PackageReference Include="Microsoft.Windows.Compatibility" Version="2.1.0-preview.18571.3" />
|
||||||
<Private>True</Private>
|
<PackageReference Include="MvvmLightLibs" Version="5.4.1.1" />
|
||||||
</Reference>
|
<PackageReference Include="System.Collections.Immutable" Version="1.4.0-preview1-25305-02" />
|
||||||
<Reference Include="GalaSoft.MvvmLight.Extras, Version=5.1.1.35049, Culture=neutral, PublicKeyToken=669f0b5e8f868abf, processorArchitecture=MSIL">
|
<PackageReference Include="System.Windows.Interactivity.WPF" Version="2.0.20525" />
|
||||||
<HintPath>..\packages\MvvmLightLibs.5.1.1.0\lib\net45\GalaSoft.MvvmLight.Extras.dll</HintPath>
|
<PackageReference Include="WindowsAPICodePack" Version="1.1.0" />
|
||||||
<Private>True</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="GalaSoft.MvvmLight.Platform, Version=5.1.1.35053, Culture=neutral, PublicKeyToken=5f873c45e98af8a1, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\MvvmLightLibs.5.1.1.0\lib\net45\GalaSoft.MvvmLight.Platform.dll</HintPath>
|
|
||||||
<Private>True</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Microsoft.Practices.ServiceLocation, Version=1.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll</HintPath>
|
|
||||||
<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>
|
|
||||||
<Reference Include="System.Data" />
|
|
||||||
<Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\MvvmLightLibs.5.1.1.0\lib\net45\System.Windows.Interactivity.dll</HintPath>
|
|
||||||
<Private>True</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="System.Xml" />
|
|
||||||
<Reference Include="Microsoft.CSharp" />
|
|
||||||
<Reference Include="System.Core" />
|
|
||||||
<Reference Include="System.Xml.Linq" />
|
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
|
||||||
<Reference Include="System.Net.Http" />
|
|
||||||
<Reference Include="System.Xaml">
|
|
||||||
<RequiredTargetFramework>4.0</RequiredTargetFramework>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="WindowsBase" />
|
|
||||||
<Reference Include="PresentationCore" />
|
|
||||||
<Reference Include="PresentationFramework" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ApplicationDefinition Include="App.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
<SubType>Designer</SubType>
|
|
||||||
</ApplicationDefinition>
|
|
||||||
<Compile Include="CommandBindings.cs" />
|
|
||||||
<Compile Include="Converters\TestStateConverter.cs" />
|
|
||||||
<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\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\ViewModelLocator.cs" />
|
|
||||||
<Page Include="LoadingDialog.xaml">
|
|
||||||
<SubType>Designer</SubType>
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
</Page>
|
|
||||||
<Page Include="MainWindow.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
<SubType>Designer</SubType>
|
|
||||||
</Page>
|
|
||||||
<Compile Include="App.xaml.cs">
|
|
||||||
<DependentUpon>App.xaml</DependentUpon>
|
|
||||||
<SubType>Code</SubType>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="MainWindow.xaml.cs">
|
|
||||||
<DependentUpon>MainWindow.xaml</DependentUpon>
|
|
||||||
<SubType>Code</SubType>
|
|
||||||
</Compile>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="Properties\AssemblyInfo.cs">
|
|
||||||
<SubType>Code</SubType>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="Properties\Resources.Designer.cs">
|
|
||||||
<AutoGen>True</AutoGen>
|
|
||||||
<DesignTime>True</DesignTime>
|
|
||||||
<DependentUpon>Resources.resx</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="Properties\Settings.Designer.cs">
|
|
||||||
<AutoGen>True</AutoGen>
|
|
||||||
<DependentUpon>Settings.settings</DependentUpon>
|
|
||||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
|
||||||
</Compile>
|
|
||||||
<EmbeddedResource Include="Properties\Resources.resx">
|
|
||||||
<Generator>ResXFileCodeGenerator</Generator>
|
|
||||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
|
||||||
</EmbeddedResource>
|
|
||||||
<None Include="packages.config" />
|
|
||||||
<None Include="Properties\Settings.settings">
|
|
||||||
<Generator>SettingsSingleFileGenerator</Generator>
|
|
||||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
|
||||||
</None>
|
|
||||||
<AppDesigner Include="Properties\" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="App.config" />
|
|
||||||
</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>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\SampleTestAssembly\SampleTestAssembly.csproj">
|
<ProjectReference Include="..\SampleTestAssembly\SampleTestAssembly.csproj">
|
||||||
<Project>{bdafb5dd-ffb3-4a94-a312-dfb080010846}</Project>
|
|
||||||
<Name>SampleTestAssembly</Name>
|
|
||||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
<ProjectReference Include="..\xunit.runner.data\xunit.runner.data.csproj">
|
<ProjectReference Include="..\xunit.runner.data\xunit.runner.data.csproj" />
|
||||||
<Project>{a1f579f4-443e-4f64-bc55-998ab86ff293}</Project>
|
<ProjectReference Include="..\xunit.runner.worker\xunit.runner.worker.csproj" />
|
||||||
<Name>xunit.runner.data</Name>
|
|
||||||
</ProjectReference>
|
|
||||||
<ProjectReference Include="..\xunit.runner.worker\xunit.runner.worker.csproj">
|
|
||||||
<Project>{9df97a2b-0eb5-4b12-9f81-69dfac979814}</Project>
|
|
||||||
<Name>xunit.runner.worker</Name>
|
|
||||||
</ProjectReference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<WCFMetadata Include="Service References\" />
|
<Resource Include="Artwork\**\*.png" />
|
||||||
|
<Resource Include="Artwork\Application.ico" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
|
||||||
<!-- 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">
|
|
||||||
</Target>
|
|
||||||
<Target Name="AfterBuild">
|
|
||||||
</Target>
|
|
||||||
-->
|
|
||||||
</Project>
|
</Project>
|
||||||