Compare commits
2 Commits
1.0.9
..
UseNullable
| Author | SHA1 | Date | |
|---|---|---|---|
| a96ce278d2 | |||
| a10c13626c |
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,8 +0,0 @@
|
||||
<Application x:Class="Launcher.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Launcher">
|
||||
<Application.Resources>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -1,26 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using Xunit.Runner.Wpf;
|
||||
|
||||
namespace Launcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
base.OnStartup(e);
|
||||
|
||||
var main = new MainWindow();
|
||||
main.Title = "xUnit Revit by Speckle";
|
||||
main.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{F7536830-352E-4081-BBBE-F3CF1A2F38FA}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>Launcher</RootNamespace>
|
||||
<AssemblyName>Launcher</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Deterministic>true</Deterministic>
|
||||
</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>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Data" />
|
||||
<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="App.xaml.cs">
|
||||
<DependentUpon>App.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="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\speckle.xunit.runner.wpf\speckle.xunit.runner.wpf.csproj">
|
||||
<Project>{e43230d2-eeb7-45d3-b02c-0c1ef37b7a07}</Project>
|
||||
<Name>speckle.xunit.runner.wpf</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -1,71 +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 Launcher.Properties
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources
|
||||
{
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager
|
||||
{
|
||||
get
|
||||
{
|
||||
if ((resourceMan == null))
|
||||
{
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Launcher.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture
|
||||
{
|
||||
get
|
||||
{
|
||||
return resourceCulture;
|
||||
}
|
||||
set
|
||||
{
|
||||
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 Launcher.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,20 +1,10 @@
|
||||
# speckle.xunit.runner.wpf
|
||||
|
||||
[](https://teocomi.visualstudio.com/Speckle/_build/latest?definitionId=4&branchName=master)
|
||||
|
||||
# xunit.runner.wpf
|
||||
XUnit Gui written in WPF
|
||||
|
||||
Fork of [xunit.runner.wpf](https://www.nuget.org/packages/xunit.runner.wpf).
|
||||
A simple replacement for the old winforms xunit.gui with support for xunit 2.0.
|
||||
|
||||
This fork, uses the [AssemblyRunner](https://github.com/xunit/xunit/blob/main/src/xunit.v3.runner.utility/Runners/AssemblyRunner.cs) of xunit.v3.runner.utility to run the tests so that the external dlls are loaded via reflection in the same AppDomain.
|
||||
This is necessary when running unit tests of code hosted by external applications (Revit etc).
|
||||
Find it on NuGet at [xunit.runner.wpf](https://www.nuget.org/packages/xunit.runner.wpf).
|
||||
|
||||
NuGet package: https://www.nuget.org/packages/speckle.xunit.runner.wpf/
|
||||

|
||||
|
||||
Sample application using it: https://github.com/Speckle-Next/xUnitRevit
|
||||
|
||||
Many thanks to all the developers of xunit and xunit.runner.wpf!
|
||||
|
||||
|
||||
|
||||
Check out our blog post on this 👉 https://speckle.systems/blog/xunitrevit !
|
||||
[](https://ci.appveyor.com/project/Pilchie/xunit-runner-wpf/branch/master)
|
||||
|
||||
@@ -11,22 +11,22 @@ namespace SampleTestAssembly
|
||||
public class Class1
|
||||
{
|
||||
[Fact]
|
||||
[Trait("TraitName1", "TraitValue1")]
|
||||
//[Trait("TraitName1", "TraitValue1")]
|
||||
public void Pass()
|
||||
{
|
||||
Thread.Sleep(TimeSpan.FromSeconds(1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("TraitName1", "TraitValue2")]
|
||||
//[Trait("TraitName1", "TraitValue2")]
|
||||
public void Fail()
|
||||
{
|
||||
Thread.Sleep(TimeSpan.FromSeconds(2));
|
||||
Assert.True(true);
|
||||
Thread.Sleep(TimeSpan.FromSeconds(1));
|
||||
Assert.True(false);
|
||||
}
|
||||
|
||||
[Fact(Skip = "Testing")]
|
||||
[Trait("TraitName2", "TraitValue2")]
|
||||
//[Trait("TraitName2", "TraitValue2")]
|
||||
public void Skip()
|
||||
{
|
||||
Thread.Sleep(TimeSpan.FromSeconds(1));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\xunit.core.2.4.1\build\xunit.core.props" Condition="Exists('..\packages\xunit.core.2.4.1\build\xunit.core.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
@@ -42,16 +41,20 @@
|
||||
<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.3\lib\net35\xunit.abstractions.dll</HintPath>
|
||||
<HintPath>..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="xunit.assert, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll</HintPath>
|
||||
<Reference Include="xunit.assert, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="xunit.core, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.extensibility.core.2.4.1\lib\net452\xunit.core.dll</HintPath>
|
||||
<Reference Include="xunit.core, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="xunit.execution.desktop, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll</HintPath>
|
||||
<Reference Include="xunit.execution.desktop, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -61,18 +64,7 @@
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Analyzer Include="..\packages\xunit.analyzers.0.10.0\analyzers\dotnet\cs\xunit.analyzers.dll" />
|
||||
</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.4.1\build\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.4.1\build\xunit.core.props'))" />
|
||||
<Error Condition="!Exists('..\packages\xunit.core.2.4.1\build\xunit.core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.4.1\build\xunit.core.targets'))" />
|
||||
</Target>
|
||||
<Import Project="..\packages\xunit.core.2.4.1\build\xunit.core.targets" Condition="Exists('..\packages\xunit.core.2.4.1\build\xunit.core.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">
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="xunit" version="2.4.1" targetFramework="net452" />
|
||||
<package id="xunit.abstractions" version="2.0.3" targetFramework="net452" />
|
||||
<package id="xunit.analyzers" version="0.10.0" targetFramework="net452" />
|
||||
<package id="xunit.assert" version="2.4.1" targetFramework="net452" />
|
||||
<package id="xunit.core" version="2.4.1" targetFramework="net452" />
|
||||
<package id="xunit.extensibility.core" version="2.4.1" targetFramework="net452" />
|
||||
<package id="xunit.extensibility.execution" version="2.4.1" targetFramework="net452" />
|
||||
<package id="xunit" version="2.1.0" targetFramework="net452" />
|
||||
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
|
||||
<package id="xunit.assert" version="2.1.0" targetFramework="net452" />
|
||||
<package id="xunit.core" version="2.1.0" targetFramework="net452" />
|
||||
<package id="xunit.extensibility.core" version="2.1.0" targetFramework="net452" />
|
||||
<package id="xunit.extensibility.execution" version="2.1.0" targetFramework="net452" />
|
||||
</packages>
|
||||
@@ -1,53 +0,0 @@
|
||||
# .NET Desktop
|
||||
# Build and run tests for .NET Desktop or Windows classic desktop solutions.
|
||||
# Add steps that publish symbols, save build artifacts, and more:
|
||||
# https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net
|
||||
|
||||
trigger:
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
- refs/tags/*
|
||||
|
||||
pool:
|
||||
vmImage: 'windows-latest'
|
||||
|
||||
variables:
|
||||
solution: '**/*.sln'
|
||||
buildPlatform: 'Any CPU'
|
||||
buildConfiguration: 'Release'
|
||||
|
||||
steps:
|
||||
- task: NuGetToolInstaller@1
|
||||
|
||||
- task: NuGetCommand@2
|
||||
inputs:
|
||||
restoreSolution: '$(solution)'
|
||||
|
||||
- task: VSBuild@1
|
||||
inputs:
|
||||
solution: '$(solution)'
|
||||
platform: '$(buildPlatform)'
|
||||
configuration: '$(buildConfiguration)'
|
||||
|
||||
# - task: VSTest@2
|
||||
# inputs:
|
||||
# platform: '$(buildPlatform)'
|
||||
# configuration: '$(buildConfiguration)'
|
||||
|
||||
- task: NuGetCommand@2
|
||||
inputs:
|
||||
command: 'pack'
|
||||
packagesToPack: '**/speckle.xunit.runner.wpf.csproj'
|
||||
versioningScheme: 'off'
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
|
||||
ArtifactName: 'artifact'
|
||||
publishLocation: 'Container'
|
||||
|
||||
- powershell: |
|
||||
If ($env:BRANCH.StartsWith('refs/tags/')) { nuget push -ApiKey $env:APIKEY -Source https://api.nuget.org/v3/index.json $(Build.ArtifactStagingDirectory)/**/*.nupkg }
|
||||
env:
|
||||
APIKEY: $(nuget-apikey)
|
||||
BRANCH: $(Build.SourceBranch)
|
||||
@@ -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,37 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30011.22
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleTestAssembly", "SampleTestAssembly\SampleTestAssembly.csproj", "{BDAFB5DD-FFB3-4A94-A312-DFB080010846}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "speckle.xunit.runner.wpf", "speckle.xunit.runner.wpf\speckle.xunit.runner.wpf.csproj", "{E43230D2-EEB7-45D3-B02C-0C1EF37B7A07}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Launcher", "Launcher\Launcher.csproj", "{F7536830-352E-4081-BBBE-F3CF1A2F38FA}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{BDAFB5DD-FFB3-4A94-A312-DFB080010846}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BDAFB5DD-FFB3-4A94-A312-DFB080010846}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BDAFB5DD-FFB3-4A94-A312-DFB080010846}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BDAFB5DD-FFB3-4A94-A312-DFB080010846}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E43230D2-EEB7-45D3-B02C-0C1EF37B7A07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E43230D2-EEB7-45D3-B02C-0C1EF37B7A07}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E43230D2-EEB7-45D3-B02C-0C1EF37B7A07}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E43230D2-EEB7-45D3-B02C-0C1EF37B7A07}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F7536830-352E-4081-BBBE-F3CF1A2F38FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F7536830-352E-4081-BBBE-F3CF1A2F38FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F7536830-352E-4081-BBBE-F3CF1A2F38FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F7536830-352E-4081-BBBE-F3CF1A2F38FA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {F8EBD009-FF10-49B8-91DB-F90A51C529BA}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -1,384 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Runner.Wpf;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Xunit.Runners
|
||||
{
|
||||
/// <summary>
|
||||
/// A class which makes it simpler for casual runner authors to find and run tests and get results.
|
||||
/// Adapted from: https://github.com/xunit/xunit/blob/main/src/xunit.v3.runner.utility/Runners/AssemblyRunner.cs
|
||||
/// </summary>
|
||||
public class AssemblyRunner2 : LongLivedMarshalByRefObject, IDisposable, IMessageSinkWithTypes
|
||||
{
|
||||
static readonly Dictionary<Type, string> MessageTypeNames;
|
||||
|
||||
private TaskCompletionSource<string> tcs { get; set; }
|
||||
private CancellationToken cancellationToken { get; set; }
|
||||
|
||||
volatile bool cancelled;
|
||||
bool disposed;
|
||||
readonly TestAssemblyConfiguration configuration;
|
||||
readonly IFrontController controller;
|
||||
|
||||
string assemblyFileName { get; set; }
|
||||
int testCasesDiscovered;
|
||||
readonly List<ITestCase> testCasesToRun = new List<ITestCase>();
|
||||
|
||||
static AssemblyRunner2()
|
||||
{
|
||||
MessageTypeNames = new Dictionary<Type, string>();
|
||||
|
||||
AddMessageTypeName<IDiagnosticMessage>();
|
||||
AddMessageTypeName<IDiscoveryCompleteMessage>();
|
||||
AddMessageTypeName<IErrorMessage>();
|
||||
AddMessageTypeName<ITestAssemblyCleanupFailure>();
|
||||
AddMessageTypeName<ITestAssemblyFinished>();
|
||||
AddMessageTypeName<ITestCaseCleanupFailure>();
|
||||
AddMessageTypeName<ITestCaseDiscoveryMessage>();
|
||||
AddMessageTypeName<ITestClassCleanupFailure>();
|
||||
AddMessageTypeName<ITestCleanupFailure>();
|
||||
AddMessageTypeName<ITestCollectionCleanupFailure>();
|
||||
AddMessageTypeName<ITestFailed>();
|
||||
AddMessageTypeName<ITestFinished>();
|
||||
AddMessageTypeName<ITestMethodCleanupFailure>();
|
||||
AddMessageTypeName<ITestOutput>();
|
||||
AddMessageTypeName<ITestPassed>();
|
||||
AddMessageTypeName<ITestSkipped>();
|
||||
AddMessageTypeName<ITestStarting>();
|
||||
}
|
||||
|
||||
AssemblyRunner2(AppDomainSupport appDomainSupport,
|
||||
string assemblyFileName,
|
||||
TaskCompletionSource<string> tcs,
|
||||
CancellationToken cancellationToken,
|
||||
string configFileName = null,
|
||||
bool shadowCopy = true,
|
||||
string shadowCopyFolder = null)
|
||||
{
|
||||
this.tcs = tcs;
|
||||
this.cancellationToken = cancellationToken;
|
||||
this.assemblyFileName = assemblyFileName;
|
||||
controller = new XunitFrontController(appDomainSupport, assemblyFileName, configFileName, shadowCopy, shadowCopyFolder, diagnosticMessageSink: MessageSinkAdapter.Wrap(this));
|
||||
configuration = ConfigReader.Load(assemblyFileName, configFileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set to get notification of diagnostic messages.
|
||||
/// </summary>
|
||||
public Action<DiagnosticMessageInfo> OnDiagnosticMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to get notification of when test discovery is complete.
|
||||
/// </summary>
|
||||
public Action<TestDiscoveryInfo> OnDiscoveryComplete { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to get notification of error messages (unhandled exceptions outside of tests).
|
||||
/// </summary>
|
||||
public Action<ErrorMessageInfo> OnErrorMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to get notification of when test execution is complete.
|
||||
/// </summary>
|
||||
public Action<ExecutionCompleteInfo> OnExecutionComplete { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to get notification of failed tests.
|
||||
/// </summary>
|
||||
public Action<TestFailedInfo> OnTestFailed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to get notification of finished tests (regardless of outcome).
|
||||
/// </summary>
|
||||
public Action<TestFinishedInfo> OnTestFinished { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to get real-time notification of test output (for xUnit.net v2 tests only).
|
||||
/// Note that output is captured and reported back to all the test completion Info>s
|
||||
/// in addition to being sent to this Info>.
|
||||
/// </summary>
|
||||
public Action<TestOutputInfo> OnTestOutput { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to get notification of passing tests.
|
||||
/// </summary>
|
||||
public Action<TestPassedInfo> OnTestPassed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to get notification of skipped tests.
|
||||
/// </summary>
|
||||
public Action<TestSkippedInfo> OnTestSkipped { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to get notification of when tests start running.
|
||||
/// </summary>
|
||||
public Action<TestStartingInfo> OnTestStarting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to be able to filter the test cases to decide which ones to run. If this is not set,
|
||||
/// then all test cases will be run.
|
||||
/// </summary>
|
||||
public Func<ITestCase, bool> TestCaseFilter { get; set; }
|
||||
|
||||
static void AddMessageTypeName<T>() => MessageTypeNames.Add(typeof(T), typeof(T).FullName);
|
||||
|
||||
/// <summary>
|
||||
/// Call to request that the current run be cancelled. Note that cancellation may not be
|
||||
/// instantaneous, and even after cancellation has been acknowledged, you can expect to
|
||||
/// receive all the cleanup-related messages.
|
||||
/// </summary>
|
||||
public void Cancel()
|
||||
{
|
||||
cancelled = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposed)
|
||||
return;
|
||||
|
||||
disposed = true;
|
||||
|
||||
controller?.Dispose();
|
||||
}
|
||||
|
||||
ITestFrameworkDiscoveryOptions GetDiscoveryOptions(bool? diagnosticMessages, TestMethodDisplay? methodDisplay, TestMethodDisplayOptions? methodDisplayOptions, bool? preEnumerateTheories, bool? internalDiagnosticMessages)
|
||||
{
|
||||
var discoveryOptions = TestFrameworkOptions.ForDiscovery(configuration);
|
||||
discoveryOptions.SetSynchronousMessageReporting(true);
|
||||
|
||||
if (diagnosticMessages.HasValue)
|
||||
discoveryOptions.SetDiagnosticMessages(diagnosticMessages);
|
||||
if (internalDiagnosticMessages.HasValue)
|
||||
discoveryOptions.SetDiagnosticMessages(internalDiagnosticMessages);
|
||||
if (methodDisplay.HasValue)
|
||||
discoveryOptions.SetMethodDisplay(methodDisplay);
|
||||
if (methodDisplayOptions.HasValue)
|
||||
discoveryOptions.SetMethodDisplayOptions(methodDisplayOptions);
|
||||
if (preEnumerateTheories.HasValue)
|
||||
discoveryOptions.SetPreEnumerateTheories(preEnumerateTheories);
|
||||
|
||||
return discoveryOptions;
|
||||
}
|
||||
|
||||
ITestFrameworkExecutionOptions GetExecutionOptions(bool? diagnosticMessages, bool? parallel, int? maxParallelThreads, bool? internalDiagnosticMessages)
|
||||
{
|
||||
var executionOptions = TestFrameworkOptions.ForExecution(configuration);
|
||||
executionOptions.SetSynchronousMessageReporting(true);
|
||||
|
||||
if (diagnosticMessages.HasValue)
|
||||
executionOptions.SetDiagnosticMessages(diagnosticMessages);
|
||||
if (internalDiagnosticMessages.HasValue)
|
||||
executionOptions.SetDiagnosticMessages(internalDiagnosticMessages);
|
||||
if (parallel.HasValue)
|
||||
executionOptions.SetDisableParallelization(!parallel.GetValueOrDefault());
|
||||
if (maxParallelThreads.HasValue)
|
||||
executionOptions.SetMaxParallelThreads(maxParallelThreads);
|
||||
|
||||
return executionOptions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts running tests from a single type (if provided) or the whole assembly (if not). This call returns
|
||||
/// immediately, and status results are dispatched to the Info>s on this class. Callers can check <see cref="Status"/>
|
||||
/// to find out the current status.
|
||||
/// </summary>
|
||||
/// <param name="typeName">The (optional) type name of the single test class to run</param>
|
||||
/// <param name="diagnosticMessages">Set to <c>true</c> to enable diagnostic messages; set to <c>false</c> to disable them.
|
||||
/// By default, uses the value from the assembly configuration file.</param>
|
||||
/// <param name="methodDisplay">Set to choose the default display name style for test methods.
|
||||
/// By default, uses the value from the assembly configuration file. (This parameter is ignored for xUnit.net v1 tests.)</param>
|
||||
/// <param name="methodDisplayOptions">Set to choose the default display name style options for test methods.
|
||||
/// By default, uses the value from the assembly configuration file. (This parameter is ignored for xUnit.net v1 tests.)</param>
|
||||
/// <param name="preEnumerateTheories">Set to <c>true</c> to pre-enumerate individual theory tests; set to <c>false</c> to use
|
||||
/// a single test case for the theory. By default, uses the value from the assembly configuration file. (This parameter is ignored
|
||||
/// for xUnit.net v1 tests.)</param>
|
||||
/// <param name="parallel">Set to <c>true</c> to run test collections in parallel; set to <c>false</c> to run them sequentially.
|
||||
/// By default, uses the value from the assembly configuration file. (This parameter is ignored for xUnit.net v1 tests.)</param>
|
||||
/// <param name="maxParallelThreads">Set to 0 to use unlimited threads; set to any other positive integer to limit to an exact number
|
||||
/// of threads. By default, uses the value from the assembly configuration file. (This parameter is ignored for xUnit.net v1 tests.)</param>
|
||||
/// <param name="internalDiagnosticMessages">Set to <c>true</c> to enable internal diagnostic messages; set to <c>false</c> to disable them.
|
||||
/// By default, uses the value from the assembly configuration file.</param>
|
||||
public void Discover(
|
||||
string typeName = null,
|
||||
bool? diagnosticMessages = null,
|
||||
TestMethodDisplay? methodDisplay = null,
|
||||
TestMethodDisplayOptions? methodDisplayOptions = null,
|
||||
bool? preEnumerateTheories = null,
|
||||
bool? internalDiagnosticMessages = null)
|
||||
{
|
||||
|
||||
|
||||
cancelled = false;
|
||||
testCasesDiscovered = 0;
|
||||
testCasesToRun.Clear();
|
||||
|
||||
XunitWorkerThread.QueueUserWorkItem(() =>
|
||||
{
|
||||
var discoveryOptions = GetDiscoveryOptions(diagnosticMessages, methodDisplay, methodDisplayOptions, preEnumerateTheories, internalDiagnosticMessages);
|
||||
if (typeName != null)
|
||||
controller.Find(typeName, false, this, discoveryOptions);
|
||||
else
|
||||
controller.Find(false, this, discoveryOptions);
|
||||
|
||||
if (cancelled)
|
||||
{
|
||||
// Synthesize the execution complete message, since we're not going to run at all
|
||||
if (OnExecutionComplete != null)
|
||||
OnExecutionComplete(ExecutionCompleteInfo.Empty);
|
||||
return;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public void Run(List<ITestCase> cases,
|
||||
bool? diagnosticMessages = null,
|
||||
bool? parallel = null,
|
||||
int? maxParallelThreads = null,
|
||||
bool? internalDiagnosticMessages = null)
|
||||
{
|
||||
cancelled = false;
|
||||
testCasesDiscovered = cases.Count();
|
||||
testCasesToRun.Clear();
|
||||
testCasesToRun.AddRange(cases);
|
||||
|
||||
XunitWorkerThread.QueueUserWorkItem(() =>
|
||||
{
|
||||
|
||||
if (cancelled)
|
||||
{
|
||||
// Synthesize the execution complete message, since we're not going to run at all
|
||||
if (OnExecutionComplete != null)
|
||||
OnExecutionComplete(ExecutionCompleteInfo.Empty);
|
||||
return;
|
||||
}
|
||||
|
||||
var executionOptions = GetExecutionOptions(diagnosticMessages, parallel, maxParallelThreads, internalDiagnosticMessages);
|
||||
|
||||
controller.RunTests(testCasesToRun, this, executionOptions);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates an assembly runner that discovers and run tests in a separate app domain.
|
||||
/// </summary>
|
||||
/// <param name="assemblyFileName">The test assembly.</param>
|
||||
/// <param name="configFileName">The test assembly configuration file.</param>
|
||||
/// <param name="shadowCopy">If set to <c>true</c>, runs tests in a shadow copied app domain, which allows
|
||||
/// tests to be discovered and run without locking assembly files on disk.</param>
|
||||
/// <param name="shadowCopyFolder">The path on disk to use for shadow copying; if <c>null</c>, a folder
|
||||
/// will be automatically (randomly) generated</param>
|
||||
//public static AssemblyRunner2 WithAppDomain(string assemblyFileName,
|
||||
// string configFileName = null,
|
||||
// bool shadowCopy = true,
|
||||
// string shadowCopyFolder = null)
|
||||
//{
|
||||
// //Guard.ArgumentValid(nameof(shadowCopyFolder), "Cannot set shadowCopyFolder if shadowCopy is false", shadowCopy == true || shadowCopyFolder == null);
|
||||
|
||||
// return new AssemblyRunner2(AppDomainSupport.Required, assemblyFileName, configFileName, shadowCopy, shadowCopyFolder);
|
||||
//}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates an assembly runner that discovers and runs tests without a separate app domain.
|
||||
/// </summary>
|
||||
/// <param name="assemblyFileName">The test assembly.</param>
|
||||
public static AssemblyRunner2 WithoutAppDomain(string assemblyFileName, TaskCompletionSource<string> tcs, CancellationToken cancellationToken)
|
||||
{
|
||||
return new AssemblyRunner2(AppDomainSupport.Denied, assemblyFileName, tcs, cancellationToken);
|
||||
}
|
||||
|
||||
bool DispatchMessage<TMessage>(IMessageSinkMessage message, HashSet<string> messageTypes, Action<TMessage> handler)
|
||||
where TMessage : class
|
||||
{
|
||||
|
||||
if (messageTypes == null || !MessageTypeNames.TryGetValue(typeof(TMessage), out var typeName) || !messageTypes.Contains(typeName))
|
||||
return false;
|
||||
|
||||
handler((TMessage)message);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IMessageSinkWithTypes.OnMessageWithTypes(IMessageSinkMessage message, HashSet<string> messageTypes)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
cancelled = true;
|
||||
}
|
||||
|
||||
if (DispatchMessage<ITestCaseDiscoveryMessage>(message, messageTypes, testDiscovered =>
|
||||
{
|
||||
++testCasesDiscovered;
|
||||
if (TestCaseFilter == null || TestCaseFilter(testDiscovered.TestCase))
|
||||
testCasesToRun.Add(testDiscovered.TestCase);
|
||||
}))
|
||||
return !cancelled;
|
||||
|
||||
if (DispatchMessage<IDiscoveryCompleteMessage>(message, messageTypes, discoveryComplete =>
|
||||
{
|
||||
OnDiscoveryComplete?.Invoke(new TestDiscoveryInfo(testCasesToRun, assemblyFileName));
|
||||
tcs.TrySetResult("");
|
||||
}))
|
||||
return !cancelled;
|
||||
|
||||
if (DispatchMessage<ITestAssemblyFinished>(message, messageTypes, assemblyFinished =>
|
||||
{
|
||||
|
||||
OnExecutionComplete?.Invoke(new ExecutionCompleteInfo(assemblyFinished.TestsRun, assemblyFinished.TestsFailed, assemblyFinished.TestsSkipped, assemblyFinished.ExecutionTime));
|
||||
tcs.TrySetResult("");
|
||||
}))
|
||||
return !cancelled;
|
||||
|
||||
if (OnDiagnosticMessage != null)
|
||||
if (DispatchMessage<IDiagnosticMessage>(message, messageTypes, m => OnDiagnosticMessage(new DiagnosticMessageInfo(m.Message))))
|
||||
return !cancelled;
|
||||
if (OnTestFailed != null)
|
||||
if (DispatchMessage<ITestFailed>(message, messageTypes, m => OnTestFailed(new TestFailedInfo(m.TestClass.Class.Name, m.TestMethod.Method.Name, m.TestCase.Traits, m.Test.DisplayName, m.TestCollection.DisplayName, m.ExecutionTime, m.Output, string.Join("\n\n", m.ExceptionTypes), string.Join("\n\n", m.Messages), string.Join("\n\n", m.StackTraces)))))
|
||||
return !cancelled;
|
||||
if (OnTestFinished != null)
|
||||
if (DispatchMessage<ITestFinished>(message, messageTypes, m => OnTestFinished(new TestFinishedInfo(m.TestClass.Class.Name, m.TestMethod.Method.Name, m.TestCase.Traits, m.Test.DisplayName, m.TestCollection.DisplayName, m.ExecutionTime, m.Output))))
|
||||
return !cancelled;
|
||||
if (OnTestOutput != null)
|
||||
if (DispatchMessage<ITestOutput>(message, messageTypes, m => OnTestOutput(new TestOutputInfo(m.TestClass.Class.Name, m.TestMethod.Method.Name, m.TestCase.Traits, m.Test.DisplayName, m.TestCollection.DisplayName, m.Output))))
|
||||
return !cancelled;
|
||||
if (OnTestPassed != null)
|
||||
if (DispatchMessage<ITestPassed>(message, messageTypes, m => OnTestPassed(new TestPassedInfo(m.TestClass.Class.Name, m.TestMethod.Method.Name, m.TestCase.Traits, m.Test.DisplayName, m.TestCollection.DisplayName, m.ExecutionTime, m.Output))))
|
||||
return !cancelled;
|
||||
if (OnTestSkipped != null)
|
||||
if (DispatchMessage<ITestSkipped>(message, messageTypes, m => OnTestSkipped(new TestSkippedInfo(m.TestClass.Class.Name, m.TestMethod.Method.Name, m.TestCase.Traits, m.Test.DisplayName, m.TestCollection.DisplayName, m.Reason))))
|
||||
return !cancelled;
|
||||
if (OnTestStarting != null)
|
||||
if (DispatchMessage<ITestStarting>(message, messageTypes, m => OnTestStarting(new TestStartingInfo(m.TestClass.Class.Name, m.TestMethod.Method.Name, m.TestCase.Traits, m.Test.DisplayName, m.TestCollection.DisplayName))))
|
||||
return !cancelled;
|
||||
|
||||
if (OnErrorMessage != null)
|
||||
{
|
||||
if (DispatchMessage<IErrorMessage>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.CatastrophicError, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
|
||||
return !cancelled;
|
||||
if (DispatchMessage<ITestAssemblyCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestAssemblyCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
|
||||
return !cancelled;
|
||||
if (DispatchMessage<ITestCaseCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestCaseCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
|
||||
return !cancelled;
|
||||
if (DispatchMessage<ITestClassCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestClassCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
|
||||
return !cancelled;
|
||||
if (DispatchMessage<ITestCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
|
||||
return !cancelled;
|
||||
if (DispatchMessage<ITestCollectionCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestCollectionCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
|
||||
return !cancelled;
|
||||
if (DispatchMessage<ITestMethodCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestMethodCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
|
||||
return !cancelled;
|
||||
}
|
||||
|
||||
return !cancelled;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Xunit.Runner.Wpf.Converters
|
||||
{
|
||||
public class TestStateConverter : IValueConverter
|
||||
{
|
||||
private static ImageSource runningSource;
|
||||
private static ImageSource failedSource;
|
||||
private static ImageSource passedSource;
|
||||
private static ImageSource skippedSource;
|
||||
|
||||
private static SolidColorBrush skippedBrush = new SolidColorBrush(Color.FromRgb(0xEB, 0xCA, 0x00));
|
||||
|
||||
static TestStateConverter()
|
||||
{
|
||||
runningSource = LoadResourceImage("Running_small.png");
|
||||
failedSource = LoadResourceImage("Failed_small.png");
|
||||
passedSource = LoadResourceImage("Passed_small.png");
|
||||
skippedSource = LoadResourceImage("Skipped_small.png");
|
||||
}
|
||||
|
||||
private static BitmapImage LoadResourceImage(string resourceName)
|
||||
{
|
||||
var image = new BitmapImage();
|
||||
image.BeginInit();
|
||||
image.UriSource = new Uri("pack://application:,,,/Speckle.Xunit.Runner.Wpf;component/Artwork/" + resourceName);
|
||||
image.EndInit();
|
||||
return image;
|
||||
}
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
var state = (TestState)value;
|
||||
if (targetType == typeof(Brush))
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case TestState.Running:
|
||||
return Brushes.Blue;
|
||||
case TestState.Failed:
|
||||
return Brushes.Red;
|
||||
case TestState.Passed:
|
||||
return Brushes.Green;
|
||||
case TestState.Skipped:
|
||||
return skippedBrush;
|
||||
default:
|
||||
return Brushes.Gray;
|
||||
}
|
||||
}
|
||||
else if (targetType == typeof(ImageSource))
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case TestState.Running:
|
||||
return runningSource;
|
||||
case TestState.Failed:
|
||||
return failedSource;
|
||||
case TestState.Passed:
|
||||
return passedSource;
|
||||
case TestState.Skipped:
|
||||
return skippedSource;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using mscoree;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Xunit.Runner.Wpf
|
||||
{
|
||||
public static class AppDomains
|
||||
{
|
||||
public static IEnumerable<AppDomain> EnumAppDomains()
|
||||
{
|
||||
IntPtr enumHandle = IntPtr.Zero;
|
||||
ICorRuntimeHost host = null;
|
||||
|
||||
try
|
||||
{
|
||||
host = GetCorRuntimeHost();
|
||||
host.EnumDomains(out enumHandle);
|
||||
object domain = null;
|
||||
|
||||
host.NextDomain(enumHandle, out domain);
|
||||
while (domain != null)
|
||||
{
|
||||
yield return (AppDomain)domain;
|
||||
host.NextDomain(enumHandle, out domain);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (host != null)
|
||||
{
|
||||
if (enumHandle != IntPtr.Zero)
|
||||
{
|
||||
host.CloseEnum(enumHandle);
|
||||
}
|
||||
|
||||
Marshal.ReleaseComObject(host);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ICorRuntimeHost GetCorRuntimeHost()
|
||||
{
|
||||
return (ICorRuntimeHost)Activator.CreateInstance(Marshal.GetTypeFromCLSID(new Guid("CB2F6723-AB3A-11D2-9C40-00C04FA30A3E")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Xunit.Runner.Wpf
|
||||
{
|
||||
public static partial class Extensions
|
||||
{
|
||||
public static void AddRange<TList, TEnumerable>(this ICollection<TList> list, IEnumerable<TEnumerable> items) where TEnumerable : TList
|
||||
{
|
||||
foreach (var i in items)
|
||||
{
|
||||
list.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddRange<TList, TEnumerable>(this ObservableCollection<TList> list, IEnumerable<TEnumerable> items) where TEnumerable : TList
|
||||
{
|
||||
foreach (var i in items)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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,23 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace mscoree
|
||||
{
|
||||
[CompilerGenerated]
|
||||
[Guid("CB2F6722-AB3A-11D2-9C40-00C04FA30A3E")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[TypeIdentifier]
|
||||
[ComImport]
|
||||
[CLSCompliant(false)]
|
||||
public interface ICorRuntimeHost
|
||||
{
|
||||
void _VtblGap1_11();
|
||||
|
||||
void EnumDomains(out IntPtr enumHandle);
|
||||
|
||||
void NextDomain([In] IntPtr enumHandle, [MarshalAs(UnmanagedType.IUnknown)] out object appDomain);
|
||||
|
||||
void CloseEnum([In] IntPtr enumHandle);
|
||||
}
|
||||
}
|
||||
@@ -1,421 +0,0 @@
|
||||
<Window x:Class="Xunit.Runner.Wpf.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
|
||||
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform"
|
||||
xmlns:converters="clr-namespace:Xunit.Runner.Wpf.Converters"
|
||||
xmlns:local="clr-namespace:Xunit.Runner.Wpf"
|
||||
xmlns:vm="clr-namespace:Xunit.Runner.Wpf.ViewModel"
|
||||
mc:Ignorable="d"
|
||||
Title="MainWindow" SizeToContent="Height" Width="800">
|
||||
<!--<Grid>
|
||||
-->
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="Loaded">
|
||||
<cmd:EventToCommand Command="{Binding WindowLoadedCommand}" />
|
||||
</i:EventTrigger>
|
||||
|
||||
<i:EventTrigger EventName="Closing">
|
||||
<cmd:EventToCommand Command="{Binding WindowClosingCommand}" PassEventArgsToCommand="True" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
|
||||
<Window.Resources>
|
||||
<converters:TestStateConverter x:Key="TestStateConverter" />
|
||||
</Window.Resources>
|
||||
|
||||
<Grid local:CommandBindings.Registration="{Binding CommandBindings}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Menu Grid.Row="0">
|
||||
<MenuItem Header="_File">
|
||||
<MenuItem Command="{Binding ExitCommand}" Header="E_xit" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Assembly">
|
||||
<MenuItem Command="ApplicationCommands.Open" Header="_Open" />
|
||||
<MenuItem Header="R_ecent" ItemsSource="{Binding RecentAssemblies}">
|
||||
<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 />
|
||||
<MenuItem Command="{Binding AssemblyRemoveAllCommand}" Header="_Unload" />
|
||||
<MenuItem Command="{Binding AssemblyReloadAllCommand}" Header="_Reload" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{Binding AutoReloadAssembliesCommand}"
|
||||
Header="_Auto Reload Test Assemblies"
|
||||
IsCheckable="True"
|
||||
IsChecked="{Binding AutoReloadAssemblies}" />
|
||||
</MenuItem>
|
||||
|
||||
</Menu>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" MinWidth="200px" />
|
||||
<ColumnDefinition Width="2*" MinWidth="200px" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<GroupBox
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="3"
|
||||
Header="Refinements">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Content="Search:" />
|
||||
<TextBox Grid.Row="1" Text="{Binding FilterString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
|
||||
<Label Grid.Row="2" Content="Assemblies:" />
|
||||
<ListBox
|
||||
Grid.Row="3"
|
||||
Height="175"
|
||||
ItemsSource="{Binding Assemblies}"
|
||||
SelectionMode="Extended">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="vm:TestAssemblyViewModel">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding DisplayName}" />
|
||||
|
||||
<TextBlock
|
||||
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>
|
||||
</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>
|
||||
|
||||
<Label Grid.Row="4" Content="Traits:" />
|
||||
|
||||
<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>
|
||||
</GroupBox>
|
||||
|
||||
<Grid Grid.Row="1" Grid.Column="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button
|
||||
Grid.Column="0"
|
||||
Margin="10,0,0,0"
|
||||
Command="{Binding RunAllCommand}"
|
||||
Content="_Run All" />
|
||||
<Button
|
||||
Grid.Column="1"
|
||||
Command="{Binding RunSelectedCommand}"
|
||||
Content="Run _Selected" />
|
||||
<Button
|
||||
Grid.Column="2"
|
||||
Margin="0,0,10,0"
|
||||
Command="{Binding CancelCommand}"
|
||||
Content="_Cancel" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="0" Grid.Column="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" MinHeight="200px" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="*" MinHeight="200px" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<GroupBox
|
||||
Grid.Row="0"
|
||||
Margin="3"
|
||||
Header="{Binding TestCasesCaption}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="55*"/>
|
||||
<ColumnDefinition Width="244*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.ColumnSpan="2">
|
||||
<ToggleButton
|
||||
Grid.Column="0"
|
||||
Margin="0,4,2,4"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Command="{Binding TestFilterChanged}"
|
||||
IsChecked="{Binding FilterPassedTests}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Artwork\Passed_large.png" />
|
||||
<TextBlock
|
||||
Margin="4,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
Text="{Binding TestsPassed, StringFormat={}{0:#\,0}}" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
<ToggleButton
|
||||
Grid.Column="1"
|
||||
Margin="2,4"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Command="{Binding TestFilterChanged}"
|
||||
IsChecked="{Binding FilterFailedTests}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Artwork\Failed_large.png" />
|
||||
<TextBlock
|
||||
Margin="4,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
Text="{Binding TestsFailed, StringFormat={}{0:#\,0}}" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
<ToggleButton
|
||||
Grid.Column="2"
|
||||
Margin="2,4,0,4"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Command="{Binding TestFilterChanged}"
|
||||
IsChecked="{Binding FilterSkippedTests}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Artwork\Skipped_large.png" />
|
||||
<TextBlock
|
||||
Margin="4,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
Text="{Binding TestsSkipped, StringFormat={}{0:#\,0}}" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
<ToggleButton
|
||||
Grid.Column="2"
|
||||
Margin="2,4,0,4"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Command="{Binding TestFilterChanged}"
|
||||
IsChecked="{Binding FilterRunningTests}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Artwork\Running_large.png" />
|
||||
<TextBlock
|
||||
Margin="4,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
Text="{Binding TestsRunning, StringFormat={}{0:#\,0}}" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
|
||||
<ListBox
|
||||
x:Name="TestCases"
|
||||
Grid.Row="1"
|
||||
ItemsSource="{Binding FilteredTestCases}"
|
||||
SelectedItem="{Binding SelectedTestCase, Mode=TwoWay}"
|
||||
SelectionChanged="TestCases_SelectionChanged"
|
||||
SelectionMode="Extended" Grid.ColumnSpan="2">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="vm:TestCaseViewModel">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="20" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Image
|
||||
Grid.Column="0"
|
||||
Width="16"
|
||||
Margin="0,0,2,0"
|
||||
Source="{Binding Path=State, Mode=OneWay, Converter={StaticResource TestStateConverter}}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding DisplayName}" />
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Margin="10,0,2,0"
|
||||
Text="{Binding ExecutionTime}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
<ListBox.InputBindings>
|
||||
<KeyBinding Key="Enter" Command="{Binding RunSelectedCommand}" />
|
||||
</ListBox.InputBindings>
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="MouseDoubleClick">
|
||||
<cmd:EventToCommand Command="{Binding Path=RunSelectedCommand}" PassEventArgsToCommand="False" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<GridSplitter
|
||||
Grid.Row="1"
|
||||
Height="3"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="White" />
|
||||
|
||||
<GroupBox
|
||||
Grid.Row="2"
|
||||
Margin="3"
|
||||
Header="Output">
|
||||
<TextBox
|
||||
FontFamily="Consolas"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding SelectedTestCase.Output}"
|
||||
VerticalScrollBarVisibility="Visible" />
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
|
||||
<GridSplitter
|
||||
Grid.Column="0"
|
||||
Width="3"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="White" />
|
||||
|
||||
<ProgressBar
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="3"
|
||||
Foreground="{Binding Path=CurrentRunState, Converter={StaticResource TestStateConverter}, Mode=OneWay}"
|
||||
Maximum="{Binding MaximumProgress}"
|
||||
Minimum="0"
|
||||
Value="{Binding Path=TestsCompleted}" />
|
||||
</Grid>
|
||||
|
||||
<StackPanel Grid.Row="2">
|
||||
<Border
|
||||
Margin="3"
|
||||
BorderBrush="LightGray"
|
||||
BorderThickness="1" />
|
||||
<StatusBar>
|
||||
<StatusBar.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="16" />
|
||||
</Grid.ColumnDefinitions>
|
||||
</Grid>
|
||||
</ItemsPanelTemplate>
|
||||
</StatusBar.ItemsPanel>
|
||||
|
||||
<StatusBarItem Grid.Column="1">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock 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>
|
||||
</StatusBar>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -1,60 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
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.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using Xunit.Runner.Wpf.ViewModel;
|
||||
|
||||
namespace Xunit.Runner.Wpf
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
|
||||
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
//in case it's loaded by an external application
|
||||
this.DataContext = new ViewModelLocator().Main;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Xunit.Runner.Wpf
|
||||
{
|
||||
public class TestDiscoveryInfo
|
||||
{
|
||||
public IEnumerable<ITestCase> Cases { get; set; }
|
||||
public string AssemblyFileName { get; set; }
|
||||
|
||||
public TestDiscoveryInfo(IEnumerable<ITestCase> cases, string assemblyFileName)
|
||||
{
|
||||
Cases = cases;
|
||||
AssemblyFileName = assemblyFileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Xunit.Runner.Wpf
|
||||
{
|
||||
public enum TestState
|
||||
{
|
||||
All = 0,
|
||||
NotRun = 1,
|
||||
Running = 2,
|
||||
Passed = 3,
|
||||
Skipped = 4,
|
||||
Failed = 5
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Xunit.Runner.Wpf
|
||||
{
|
||||
public class Tests
|
||||
{
|
||||
[Fact]
|
||||
//[Trait("TraitName1", "TraitValue1")]
|
||||
public void Pass()
|
||||
{
|
||||
Thread.Sleep(TimeSpan.FromSeconds(1));
|
||||
}
|
||||
[Fact]
|
||||
public void Pass2()
|
||||
{
|
||||
Thread.Sleep(TimeSpan.FromSeconds(3));
|
||||
}
|
||||
[Fact]
|
||||
public void Pass3()
|
||||
{
|
||||
Thread.Sleep(TimeSpan.FromSeconds(3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
//[Trait("TraitName1", "TraitValue2")]
|
||||
public void Fail()
|
||||
{
|
||||
Thread.Sleep(TimeSpan.FromSeconds(1));
|
||||
Assert.True(false);
|
||||
}
|
||||
|
||||
[Fact(Skip = "Testing")]
|
||||
//[Trait("TraitName2", "TraitValue2")]
|
||||
public void Skip()
|
||||
{
|
||||
Thread.Sleep(TimeSpan.FromSeconds(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,998 +0,0 @@
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.CommandWpf;
|
||||
using Microsoft.Win32;
|
||||
using Microsoft.WindowsAPICodePack.Taskbar;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
using Xunit.Runner.Wpf.Persistence;
|
||||
using Xunit.Runners;
|
||||
|
||||
namespace Xunit.Runner.Wpf.ViewModel
|
||||
{
|
||||
public class MainViewModel : ViewModelBase
|
||||
{
|
||||
|
||||
//public IAssemblyInjector Injector { get; set; }
|
||||
public List<string> StartupAssemblies { get; set; }
|
||||
|
||||
#region commands
|
||||
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; }
|
||||
|
||||
#endregion
|
||||
|
||||
private readonly Settings settings;
|
||||
|
||||
|
||||
private readonly ITestAssemblyWatcher assemblyWatcher;
|
||||
private readonly HashSet<string> allTestCaseUniqueIDs = new HashSet<string>();
|
||||
private readonly ObservableCollection<TestCaseViewModel> allTestCases = new ObservableCollection<TestCaseViewModel>();
|
||||
private readonly TraitCollectionView traitCollectionView = new TraitCollectionView();
|
||||
private readonly HashSet<string> runningTestSet = new HashSet<string>();
|
||||
|
||||
private CancellationTokenSource filterCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
private CancellationTokenSource cancellationTokenSource;
|
||||
private bool isBusy;
|
||||
private SearchQuery searchQuery = new SearchQuery();
|
||||
private bool autoReloadAssemblies;
|
||||
|
||||
public ObservableCollection<TestAssemblyViewModel> Assemblies { get; } = new ObservableCollection<TestAssemblyViewModel>();
|
||||
public FilteredCollectionView<TestCaseViewModel, SearchQuery> FilteredTestCases { get; }
|
||||
public ObservableCollection<TraitViewModel> Traits => this.traitCollectionView.Collection;
|
||||
public bool AutoReloadAssemblies
|
||||
{
|
||||
get => autoReloadAssemblies;
|
||||
set
|
||||
{
|
||||
var oldVal = autoReloadAssemblies;
|
||||
autoReloadAssemblies = value;
|
||||
RaisePropertyChanged(nameof(AutoReloadAssemblies), oldVal, autoReloadAssemblies);
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<RecentAssemblyViewModel> RecentAssemblies { get; } = new ObservableCollection<RecentAssemblyViewModel>();
|
||||
|
||||
SynchronizationContext uiContext;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the MainViewModel class.
|
||||
/// </summary>
|
||||
public MainViewModel()
|
||||
{
|
||||
this.settings = Settings.Load();
|
||||
|
||||
if (IsInDesignMode)
|
||||
{
|
||||
this.Assemblies.Add(new TestAssemblyViewModel(new AssemblyAndConfigFile(@"C:\Code\Xunit.Runner.Wpf\SampleTestAssembly\bin\Debug\SampleTestAssembly.dll", null)));
|
||||
}
|
||||
|
||||
//Commands
|
||||
CommandBindings = CreateCommandBindings();
|
||||
this.ExitCommand = new RelayCommand(OnExecuteExit);
|
||||
this.WindowLoadedCommand = new RelayCommand(OnExecuteWindowLoaded);
|
||||
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.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);
|
||||
|
||||
this.FilteredTestCases = new FilteredCollectionView<TestCaseViewModel, SearchQuery>(
|
||||
allTestCases, TestCaseMatches, searchQuery, TestComparer.Instance);
|
||||
|
||||
this.FilteredTestCases.CollectionChanged += TestCases_CollectionChanged;
|
||||
|
||||
this.assemblyWatcher = new Impl.TestAssemblyWatcher(Dispatcher.CurrentDispatcher);
|
||||
this.TestCasesCaption = "Test Cases (0)";
|
||||
|
||||
|
||||
RebuildRecentAssembliesMenu();
|
||||
AutoReloadAssemblies = this.settings.GetAutoReloadAssemblies();
|
||||
UpdateAutoReloadStatus();
|
||||
|
||||
uiContext = SynchronizationContext.Current;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Task Discover(string assemblyPath)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<string>();
|
||||
try
|
||||
{
|
||||
using (var runner = AssemblyRunner2.WithoutAppDomain(assemblyPath, tcs, cancellationTokenSource.Token))
|
||||
{
|
||||
runner.OnDiscoveryComplete = OnTestsDiscovered;
|
||||
runner.Discover();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
}
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private List<Task> RunTests(IEnumerable<TestCaseViewModel> testsToRun)
|
||||
{
|
||||
Debug.Assert(this.isBusy);
|
||||
Debug.Assert(this.cancellationTokenSource != null);
|
||||
|
||||
TestsCompleted = 0;
|
||||
TestsRunning = 0;
|
||||
TestsPassed = 0;
|
||||
TestsFailed = 0;
|
||||
TestsSkipped = 0;
|
||||
CurrentRunState = TestState.NotRun;
|
||||
|
||||
foreach (var test in testsToRun)
|
||||
{
|
||||
test.State = TestState.NotRun;
|
||||
test.ExecutionTime = "";
|
||||
}
|
||||
|
||||
|
||||
|
||||
var taskList = new List<Task>();
|
||||
var assemblies = testsToRun.GroupBy(x => x.AssemblyFileName).ToDictionary(x => x.Key, y => y.ToList());
|
||||
|
||||
foreach (var key in assemblies.Keys)
|
||||
{
|
||||
var path = assemblies[key].FirstOrDefault().AssemblyPath;
|
||||
taskList.Add(Run(path, key, assemblies[key]));
|
||||
}
|
||||
return taskList;
|
||||
|
||||
}
|
||||
|
||||
private Task Run(string assemblyPath, string assemblyName, IEnumerable<TestCaseViewModel> testsToRun)
|
||||
{
|
||||
|
||||
var tcs = new TaskCompletionSource<string>();
|
||||
|
||||
try
|
||||
{
|
||||
////inject stuff into assembly
|
||||
//if (Injector != null)
|
||||
//{
|
||||
// Assembly ass = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => x.FullName == assemblyName);
|
||||
// if (ass != null)
|
||||
// Injector.Inject(ass);
|
||||
//}
|
||||
|
||||
using (var runner = AssemblyRunner2.WithoutAppDomain(assemblyPath, tcs, cancellationTokenSource.Token))
|
||||
{
|
||||
runner.OnExecutionComplete = OnExecutionComplete;
|
||||
runner.OnTestStarting = OnTestStarting;
|
||||
runner.OnTestFailed = OnTestFailed;
|
||||
runner.OnTestSkipped = OnTestSkipped;
|
||||
runner.OnTestPassed = OnTestPassed;
|
||||
runner.OnTestFinished = OnTestFinished;
|
||||
|
||||
runner.Run(testsToRun.Select(x => x.TestCase).ToList(), maxParallelThreads:1);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
}
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
#region commands
|
||||
private bool CanExecuteCancel()
|
||||
{
|
||||
return this.cancellationTokenSource != null && !this.cancellationTokenSource.IsCancellationRequested;
|
||||
}
|
||||
|
||||
private void OnExecuteCancel()
|
||||
{
|
||||
Debug.Assert(CanExecuteCancel());
|
||||
this.cancellationTokenSource.Cancel();
|
||||
}
|
||||
|
||||
|
||||
private async void OnExecuteOpen(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
var fileDialog = new OpenFileDialog
|
||||
{
|
||||
Filter = "Unit Test Assemblies|*.dll",
|
||||
Multiselect = true
|
||||
};
|
||||
|
||||
if (fileDialog.ShowDialog() != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var assemblies = fileDialog.FileNames.Select(x => new AssemblyAndConfigFile(x, configFileName: null));
|
||||
await AddAssemblies(assemblies);
|
||||
}
|
||||
|
||||
private static void OnExecuteExit()
|
||||
{
|
||||
//Application.Current.Shutdown();
|
||||
}
|
||||
|
||||
private async void OnExecuteWindowLoaded()
|
||||
{
|
||||
if (StartupAssemblies == null)
|
||||
return;
|
||||
List<AssemblyAndConfigFile> assemblies = new List<AssemblyAndConfigFile>();
|
||||
foreach(var assembly in StartupAssemblies)
|
||||
{
|
||||
if (File.Exists(assembly))
|
||||
assemblies.Add(new AssemblyAndConfigFile(assembly, configFileName: null));
|
||||
}
|
||||
await AddAssemblies(assemblies);
|
||||
//await AddAssemblies(ParseCommandLine(Environment.GetCommandLineArgs().Skip(1)));
|
||||
}
|
||||
|
||||
private void OnExecuteWindowClosing(CancelEventArgs e)
|
||||
{
|
||||
this.settings.Save();
|
||||
}
|
||||
|
||||
private bool CanExecuteRunAll()
|
||||
=> !IsBusy && FilteredTestCases.Any();
|
||||
|
||||
private bool CanExecuteRunSelected()
|
||||
=> !IsBusy && SelectedTestCase != null;
|
||||
private async void OnExecuteRunAll()
|
||||
{
|
||||
UpdateTestCaseInfo(useSelected: false);
|
||||
|
||||
await ExecuteTestSessionOperation(RunFilteredTests);
|
||||
}
|
||||
private async void OnExecuteRunSelected()
|
||||
{
|
||||
Debug.Assert(this.SelectedTestCase != null);
|
||||
UpdateTestCaseInfo(useSelected: true);
|
||||
|
||||
await ExecuteTestSessionOperation(RunSelectedTests);
|
||||
|
||||
}
|
||||
private List<Task> RunFilteredTests()
|
||||
{
|
||||
return RunTests(FilteredTestCases.ToImmutableList());
|
||||
}
|
||||
private List<Task> RunSelectedTests()
|
||||
{
|
||||
return RunTests(ImmutableList.CreateRange(FilteredTestCases.Where(x => x.IsSelected).ToList()));
|
||||
}
|
||||
|
||||
private void OnTestsDiscovered(TestDiscoveryInfo discoveryInfo)
|
||||
{
|
||||
uiContext.Send(x =>
|
||||
{
|
||||
var traitWorkerList = new List<TraitViewModel>();
|
||||
|
||||
foreach (var testCase in discoveryInfo.Cases)
|
||||
{
|
||||
traitWorkerList.Clear();
|
||||
|
||||
// Get or create traits.
|
||||
if (testCase.Traits.Count > 0)
|
||||
{
|
||||
foreach (var kvp in testCase.Traits)
|
||||
{
|
||||
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 vm = new TestCaseViewModel(
|
||||
testCase.DisplayName,
|
||||
testCase.UniqueID,
|
||||
testCase.SkipReason,
|
||||
testCase.TestMethod.TestClass.TestCollection.TestAssembly.Assembly.Name,
|
||||
testCase.TestMethod.TestClass.TestCollection.TestAssembly.Assembly.AssemblyPath,
|
||||
testCase,
|
||||
traitWorkerList);
|
||||
allTestCases.Add(vm);
|
||||
}
|
||||
}, null);
|
||||
|
||||
}
|
||||
|
||||
private void OnExecutionComplete(ExecutionCompleteInfo info)
|
||||
{
|
||||
//Console.WriteLine($"Finished: {info.TotalTests} tests in {Math.Round(info.ExecutionTime, 3)}s ({info.TestsFailed} failed, {info.TestsSkipped} skipped)");
|
||||
//uiContext.Send(x =>
|
||||
//{
|
||||
// IsBusy = false;
|
||||
//}, null);
|
||||
}
|
||||
private void OnTestFinished(TestFinishedInfo info)
|
||||
{
|
||||
TestsCompleted++;
|
||||
var test = FilteredTestCases.FirstOrDefault(x => x.DisplayName == info.TestDisplayName);
|
||||
if (test != null)
|
||||
{
|
||||
test.ExecutionTime = Math.Round(info.ExecutionTime, 2) + "s";
|
||||
}
|
||||
}
|
||||
private void OnTestPassed(TestPassedInfo info)
|
||||
{
|
||||
TestsPassed++;
|
||||
var test = FilteredTestCases.FirstOrDefault(x => x.DisplayName == info.TestDisplayName);
|
||||
if (test != null)
|
||||
{
|
||||
test.Output = info.Output;
|
||||
test.State = TestState.Passed;
|
||||
CurrentRunState = test.State;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTestFailed(TestFailedInfo info)
|
||||
{
|
||||
TestsFailed++;
|
||||
var test = FilteredTestCases.FirstOrDefault(x => x.DisplayName == info.TestDisplayName);
|
||||
if (test != null)
|
||||
{
|
||||
test.State = TestState.Failed;
|
||||
CurrentRunState = test.State;
|
||||
test.Output = info.ExceptionMessage;
|
||||
if (info.ExceptionStackTrace != null)
|
||||
{
|
||||
test.Output += "\n\n";
|
||||
test.Output += info.ExceptionStackTrace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTestSkipped(TestSkippedInfo info)
|
||||
{
|
||||
TestsSkipped++;
|
||||
var test = FilteredTestCases.FirstOrDefault(x => x.DisplayName == info.TestDisplayName);
|
||||
if (test != null)
|
||||
{
|
||||
test.State = TestState.Skipped;
|
||||
CurrentRunState = test.State;
|
||||
test.Output = info.SkipReason;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTestStarting(TestStartingInfo info)
|
||||
{
|
||||
var test = FilteredTestCases.FirstOrDefault(x => x.DisplayName == info.TestDisplayName);
|
||||
if (test != null)
|
||||
{
|
||||
test.State = TestState.Running;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnExecuteTraitCheckedChanged(TraitViewModel trait)
|
||||
{
|
||||
this.searchQuery.TraitSet = this.traitCollectionView.GetCheckedTraits();
|
||||
FilterAfterDelay();
|
||||
}
|
||||
|
||||
private void OnExecuteTraitsClear()
|
||||
{
|
||||
foreach (var cur in this.traitCollectionView.Collection)
|
||||
{
|
||||
cur.IsChecked = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnExecuteRecentAssembly(RecentAssemblyViewModel recentAssembly)
|
||||
{
|
||||
var assemblyAndConfig = new AssemblyAndConfigFile(recentAssembly.FilePath, configFileName: null);
|
||||
|
||||
await this.AddAssemblies(new[] { assemblyAndConfig });
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
public List<TestAssemblyViewModel> SelectedAssemblies
|
||||
{
|
||||
get { return Assemblies.Where(x => x.IsSelected).ToList(); }
|
||||
}
|
||||
|
||||
private void ToggleReloadAssemblies()
|
||||
{
|
||||
this.settings.ToggleAutoReloadAssemblies();
|
||||
AutoReloadAssemblies = this.settings.GetAutoReloadAssemblies();
|
||||
UpdateAutoReloadStatus();
|
||||
}
|
||||
|
||||
private void UpdateAutoReloadStatus()
|
||||
{
|
||||
if (AutoReloadAssemblies)
|
||||
{
|
||||
assemblyWatcher.EnableWatch(ReloadAssemblies);
|
||||
}
|
||||
else
|
||||
{
|
||||
assemblyWatcher.DisableWatch();
|
||||
}
|
||||
}
|
||||
|
||||
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 void RemoveAssemblyTestCases(string assemblyPath)
|
||||
{
|
||||
var i = 0;
|
||||
while (i < this.allTestCases.Count)
|
||||
{
|
||||
if (string.Compare(this.allTestCases[i].AssemblyPath, assemblyPath, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
this.allTestCaseUniqueIDs.Remove(this.allTestCases[i].UniqueID);
|
||||
this.allTestCases.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CommandBindingCollection CreateCommandBindings()
|
||||
{
|
||||
var openBinding = new CommandBinding(ApplicationCommands.Open, OnExecuteOpen);
|
||||
CommandManager.RegisterClassCommandBinding(typeof(MainViewModel), openBinding);
|
||||
|
||||
return new CommandBindingCollection
|
||||
{
|
||||
openBinding,
|
||||
};
|
||||
}
|
||||
|
||||
private IEnumerable<AssemblyAndConfigFile> ParseCommandLine(IEnumerable<string> enumerable)
|
||||
{
|
||||
while (enumerable.Any())
|
||||
{
|
||||
var assemblyFileName = enumerable.First();
|
||||
enumerable = enumerable.Skip(1);
|
||||
|
||||
var configFileName = (string)null;
|
||||
if (IsConfigFile(enumerable.FirstOrDefault()))
|
||||
{
|
||||
configFileName = enumerable.First();
|
||||
enumerable = enumerable.Skip(1);
|
||||
}
|
||||
|
||||
yield return new AssemblyAndConfigFile(assemblyFileName, configFileName);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsConfigFile(string fileName)
|
||||
=> (fileName?.EndsWith(".config", StringComparison.OrdinalIgnoreCase) ?? false) ||
|
||||
(fileName?.EndsWith(".json", StringComparison.OrdinalIgnoreCase) ?? false);
|
||||
|
||||
|
||||
|
||||
public async Task AddAssemblies(IEnumerable<AssemblyAndConfigFile> assemblies)
|
||||
{
|
||||
if (!assemblies.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var newAssemblyViewModels = new List<TestAssemblyViewModel>();
|
||||
|
||||
try
|
||||
{
|
||||
await this.ExecuteTestSessionOperation(() =>
|
||||
{
|
||||
var taskList = new List<Task>();
|
||||
|
||||
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
|
||||
taskList.Add(Discover(assembly.AssemblyFileName));
|
||||
|
||||
var assemblyViewModel = new TestAssemblyViewModel(assembly);
|
||||
|
||||
newAssemblyViewModels.Add(assemblyViewModel);
|
||||
this.Assemblies.Add(assemblyViewModel);
|
||||
this.settings.AddRecentAssembly(assembly.AssemblyFileName);
|
||||
|
||||
assemblyViewModel.State = AssemblyState.Loading;
|
||||
}
|
||||
|
||||
return taskList;
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var assemblyViewModel in newAssemblyViewModels)
|
||||
{
|
||||
assemblyViewModel.State = AssemblyState.Ready;
|
||||
assemblyWatcher.AddAssembly(assemblyViewModel.FileName);
|
||||
}
|
||||
|
||||
RebuildRecentAssembliesMenu();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteTestSessionOperation(Func<List<Task>> operation)
|
||||
{
|
||||
Debug.Assert(!this.IsBusy);
|
||||
Debug.Assert(this.cancellationTokenSource == null);
|
||||
|
||||
try
|
||||
{
|
||||
this.IsBusy = true;
|
||||
this.cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
var taskList = operation();
|
||||
await Task.WhenAll(taskList);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.cancellationTokenSource?.Cancel();
|
||||
//MessageBox.Show(ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.cancellationTokenSource = null;
|
||||
this.IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ReloadAssemblies(IEnumerable<string> assemblies)
|
||||
{
|
||||
if (IsBusy)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var testAssemblies = Assemblies.Where(assembly => assemblies.Contains(assembly.FileName));
|
||||
uiContext.Send(x => { ReloadAssemblies(testAssemblies); }, null);
|
||||
|
||||
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(Discover(assemblyFileName));
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void TestCases_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
UpdateTestCaseInfo(useSelected: false);
|
||||
ClearSelectionFlags();
|
||||
}
|
||||
|
||||
private void ClearSelectionFlags()
|
||||
{
|
||||
foreach (var test in this.allTestCases)
|
||||
{
|
||||
test.IsSelected = false;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateTestCaseInfo(bool useSelected)
|
||||
{
|
||||
var count = FilteredTestCases.Count;
|
||||
if (useSelected)
|
||||
{
|
||||
var selected = FilteredTestCases.Count(tc => tc.IsSelected);
|
||||
if (selected > 0)
|
||||
{
|
||||
count = selected;
|
||||
}
|
||||
}
|
||||
|
||||
//TestCasesCaption = $"Test Cases ({count:#,0})";
|
||||
MaximumProgress = count;
|
||||
}
|
||||
|
||||
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 void FilterAfterDelay()
|
||||
{
|
||||
filterCancellationTokenSource.Cancel();
|
||||
filterCancellationTokenSource = new CancellationTokenSource();
|
||||
var token = filterCancellationTokenSource.Token;
|
||||
|
||||
Task
|
||||
.Delay(TimeSpan.FromMilliseconds(200), token)
|
||||
.ContinueWith(
|
||||
x =>
|
||||
{
|
||||
FilteredTestCases.FilterArgument = searchQuery;
|
||||
},
|
||||
token,
|
||||
TaskContinuationOptions.None,
|
||||
TaskScheduler.FromCurrentSynchronizationContext());
|
||||
}
|
||||
|
||||
#region bindings
|
||||
|
||||
public string FilterString
|
||||
{
|
||||
get { return searchQuery.SearchString; }
|
||||
set
|
||||
{
|
||||
if (Set(ref searchQuery.SearchString, value))
|
||||
{
|
||||
FilterAfterDelay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int testsCompleted = 0;
|
||||
public int TestsCompleted
|
||||
{
|
||||
get { return testsCompleted; }
|
||||
|
||||
set
|
||||
{
|
||||
Set(ref testsCompleted, value);
|
||||
UpdateProgress();
|
||||
}
|
||||
}
|
||||
|
||||
private TestCaseViewModel selectedTest;
|
||||
public TestCaseViewModel SelectedTestCase
|
||||
{
|
||||
get { return selectedTest; }
|
||||
|
||||
set
|
||||
{
|
||||
Set(ref selectedTest, value);
|
||||
}
|
||||
}
|
||||
|
||||
private string testCasesCaption;
|
||||
public string TestCasesCaption
|
||||
{
|
||||
get { return testCasesCaption; }
|
||||
private set { Set(ref testCasesCaption, value); }
|
||||
}
|
||||
|
||||
private bool IsBusy
|
||||
{
|
||||
get { return isBusy; }
|
||||
set
|
||||
{
|
||||
isBusy = value;
|
||||
RunAllCommand.RaiseCanExecuteChanged();
|
||||
RunSelectedCommand.RaiseCanExecuteChanged();
|
||||
CancelCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int testsRunning = 0;
|
||||
public int TestsRunning
|
||||
{
|
||||
get { return testsRunning; }
|
||||
set { Set(ref testsRunning, value); }
|
||||
}
|
||||
|
||||
private int testsPassed = 0;
|
||||
public int TestsPassed
|
||||
{
|
||||
get { return testsPassed; }
|
||||
set { Set(ref testsPassed, value); }
|
||||
}
|
||||
|
||||
private int testsFailed = 0;
|
||||
public int TestsFailed
|
||||
{
|
||||
get { return testsFailed; }
|
||||
set { Set(ref testsFailed, value); }
|
||||
}
|
||||
|
||||
private int testsSkipped = 0;
|
||||
public int TestsSkipped
|
||||
{
|
||||
get { return testsSkipped; }
|
||||
set { Set(ref testsSkipped, value); }
|
||||
}
|
||||
|
||||
private int maximumProgress = int.MaxValue;
|
||||
public int MaximumProgress
|
||||
{
|
||||
get { return maximumProgress; }
|
||||
|
||||
set
|
||||
{
|
||||
Set(ref maximumProgress, value);
|
||||
UpdateProgress();
|
||||
}
|
||||
}
|
||||
|
||||
private TestState currentRunState;
|
||||
public TestState CurrentRunState
|
||||
{
|
||||
get { return currentRunState; }
|
||||
|
||||
set
|
||||
{
|
||||
Set(ref currentRunState, value);
|
||||
UpdateProgress();
|
||||
}
|
||||
}
|
||||
|
||||
public bool FilterRunningTests
|
||||
{
|
||||
get { return searchQuery.FilterRunningTests; }
|
||||
set
|
||||
{
|
||||
if (Set(ref searchQuery.FilterRunningTests, value))
|
||||
{
|
||||
FilterAfterDelay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool FilterPassedTests
|
||||
{
|
||||
get { return searchQuery.FilterPassedTests; }
|
||||
set
|
||||
{
|
||||
if (Set(ref searchQuery.FilterPassedTests, value))
|
||||
{
|
||||
FilterAfterDelay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool FilterFailedTests
|
||||
{
|
||||
get { return searchQuery.FilterFailedTests; }
|
||||
set
|
||||
{
|
||||
if (Set(ref searchQuery.FilterFailedTests, value))
|
||||
{
|
||||
FilterAfterDelay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool FilterSkippedTests
|
||||
{
|
||||
get { return searchQuery.FilterSkippedTests; }
|
||||
set
|
||||
{
|
||||
if (Set(ref searchQuery.FilterSkippedTests, value))
|
||||
{
|
||||
FilterAfterDelay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
private static bool TestCaseMatches(TestCaseViewModel testCase, SearchQuery searchQuery)
|
||||
{
|
||||
if (testCase.DisplayName.IndexOf(searchQuery.SearchString, StringComparison.CurrentCultureIgnoreCase) < 0)
|
||||
{
|
||||
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)
|
||||
{
|
||||
case TestState.Running:
|
||||
return noFilter || searchQuery.FilterRunningTests;
|
||||
|
||||
case TestState.Passed:
|
||||
return noFilter || searchQuery.FilterPassedTests;
|
||||
|
||||
case TestState.Skipped:
|
||||
return noFilter || searchQuery.FilterSkippedTests;
|
||||
|
||||
case TestState.Failed:
|
||||
return noFilter || searchQuery.FilterFailedTests;
|
||||
|
||||
case TestState.NotRun:
|
||||
return noFilter;
|
||||
|
||||
default:
|
||||
Debug.Assert(false, "What state is this test case in?");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TestComparer : IComparer<TestCaseViewModel>
|
||||
{
|
||||
public static TestComparer Instance { get; } = new TestComparer();
|
||||
|
||||
public int Compare(TestCaseViewModel x, TestCaseViewModel y)
|
||||
{
|
||||
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() { }
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using GalaSoft.MvvmLight;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Runner.Wpf;
|
||||
|
||||
namespace Xunit.Runner.Wpf.ViewModel
|
||||
{
|
||||
public class TestCaseViewModel : ViewModelBase
|
||||
{
|
||||
private TestState _state = TestState.NotRun;
|
||||
private string _output = "";
|
||||
private string _execTime = "";
|
||||
|
||||
public string DisplayName { get; }
|
||||
public string UniqueID { get; }
|
||||
public string SkipReason { get; }
|
||||
public string AssemblyFileName { get; }
|
||||
public string AssemblyPath { get; }
|
||||
public ImmutableArray<TraitViewModel> Traits { get; }
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public bool HasSkipReason => !string.IsNullOrEmpty(this.SkipReason);
|
||||
|
||||
public ITestCase TestCase { get; }
|
||||
|
||||
public TestState State
|
||||
{
|
||||
get { return _state; }
|
||||
set { Set(ref _state, value); }
|
||||
}
|
||||
|
||||
public string ExecutionTime
|
||||
{
|
||||
get { return _execTime; }
|
||||
set
|
||||
{
|
||||
Set(ref _execTime, value);
|
||||
}
|
||||
}
|
||||
|
||||
public string Output
|
||||
{
|
||||
get { return _output; }
|
||||
set
|
||||
{
|
||||
Set(ref _output, value);
|
||||
}
|
||||
}
|
||||
|
||||
public TestCaseViewModel(string displayName, string uniqueID, string skipReason, string assemblyFileName, string assemblyPath, ITestCase testCase, IEnumerable<TraitViewModel> traits)
|
||||
{
|
||||
this.DisplayName = displayName;
|
||||
this.UniqueID = uniqueID;
|
||||
this.SkipReason = skipReason;
|
||||
this.AssemblyFileName = assemblyFileName;
|
||||
this.AssemblyPath = assemblyPath;
|
||||
this.TestCase = testCase;
|
||||
this.Traits = traits.ToImmutableArray();
|
||||
|
||||
if (!string.IsNullOrEmpty(skipReason))
|
||||
{
|
||||
_state = TestState.Skipped;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using GalaSoft.MvvmLight;
|
||||
using Xunit.Runner.Wpf;
|
||||
|
||||
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); }
|
||||
}
|
||||
|
||||
public string FullText
|
||||
{
|
||||
get
|
||||
{
|
||||
var fullText = _text;
|
||||
if (_parent != null)
|
||||
fullText = _parent.FullText + _text;
|
||||
return fullText;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
#if NETSTANDARD
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Xunit.Sdk
|
||||
{
|
||||
class XunitWorkerThread : IDisposable
|
||||
{
|
||||
readonly ManualResetEvent finished = new ManualResetEvent(false);
|
||||
static readonly TaskFactory taskFactory = new TaskFactory();
|
||||
|
||||
public XunitWorkerThread(Action threadProc)
|
||||
{
|
||||
QueueUserWorkItem(threadProc, finished);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
finished.Dispose();
|
||||
}
|
||||
|
||||
public void Join()
|
||||
{
|
||||
finished.WaitOne();
|
||||
}
|
||||
|
||||
public static void QueueUserWorkItem(Action backgroundTask, EventWaitHandle finished = null)
|
||||
{
|
||||
taskFactory.StartNew(_ =>
|
||||
{
|
||||
var state = (State)_;
|
||||
|
||||
try
|
||||
{
|
||||
state.BackgroundTask();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (state.Finished != null)
|
||||
state.Finished.Set();
|
||||
}
|
||||
},
|
||||
new State { BackgroundTask = backgroundTask, Finished = finished },
|
||||
CancellationToken.None,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Default);
|
||||
}
|
||||
|
||||
class State
|
||||
{
|
||||
public Action BackgroundTask;
|
||||
public EventWaitHandle Finished;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Xunit.Sdk
|
||||
{
|
||||
class XunitWorkerThread : IDisposable
|
||||
{
|
||||
readonly Thread thread;
|
||||
|
||||
public XunitWorkerThread(Action threadProc)
|
||||
{
|
||||
thread = new Thread(s => ((Action)s)()) { IsBackground = true };
|
||||
thread.Start(threadProc);
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public void Join()
|
||||
{
|
||||
if (thread != Thread.CurrentThread)
|
||||
thread.Join();
|
||||
}
|
||||
|
||||
public static void QueueUserWorkItem(Action backgroundTask, EventWaitHandle finished = null)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(_ =>
|
||||
{
|
||||
var state = (State)_;
|
||||
|
||||
try
|
||||
{
|
||||
state.BackgroundTask();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (state.Finished != null)
|
||||
state.Finished.Set();
|
||||
}
|
||||
},
|
||||
new State { BackgroundTask = backgroundTask, Finished = finished });
|
||||
}
|
||||
|
||||
class State
|
||||
{
|
||||
public Action BackgroundTask;
|
||||
public EventWaitHandle Finished;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,80 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<PackageId>speckle.xunit.runner.wpf</PackageId>
|
||||
<Authors>Speckle</Authors>
|
||||
<Company>Speckle</Company>
|
||||
<Product>speckle.xunit.runner.wpf</Product>
|
||||
<Description>WPF xUnit runner</Description>
|
||||
<PackageProjectUrl>https://github.com/Speckle-Next/speckle.xunit.runner.wpf</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/Speckle-Next/speckle.xunit.runner.wpf</RepositoryUrl>
|
||||
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
|
||||
<PackageTags>xunit wpf runner speckle</PackageTags>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<OutputType>Library</OutputType>
|
||||
<PackageIcon></PackageIcon>
|
||||
<PackageIconUrl>https://avatars2.githubusercontent.com/u/2092016</PackageIconUrl>
|
||||
<PackageIconUrl>https://avatars2.githubusercontent.com/u/2092016</PackageIconUrl>
|
||||
<Version>1.0.9</Version>
|
||||
<AssemblyVersion>1.0.9.0</AssemblyVersion>
|
||||
<FileVersion>1.0.9.0</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Artwork\Application.ico" />
|
||||
<None Remove="Artwork\Failed_large.png" />
|
||||
<None Remove="Artwork\Failed_small.png" />
|
||||
<None Remove="Artwork\Passed_large.png" />
|
||||
<None Remove="Artwork\Passed_small.png" />
|
||||
<None Remove="Artwork\Running_large.png" />
|
||||
<None Remove="Artwork\Running_small.png" />
|
||||
<None Remove="Artwork\Skipped_large.png" />
|
||||
<None Remove="Artwork\Skipped_small.png" />
|
||||
<None Remove="MainWindow.xaml" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MvvmLightLibs" Version="5.4.1.1" />
|
||||
<PackageReference Include="System.Collections.Immutable" Version="1.7.1" />
|
||||
<PackageReference Include="WindowsAPICodePack-Shell" Version="1.1.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.utility" Version="2.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Include="MainWindow.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
<Reference Include="System.Xaml" />
|
||||
<Reference Include="WindowsBase" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Artwork\Application.ico" />
|
||||
<Resource Include="Artwork\Failed_large.png" />
|
||||
<Resource Include="Artwork\Failed_small.png" />
|
||||
<Resource Include="Artwork\Passed_large.png" />
|
||||
<Resource Include="Artwork\Passed_small.png" />
|
||||
<Resource Include="Artwork\Running_large.png" />
|
||||
<Resource Include="Artwork\Running_small.png" />
|
||||
<Resource Include="Artwork\Skipped_large.png" />
|
||||
<Resource Include="Artwork\Skipped_small.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="MainWindow.xaml.cs">
|
||||
<SubType>Code</SubType>
|
||||
<DependentUpon>MainWindow.xaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package >
|
||||
<metadata>
|
||||
<id>$id$</id>
|
||||
<version>$version$</version>
|
||||
<title>$title$</title>
|
||||
<authors>Speckle</authors>
|
||||
<owners>$author$</owners>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">Apache-2.0</license>
|
||||
<projectUrl>https://github.com/Speckle-Next/speckle.xunit.runner.wpf</projectUrl>
|
||||
<iconUrl>https://avatars2.githubusercontent.com/u/2092016</iconUrl>
|
||||
<description>WPF xUnit runner</description>
|
||||
<releaseNotes></releaseNotes>
|
||||
<copyright>Copyright 2020</copyright>
|
||||
<tags>xunit wpf speckle runner</tags>
|
||||
<dependencies>
|
||||
<dependency id="MvvmLightLibs" version="5.4.1.1" />
|
||||
<dependency id="System.Collections.Immutable" version="1.7.1" />
|
||||
<dependency id="WindowsAPICodePack-Shell" version="1.1.1" />
|
||||
<dependency id="xunit" version="2.4.1" />
|
||||
<dependency id="xunit.runner.utility" version="2.4.1" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
</package>
|
||||
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Xunit.Runner.Data
|
||||
{
|
||||
public sealed class ClientReader : IDisposable
|
||||
{
|
||||
private readonly BinaryReader _reader;
|
||||
private bool _closed;
|
||||
private Exception? _exception;
|
||||
|
||||
public bool IsConnected => !_closed;
|
||||
|
||||
public ClientReader(Stream stream)
|
||||
{
|
||||
_reader = new BinaryReader(stream, Constants.Encoding, leaveOpen: true);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (_closed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_closed = true;
|
||||
_reader.Dispose();
|
||||
}
|
||||
|
||||
public TestDataKind ReadKind()
|
||||
{
|
||||
return (TestDataKind)ReadCore(() => _reader.ReadInt32());
|
||||
}
|
||||
|
||||
public TestCaseData ReadTestCaseData()
|
||||
{
|
||||
return ReadCore(() => TestCaseData.ReadFrom(_reader));
|
||||
}
|
||||
|
||||
public TestResultData ReadTestResultData()
|
||||
{
|
||||
return ReadCore(() => TestResultData.ReadFrom(_reader));
|
||||
}
|
||||
|
||||
public string ReadString()
|
||||
{
|
||||
return ReadCore(() => _reader.ReadString());
|
||||
}
|
||||
|
||||
private T ReadCore<T>(Func<T> func)
|
||||
{
|
||||
if (_closed)
|
||||
{
|
||||
if (_exception == null)
|
||||
{
|
||||
throw new Exception("Connection is closed");
|
||||
}
|
||||
|
||||
throw new Exception("Connection is closed", _exception);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return func();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Happens during rude shut down of the client. Log to the screen and close
|
||||
// the connection.
|
||||
Console.WriteLine(ex.Message);
|
||||
_exception = ex;
|
||||
Close();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Xunit.Runner.Data
|
||||
{
|
||||
public sealed class ClientWriter : IDisposable
|
||||
{
|
||||
private readonly BinaryWriter _writer;
|
||||
private bool _closed;
|
||||
|
||||
public bool IsConnected => !_closed;
|
||||
|
||||
public ClientWriter(Stream stream)
|
||||
{
|
||||
_writer = new BinaryWriter(stream, Constants.Encoding, leaveOpen: true);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (_closed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_closed = true;
|
||||
_writer.Dispose();
|
||||
}
|
||||
|
||||
public void Write(TestDataKind kind)
|
||||
{
|
||||
WriteCore(() => _writer.Write((int)kind));
|
||||
}
|
||||
|
||||
public void Write(TestCaseData testCaseData)
|
||||
{
|
||||
WriteCore(() => testCaseData.WriteTo(_writer));
|
||||
}
|
||||
|
||||
public void Write(TestResultData testCaseResultData)
|
||||
{
|
||||
WriteCore(() => testCaseResultData.WriteTo(_writer));
|
||||
}
|
||||
|
||||
public void Write(string str)
|
||||
{
|
||||
WriteCore(() => _writer.Write(str));
|
||||
}
|
||||
|
||||
private void WriteCore(Action action)
|
||||
{
|
||||
if (_closed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Happens during rude shut down of the client. Log to the screen and close
|
||||
// the connection.
|
||||
Console.WriteLine(ex.Message);
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Xunit.Runner.Data
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public const string ActionDiscover = "discover";
|
||||
public const string ActionRunAll = "runall";
|
||||
public const string ActionRunSpecific = "runspecific";
|
||||
public static readonly Encoding Encoding = Encoding.UTF8;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
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")]
|
||||
@@ -0,0 +1,69 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Xunit.Runner.Data
|
||||
{
|
||||
public sealed class TestCaseData
|
||||
{
|
||||
public string DisplayName { get; set; }
|
||||
public string UniqueID { get; set; }
|
||||
public string SkipReason { get; set; }
|
||||
public string AssemblyPath { get; set; }
|
||||
public Dictionary<string, List<string>> TraitMap { get; set; }
|
||||
|
||||
public TestCaseData(string displayName, string uniqueID, string skipReason, string assemblyPath, Dictionary<string, List<string>> traitMap)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
UniqueID = uniqueID;
|
||||
SkipReason = skipReason;
|
||||
AssemblyPath = assemblyPath;
|
||||
TraitMap = traitMap;
|
||||
}
|
||||
|
||||
public static TestCaseData ReadFrom(BinaryReader reader)
|
||||
{
|
||||
var displayName = reader.ReadString();
|
||||
var uniqueID = reader.ReadString();
|
||||
var skipReason = reader.ReadString();
|
||||
var assemblyPath = reader.ReadString();
|
||||
var count = reader.ReadInt32();
|
||||
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)
|
||||
{
|
||||
writer.Write(DisplayName);
|
||||
writer.Write(UniqueID);
|
||||
writer.Write(SkipReason ?? string.Empty);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Xunit.Runner.Data
|
||||
{
|
||||
public enum TestDataKind
|
||||
{
|
||||
Value = 1,
|
||||
EndOfData = 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.IO;
|
||||
|
||||
namespace Xunit.Runner.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Note: More severe states are higher numbers.
|
||||
/// <see cref="MainViewModel.TestRunVisitor_TestFinished(object, TestStateEventArgs)"/>
|
||||
/// </summary>
|
||||
public enum TestState
|
||||
{
|
||||
All = 0,
|
||||
NotRun,
|
||||
Running,
|
||||
Passed,
|
||||
Skipped,
|
||||
Failed,
|
||||
}
|
||||
|
||||
public sealed class TestResultData
|
||||
{
|
||||
public string TestCaseDisplayName { get; set; }
|
||||
public string TestCaseUniqueID { get; set; }
|
||||
public TestState TestState { get; set; }
|
||||
public string Output { get; set; }
|
||||
|
||||
public TestResultData(string displayName, string uniqueID, TestState state, string output = "")
|
||||
{
|
||||
TestCaseDisplayName = displayName;
|
||||
TestCaseUniqueID = uniqueID;
|
||||
TestState = state;
|
||||
Output = output;
|
||||
}
|
||||
|
||||
public static TestResultData ReadFrom(BinaryReader reader)
|
||||
{
|
||||
var displayName = reader.ReadString();
|
||||
var uniqueID = reader.ReadString();
|
||||
var state = (TestState)reader.ReadInt32();
|
||||
var output = reader.ReadString();
|
||||
return new TestResultData(displayName, uniqueID, state, output);
|
||||
}
|
||||
|
||||
public void WriteTo(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(TestCaseDisplayName);
|
||||
writer.Write(TestCaseUniqueID);
|
||||
writer.Write((int)TestState);
|
||||
writer.Write(Output);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<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 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>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
|
||||
</startup>
|
||||
</configuration>
|
||||
</configuration>
|
||||
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
|
||||
namespace Xunit.Runner.Worker
|
||||
{
|
||||
internal abstract class Connection : IDisposable
|
||||
{
|
||||
private bool _closed;
|
||||
|
||||
internal abstract Stream Stream { get; }
|
||||
|
||||
internal abstract void WaitForClientConnect();
|
||||
internal abstract void WaitForClientDone();
|
||||
|
||||
protected virtual void DisposeCore()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal void Dispose()
|
||||
{
|
||||
if (_closed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_closed = true;
|
||||
DisposeCore();
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal sealed class NamedPipeConnection : Connection
|
||||
{
|
||||
private readonly NamedPipeServerStream _stream;
|
||||
|
||||
internal override Stream Stream => _stream;
|
||||
|
||||
internal NamedPipeConnection(string pipeName)
|
||||
{
|
||||
_stream = new NamedPipeServerStream(pipeName, PipeDirection.InOut);
|
||||
}
|
||||
|
||||
protected override void DisposeCore()
|
||||
{
|
||||
_stream.Dispose();
|
||||
}
|
||||
|
||||
internal override void WaitForClientConnect()
|
||||
{
|
||||
_stream.WaitForConnection();
|
||||
}
|
||||
|
||||
internal override void WaitForClientDone()
|
||||
{
|
||||
try
|
||||
{
|
||||
_stream.ReadByte();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// If there is an error reading from the client then clearly they are done
|
||||
Console.WriteLine($"Error reading client done byte {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TestConnection : Connection
|
||||
{
|
||||
private readonly MemoryStream _stream = new MemoryStream();
|
||||
|
||||
internal override Stream Stream => _stream;
|
||||
|
||||
internal override void WaitForClientConnect()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal override void WaitForClientDone()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.IO;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Runner.Data;
|
||||
using Xunit.Runner.Worker.MessageSinks;
|
||||
|
||||
namespace Xunit.Runner.Worker
|
||||
{
|
||||
internal sealed class DiscoverUtil : XunitUtil
|
||||
{
|
||||
private sealed class TestDiscoverySink : BaseTestDiscoverySink
|
||||
{
|
||||
private readonly ClientWriter _writer;
|
||||
|
||||
internal TestDiscoverySink(ClientWriter writer)
|
||||
{
|
||||
_writer = writer;
|
||||
}
|
||||
|
||||
protected override bool ShouldContinue => _writer.IsConnected;
|
||||
|
||||
protected override void OnTestDiscovered(ITestCaseDiscoveryMessage testCaseDiscovered)
|
||||
{
|
||||
var testCase = testCaseDiscovered.TestCase;
|
||||
var testCaseData = new TestCaseData(
|
||||
testCase.DisplayName,
|
||||
testCase.UniqueID,
|
||||
testCase.SkipReason,
|
||||
testCaseDiscovered.TestAssembly.Assembly.AssemblyPath,
|
||||
testCase.Traits);
|
||||
|
||||
_writer.Write(TestDataKind.Value);
|
||||
_writer.Write(testCaseData);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Go(string assemblyFileName, Stream stream)
|
||||
{
|
||||
Go(assemblyFileName, stream, AppDomainSupport.IfAvailable,
|
||||
(xunit, configuration, writer) =>
|
||||
{
|
||||
using (var sink = new TestDiscoverySink(writer))
|
||||
{
|
||||
xunit.Find(includeSourceInformation: false, messageSink: sink,
|
||||
discoveryOptions: TestFrameworkOptions.ForDiscovery(configuration));
|
||||
|
||||
sink.Finished.WaitOne();
|
||||
|
||||
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,52 @@
|
||||
using System;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Xunit.Runner.Worker
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
private const int ExitSuccess = 0;
|
||||
private const int ExitError = 1;
|
||||
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
Usage();
|
||||
return ExitError;
|
||||
}
|
||||
|
||||
var pipeName = args[0];
|
||||
var parentPid = Int32.Parse(args[1]);
|
||||
var process = Process.GetProcessById(parentPid);
|
||||
if (process == null)
|
||||
{
|
||||
Console.WriteLine($"Invalid parent pid {parentPid}");
|
||||
return ExitError;
|
||||
}
|
||||
|
||||
Task.WaitAny(
|
||||
Task.Run(() => process.WaitForExit()),
|
||||
Task.Run(() => new Listener(pipeName).Go()));
|
||||
|
||||
return ExitSuccess;
|
||||
}
|
||||
|
||||
private static void Usage()
|
||||
{
|
||||
Console.WriteLine("xunit.runner.worker [pipe name] [action] [assembly path]");
|
||||
Console.WriteLine("\tpipe name: Name of the pipe this worker should communicate on");
|
||||
Console.WriteLine("\taction: Action performed by the worker (run or discover tests)");
|
||||
Console.WriteLine("\tassembly path: Path of assembly to perform the action against");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
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")]
|
||||
@@ -0,0 +1,160 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Xunit.Runner.Data;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Runner.Worker.MessageSinks;
|
||||
|
||||
namespace Xunit.Runner.Worker
|
||||
{
|
||||
internal sealed class RunUtil : XunitUtil
|
||||
{
|
||||
private sealed class TestRunSink : BaseTestRunSink
|
||||
{
|
||||
private readonly ClientWriter _writer;
|
||||
|
||||
public TestRunSink(ClientWriter writer)
|
||||
{
|
||||
_writer = writer;
|
||||
}
|
||||
|
||||
protected override bool ShouldContinue => _writer.IsConnected;
|
||||
|
||||
private void Process(string displayName, string uniqueID, TestState state, string output = "")
|
||||
{
|
||||
System.Diagnostics.Trace.WriteLine($"{state} - {displayName}");
|
||||
var result = new TestResultData(displayName, uniqueID, state, output);
|
||||
|
||||
_writer.Write(TestDataKind.Value);
|
||||
_writer.Write(result);
|
||||
}
|
||||
|
||||
protected override void OnTestStarted(ITestStarting testStarted)
|
||||
{
|
||||
Process(testStarted.TestCase.DisplayName, testStarted.TestCase.UniqueID, TestState.Running);
|
||||
}
|
||||
|
||||
protected override void OnTestFailed(ITestFailed testFailed)
|
||||
{
|
||||
var displayName = testFailed.TestCase.DisplayName;
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendLine($"{displayName} FAILED:");
|
||||
for (int i = 0; i < testFailed.ExceptionTypes.Length; i++)
|
||||
{
|
||||
builder.AppendLine($"\tException type: '{testFailed.ExceptionTypes[i]}', number: '{i}', parent: '{testFailed.ExceptionParentIndices[i]}'");
|
||||
builder.AppendLine($"\tException message:");
|
||||
builder.AppendLine(testFailed.Messages[i]);
|
||||
builder.AppendLine($"\tException stacktrace");
|
||||
builder.AppendLine(testFailed.StackTraces[i]);
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
|
||||
Process(testFailed.TestCase.DisplayName, testFailed.TestCase.UniqueID, TestState.Failed, builder.ToString());
|
||||
}
|
||||
|
||||
protected override void OnTestPassed(ITestPassed testPassed)
|
||||
{
|
||||
Process(testPassed.TestCase.DisplayName, testPassed.TestCase.UniqueID, TestState.Passed);
|
||||
}
|
||||
|
||||
protected override void OnTestSkipped(ITestSkipped testSkipped)
|
||||
{
|
||||
Process(testSkipped.TestCase.DisplayName, testSkipped.TestCase.UniqueID, TestState.Skipped);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TestDiscoverySink : BaseTestDiscoverySink
|
||||
{
|
||||
private readonly HashSet<string> _testCaseUniqueIDSet;
|
||||
private readonly List<ITestCase> _testCaseList;
|
||||
|
||||
internal TestDiscoverySink(HashSet<string> testCaseUniqueIDSet, List<ITestCase> testCaseList)
|
||||
{
|
||||
_testCaseUniqueIDSet = testCaseUniqueIDSet;
|
||||
_testCaseList = testCaseList;
|
||||
}
|
||||
|
||||
protected override void OnTestDiscovered(ITestCaseDiscoveryMessage testCaseDiscovered)
|
||||
{
|
||||
var testCase = testCaseDiscovered.TestCase;
|
||||
if (_testCaseUniqueIDSet.Contains(testCase.UniqueID))
|
||||
{
|
||||
_testCaseList.Add(testCaseDiscovered.TestCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read out the set of test case unique IDs to run.
|
||||
/// </summary>
|
||||
private static List<string> ReadTestCaseUniqueIDs(Stream stream)
|
||||
{
|
||||
using (var reader = new ClientReader(stream))
|
||||
{
|
||||
var list = new List<string>();
|
||||
while (reader.ReadKind() == TestDataKind.Value)
|
||||
{
|
||||
list.Add(reader.ReadString());
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ITestCase> GetTestCaseList(XunitFrontController xunit, TestAssemblyConfiguration configuration, HashSet<string> testCaseNameSet)
|
||||
{
|
||||
var testCaseList = new List<ITestCase>();
|
||||
|
||||
using (var sink = new TestDiscoverySink(testCaseNameSet, testCaseList))
|
||||
{
|
||||
xunit.Find(includeSourceInformation: false, messageSink: sink,
|
||||
discoveryOptions: TestFrameworkOptions.ForDiscovery(configuration));
|
||||
|
||||
sink.Finished.WaitOne();
|
||||
}
|
||||
|
||||
return testCaseList;
|
||||
}
|
||||
|
||||
internal static void RunAll(string assemblyFileName, Stream stream)
|
||||
{
|
||||
Go(assemblyFileName, stream, AppDomainSupport.IfAvailable,
|
||||
(xunit, configuration, writer) =>
|
||||
{
|
||||
using (var sink = new TestRunSink(writer))
|
||||
{
|
||||
xunit.RunAll(sink,
|
||||
discoveryOptions: TestFrameworkOptions.ForDiscovery(configuration),
|
||||
executionOptions: TestFrameworkOptions.ForExecution(configuration));
|
||||
|
||||
sink.Finished.WaitOne();
|
||||
|
||||
writer.Write(TestDataKind.EndOfData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
internal static void RunSpecific(string assemblyFileName, Stream stream)
|
||||
{
|
||||
var testCaseUniqueIDSet = new HashSet<string>(ReadTestCaseUniqueIDs(stream), StringComparer.Ordinal);
|
||||
|
||||
Go(assemblyFileName, stream, AppDomainSupport.IfAvailable,
|
||||
(xunit, configuration, writer) =>
|
||||
{
|
||||
using (var sink = new TestRunSink(writer))
|
||||
{
|
||||
var testCaseList = GetTestCaseList(xunit, configuration, testCaseUniqueIDSet);
|
||||
|
||||
xunit.RunTests(testCaseList, sink,
|
||||
executionOptions: TestFrameworkOptions.ForExecution(configuration));
|
||||
|
||||
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.SubscribeResolve())
|
||||
using (var xunit = new XunitFrontController(appDomainSupport, assemblyFileName, shadowCopy: false))
|
||||
using (var writer = new ClientWriter(stream))
|
||||
{
|
||||
var configuration = ConfigReader.Load(assemblyFileName);
|
||||
action(xunit, configuration, writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
|
||||
<package id="xunit.runner.utility" version="2.1.0" targetFramework="net46" />
|
||||
</packages>
|
||||
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{9DF97A2B-0EB5-4B12-9F81-69DFAC979814}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Xunit.Runner.Worker</RootNamespace>
|
||||
<AssemblyName>xunit.runner.worker</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</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>
|
||||
<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" />
|
||||
<Reference Include="xunit.abstractions">
|
||||
<HintPath>..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="xunit.runner.utility.desktop, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.runner.utility.2.1.0\lib\net35\xunit.runner.utility.desktop.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Connection.cs" />
|
||||
<Compile Include="DiscoverUtil.cs" />
|
||||
<Compile Include="Listener.cs" />
|
||||
<Compile Include="MessageSinks\BaseMessageSink.cs" />
|
||||
<Compile Include="MessageSinks\BaseTestRunSink.cs" />
|
||||
<Compile Include="MessageSinks\DiagnosticSink.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="RunUtil.cs" />
|
||||
<Compile Include="MessageSinks\BaseTestDiscoverySink.cs" />
|
||||
<Compile Include="XunitUtil.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>
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26430.4
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xunit.runner.wpf", "xunit.runner.wpf\xunit.runner.wpf.csproj", "{34FB519C-FB49-4B31-ACA2-7F7879311BCF}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleTestAssembly", "SampleTestAssembly\SampleTestAssembly.csproj", "{BDAFB5DD-FFB3-4A94-A312-DFB080010846}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xunit.runner.worker", "xunit.runner.worker\xunit.runner.worker.csproj", "{9DF97A2B-0EB5-4B12-9F81-69DFAC979814}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xunit.runner.data", "xunit.runner.data\xunit.runner.data.csproj", "{A1F579F4-443E-4F64-BC55-998AB86FF293}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{34FB519C-FB49-4B31-ACA2-7F7879311BCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{34FB519C-FB49-4B31-ACA2-7F7879311BCF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{34FB519C-FB49-4B31-ACA2-7F7879311BCF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{34FB519C-FB49-4B31-ACA2-7F7879311BCF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BDAFB5DD-FFB3-4A94-A312-DFB080010846}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BDAFB5DD-FFB3-4A94-A312-DFB080010846}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BDAFB5DD-FFB3-4A94-A312-DFB080010846}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BDAFB5DD-FFB3-4A94-A312-DFB080010846}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9DF97A2B-0EB5-4B12-9F81-69DFAC979814}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9DF97A2B-0EB5-4B12-9F81-69DFAC979814}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9DF97A2B-0EB5-4B12-9F81-69DFAC979814}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9DF97A2B-0EB5-4B12-9F81-69DFAC979814}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A1F579F4-443E-4F64-BC55-998AB86FF293}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A1F579F4-443E-4F64-BC55-998AB86FF293}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A1F579F4-443E-4F64-BC55-998AB86FF293}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A1F579F4-443E-4F64-BC55-998AB86FF293}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
|
||||
</startup>
|
||||
</configuration>
|
||||
</configuration>
|
||||
@@ -0,0 +1,14 @@
|
||||
<Application x:Class="Xunit.Runner.Wpf.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Xunit.Runner.Wpf"
|
||||
StartupUri="MainWindow.xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
d1p1:Ignorable="d"
|
||||
xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006">
|
||||
<Application.Resources>
|
||||
<vm:ViewModelLocator x:Key="Locator"
|
||||
d:IsDataSource="True"
|
||||
xmlns:vm="clr-namespace:Xunit.Runner.Wpf.ViewModel" />
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Xunit.Runner.Wpf
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 266 KiB After Width: | Height: | Size: 266 KiB |
|
Before Width: | Height: | Size: 703 B After Width: | Height: | Size: 703 B |
|
Before Width: | Height: | Size: 401 B After Width: | Height: | Size: 401 B |
|
Before Width: | Height: | Size: 659 B After Width: | Height: | Size: 659 B |
|
Before Width: | Height: | Size: 402 B After Width: | Height: | Size: 402 B |
|
Before Width: | Height: | Size: 659 B After Width: | Height: | Size: 659 B |
|
Before Width: | Height: | Size: 402 B After Width: | Height: | Size: 402 B |
|
Before Width: | Height: | Size: 523 B After Width: | Height: | Size: 523 B |
|
Before Width: | Height: | Size: 298 B After Width: | Height: | Size: 298 B |
@@ -16,16 +16,14 @@ namespace Xunit.Runner.Wpf
|
||||
}
|
||||
}
|
||||
|
||||
public static CommandBindingCollection GetRegistration(UIElement element)
|
||||
public static CommandBindingCollection? GetRegistration(UIElement element)
|
||||
=> (element != null ? (CommandBindingCollection)element.GetValue(Registration) : null);
|
||||
|
||||
private static void OnRegistrationChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
UIElement element = sender as UIElement;
|
||||
if (element != null)
|
||||
if (sender is UIElement element)
|
||||
{
|
||||
CommandBindingCollection bindings = e.NewValue as CommandBindingCollection;
|
||||
if (bindings != null)
|
||||
if (e.NewValue is CommandBindingCollection bindings)
|
||||
{
|
||||
element.CommandBindings.AddRange(bindings);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Xunit.Runner.Data;
|
||||
|
||||
namespace Xunit.Runner.Wpf.Converters
|
||||
{
|
||||
public class TestStateConverter : IValueConverter
|
||||
{
|
||||
private static ImageSource runningSource;
|
||||
private static ImageSource failedSource;
|
||||
private static ImageSource passedSource;
|
||||
private static ImageSource skippedSource;
|
||||
|
||||
private static SolidColorBrush skippedBrush = new SolidColorBrush(Color.FromRgb(0xEB, 0xCA, 0x00));
|
||||
|
||||
static TestStateConverter()
|
||||
{
|
||||
runningSource = LoadResourceImage("Running_small.png");
|
||||
failedSource = LoadResourceImage("Failed_small.png");
|
||||
passedSource = LoadResourceImage("Passed_small.png");
|
||||
skippedSource = LoadResourceImage("Skipped_small.png");
|
||||
}
|
||||
|
||||
private static BitmapImage LoadResourceImage(string resourceName)
|
||||
{
|
||||
var image = new BitmapImage();
|
||||
image.BeginInit();
|
||||
image.UriSource = new Uri("pack://application:,,,/xunit.runner.wpf;component/Artwork/" + resourceName);
|
||||
image.EndInit();
|
||||
return image;
|
||||
}
|
||||
|
||||
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
var state = (TestState)value;
|
||||
if (targetType == typeof(Brush))
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case TestState.Running:
|
||||
return Brushes.Blue;
|
||||
case TestState.Failed:
|
||||
return Brushes.Red;
|
||||
case TestState.Passed:
|
||||
return Brushes.Green;
|
||||
case TestState.Skipped:
|
||||
return skippedBrush;
|
||||
default:
|
||||
return Brushes.Gray;
|
||||
}
|
||||
}
|
||||
else if (targetType == typeof(ImageSource))
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case TestState.Running:
|
||||
return runningSource;
|
||||
case TestState.Failed:
|
||||
return failedSource;
|
||||
case TestState.Passed:
|
||||
return passedSource;
|
||||
case TestState.Skipped:
|
||||
return skippedSource;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Xunit.Runner.Wpf
|
||||
{
|
||||
public static partial class Extensions
|
||||
{
|
||||
public static void AddRange<TList, TEnumerable>(this ICollection<TList> list, IEnumerable<TEnumerable> items) where TEnumerable : TList
|
||||
{
|
||||
foreach (var i in items)
|
||||
{
|
||||
list.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddRange<TList, TEnumerable>(this ObservableCollection<TList> list, IEnumerable<TEnumerable> items) where TEnumerable : TList
|
||||
{
|
||||
foreach (var i in items)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +283,13 @@ namespace Xunit.Runner.Wpf
|
||||
|
||||
object IList.this[int index]
|
||||
{
|
||||
get { return this[index]; }
|
||||
get
|
||||
{
|
||||
// Can't figure out how to not get a warning here.
|
||||
#pragma warning disable CS8603
|
||||
return this[index];
|
||||
#pragma warning restore
|
||||
}
|
||||
set { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit.Runner.Data;
|
||||
|
||||
namespace Xunit.Runner.Wpf
|
||||
{
|
||||
internal interface ITestUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Discover the list of test cases which are available in the specified assembly.
|
||||
/// </summary>
|
||||
Task Discover(
|
||||
string assebmlyFileName,
|
||||
Action<IEnumerable<TestCaseData>> testsDiscovered,
|
||||
CancellationToken cancellationToken = default(CancellationToken));
|
||||
|
||||
/// <summary>
|
||||
/// Begin a run of all unit tests for the given assembly.
|
||||
/// </summary>
|
||||
Task RunAll(
|
||||
string assemblyFileName,
|
||||
Action<IEnumerable<TestResultData>> testsFinished,
|
||||
CancellationToken cancellationToken = default(CancellationToken));
|
||||
|
||||
/// <summary>
|
||||
/// Begin a run of specific unit tests for the given assembly.
|
||||
/// </summary>
|
||||
Task RunSpecific(
|
||||
string assemblyFileName,
|
||||
ImmutableArray<string> testCasesToRun,
|
||||
Action<IEnumerable<TestResultData>> testsFinished,
|
||||
CancellationToken cancellationToken = default(CancellationToken));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
using Xunit.Runner.Data;
|
||||
|
||||
namespace Xunit.Runner.Wpf.Impl
|
||||
{
|
||||
internal partial class RemoteTestUtil
|
||||
{
|
||||
private sealed class BackgroundWriter<T>
|
||||
{
|
||||
private readonly ClientWriter _writer;
|
||||
private readonly ImmutableArray<T> _data;
|
||||
private readonly Action<ClientWriter, T> _writeValue;
|
||||
private readonly CancellationToken _cancellationToken;
|
||||
|
||||
internal BackgroundWriter(ClientWriter writer, ImmutableArray<T> data, Action<ClientWriter, T> writeValue, CancellationToken cancellationToken)
|
||||
{
|
||||
_writer = writer;
|
||||
_writeValue = writeValue;
|
||||
_data = data;
|
||||
_cancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
internal Task WriteAsync()
|
||||
{
|
||||
return Task.Run(() => GoOnBackground(), _cancellationToken);
|
||||
}
|
||||
|
||||
private void GoOnBackground()
|
||||
{
|
||||
foreach (var item in _data)
|
||||
{
|
||||
if (_cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
_writer.Write(TestDataKind.Value);
|
||||
_writeValue(_writer, item);
|
||||
}
|
||||
|
||||
_writer.Write(TestDataKind.EndOfData);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility for reading a collection of <see cref="{T}"/> values from the given
|
||||
/// <see cref="ClientReader"/> value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
private sealed class BackgroundReader<T> where T : class
|
||||
{
|
||||
private readonly ConcurrentQueue<T?> _queue;
|
||||
private readonly ClientReader _reader;
|
||||
private readonly Func<ClientReader, T> _readValue;
|
||||
|
||||
internal ClientReader Reader => _reader;
|
||||
|
||||
internal BackgroundReader(ConcurrentQueue<T?> queue, ClientReader reader, Func<ClientReader, T> readValue)
|
||||
{
|
||||
_queue = queue;
|
||||
_reader = reader;
|
||||
_readValue = readValue;
|
||||
}
|
||||
|
||||
internal Task ReadAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return Task.Run(() => GoOnBackground(cancellationToken), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will be called on a background thread to read the results of the test from the
|
||||
/// named pipe client stream.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private void GoOnBackground(CancellationToken cancellationToken)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var kind = _reader.ReadKind();
|
||||
if (kind != TestDataKind.Value)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var value = _readValue(_reader);
|
||||
_queue.Enqueue(value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// TODO: Happens when the connection unexpectedly closes on us. Need to surface this
|
||||
// to the user.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Signal we are done
|
||||
_queue.Enqueue(null);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class BackgroundProducer<T> where T : class
|
||||
{
|
||||
private const int MaxResultPerTick = 1000;
|
||||
|
||||
private readonly Connection _connection;
|
||||
private readonly ConcurrentQueue<T?> _queue;
|
||||
private readonly DispatcherTimer _timer;
|
||||
private readonly Action<List<T>> _callback;
|
||||
private readonly int _maxPerTick;
|
||||
private readonly TaskCompletionSource<bool> _taskCompletionSource;
|
||||
|
||||
internal Task Task => _taskCompletionSource.Task;
|
||||
|
||||
internal BackgroundProducer(
|
||||
Connection connection,
|
||||
Dispatcher dispatcher,
|
||||
ConcurrentQueue<T?> queue,
|
||||
Action<List<T>> callback,
|
||||
int maxResultPerTick = MaxResultPerTick,
|
||||
TimeSpan? interval = null)
|
||||
{
|
||||
_connection = connection;
|
||||
_queue = queue;
|
||||
_maxPerTick = maxResultPerTick;
|
||||
_callback = callback;
|
||||
_timer = new DispatcherTimer(
|
||||
interval ?? TimeSpan.FromMilliseconds(500),
|
||||
DispatcherPriority.Normal,
|
||||
OnTimerTick,
|
||||
dispatcher);
|
||||
_taskCompletionSource = new TaskCompletionSource<bool>();
|
||||
}
|
||||
|
||||
private void OnTimerTick(object sender, EventArgs e)
|
||||
{
|
||||
var i = 0;
|
||||
var list = new List<T>();
|
||||
var isDone = false;
|
||||
while (i < _maxPerTick && _queue.TryDequeue(out T? value))
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
isDone = true;
|
||||
break;
|
||||
}
|
||||
|
||||
list.Add(value);
|
||||
}
|
||||
|
||||
_callback(list);
|
||||
|
||||
if (isDone)
|
||||
{
|
||||
try
|
||||
{
|
||||
_timer.Stop();
|
||||
_connection.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_taskCompletionSource.SetResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.IO.Pipes;
|
||||
using Xunit.Runner.Data;
|
||||
|
||||
namespace Xunit.Runner.Wpf.Impl
|
||||
{
|
||||
internal sealed partial class RemoteTestUtil : ITestUtil
|
||||
{
|
||||
private sealed class Connection : IDisposable
|
||||
{
|
||||
private NamedPipeClientStream? _stream;
|
||||
private ClientReader _reader;
|
||||
|
||||
internal NamedPipeClientStream Stream => _stream ?? throw new ObjectDisposedException(nameof(Connection));
|
||||
|
||||
internal ClientReader Reader => _reader;
|
||||
|
||||
internal Connection(NamedPipeClientStream stream)
|
||||
{
|
||||
_stream = stream;
|
||||
_reader = new ClientReader(stream);
|
||||
}
|
||||
|
||||
internal void Dispose()
|
||||
{
|
||||
if (_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();
|
||||
_stream = null;
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Pipes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
using Xunit.Runner.Data;
|
||||
|
||||
namespace Xunit.Runner.Wpf.Impl
|
||||
{
|
||||
internal sealed partial class RemoteTestUtil : ITestUtil
|
||||
{
|
||||
private struct ProcessInfo
|
||||
{
|
||||
internal readonly string PipeName;
|
||||
internal readonly Process Process;
|
||||
|
||||
internal ProcessInfo(string pipeName, Process process)
|
||||
{
|
||||
PipeName = pipeName;
|
||||
Process = process;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dispatcher _dispatcher;
|
||||
private ProcessInfo? _processInfo;
|
||||
|
||||
internal RemoteTestUtil(Dispatcher dispatcher)
|
||||
{
|
||||
_dispatcher = dispatcher;
|
||||
_processInfo = StartWorkerProcess();
|
||||
}
|
||||
|
||||
private async Task<Connection> CreateConnection(string action, string argument, CancellationToken cancellationToken)
|
||||
{
|
||||
var pipeName = GetPipeName();
|
||||
|
||||
try
|
||||
{
|
||||
var stream = new NamedPipeClientStream(pipeName);
|
||||
await stream.ConnectAsync(cancellationToken);
|
||||
|
||||
var writer = new ClientWriter(stream);
|
||||
writer.Write(action);
|
||||
writer.Write(argument);
|
||||
|
||||
return new Connection(stream);
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
_processInfo?.Process.Kill();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Inherent race condition here. Just need to make sure the process is
|
||||
// dead as it can't even handle new connections.
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetPipeName()
|
||||
{
|
||||
var process = _processInfo?.Process;
|
||||
if (process != null && !process.HasExited)
|
||||
{
|
||||
return _processInfo.Value.PipeName;
|
||||
}
|
||||
|
||||
_processInfo = StartWorkerProcess();
|
||||
return _processInfo.Value.PipeName;
|
||||
}
|
||||
|
||||
private static ProcessInfo StartWorkerProcess()
|
||||
{
|
||||
var pipeName = $"xunit.runner.wpf.pipe.{Guid.NewGuid()}";
|
||||
var processStartInfo = new ProcessStartInfo();
|
||||
processStartInfo.FileName = typeof(Xunit.Runner.Worker.Program).Assembly.Location;
|
||||
processStartInfo.Arguments = $"{pipeName} {Process.GetCurrentProcess().Id}";
|
||||
processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
var process = Process.Start(processStartInfo);
|
||||
return new ProcessInfo(pipeName, process);
|
||||
}
|
||||
|
||||
private void RecycleProcess()
|
||||
{
|
||||
var process = _processInfo?.Process;
|
||||
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
|
||||
|
||||
Task ITestUtil.Discover(string assemblyFileName, Action<IEnumerable<TestCaseData>> testsDiscovered, CancellationToken cancellationToken)
|
||||
{
|
||||
return Discover(assemblyFileName, testsDiscovered, cancellationToken);
|
||||
}
|
||||
|
||||
Task ITestUtil.RunAll(string assemblyFileName, Action<IEnumerable<TestResultData>> testsFinished, CancellationToken cancellationToken)
|
||||
{
|
||||
return RunCore(Constants.ActionRunAll, assemblyFileName, ImmutableArray<string>.Empty, testsFinished, cancellationToken);
|
||||
}
|
||||
|
||||
Task ITestUtil.RunSpecific(string assemblyFileName, ImmutableArray<string> testCases, Action<IEnumerable<TestResultData>> testsFinished, CancellationToken cancellationToken)
|
||||
{
|
||||
return RunCore(Constants.ActionRunSpecific, assemblyFileName, testCases, testsFinished, cancellationToken);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace Xunit.Runner.Wpf.Impl
|
||||
private readonly IDictionary<string, FileSystemWatcher> watchedAssemblies = new Dictionary<string, FileSystemWatcher>();
|
||||
private readonly Dispatcher dispatcher;
|
||||
private bool isEnabled = false;
|
||||
private ReloadDebouncer debouncer;
|
||||
private ReloadDebouncer? debouncer;
|
||||
|
||||
public TestAssemblyWatcher(Dispatcher dispatcher)
|
||||
{
|
||||
@@ -0,0 +1,428 @@
|
||||
<Window
|
||||
x:Class="Xunit.Runner.Wpf.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform"
|
||||
xmlns:converters="clr-namespace:Xunit.Runner.Wpf.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
|
||||
xmlns:local="clr-namespace:Xunit.Runner.Wpf"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:vm="clr-namespace:Xunit.Runner.Wpf.ViewModel"
|
||||
Name="Main"
|
||||
Title="xUnit.net Test Runner"
|
||||
Width="525"
|
||||
Height="600"
|
||||
MinWidth="425"
|
||||
MinHeight="525"
|
||||
DataContext="{Binding Main, Source={StaticResource Locator}}"
|
||||
Icon="Artwork\Application.ico"
|
||||
ResizeMode="CanResizeWithGrip"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="Loaded">
|
||||
<cmd:EventToCommand Command="{Binding WindowLoadedCommand}" />
|
||||
</i:EventTrigger>
|
||||
|
||||
<i:EventTrigger EventName="Closing">
|
||||
<cmd:EventToCommand Command="{Binding WindowClosingCommand}" PassEventArgsToCommand="True" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
|
||||
<Window.Resources>
|
||||
<converters:TestStateConverter x:Key="TestStateConverter" />
|
||||
</Window.Resources>
|
||||
|
||||
<Grid local:CommandBindings.Registration="{Binding CommandBindings}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Menu Grid.Row="0">
|
||||
<MenuItem Header="_File">
|
||||
<MenuItem Command="{Binding ExitCommand}" Header="E_xit" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Assembly">
|
||||
<MenuItem Command="ApplicationCommands.Open" Header="_Open" />
|
||||
<MenuItem Header="R_ecent" ItemsSource="{Binding RecentAssemblies}">
|
||||
<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 />
|
||||
<MenuItem Command="{Binding AssemblyRemoveAllCommand}" Header="_Unload" />
|
||||
<MenuItem Command="{Binding AssemblyReloadAllCommand}" Header="_Reload" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{Binding AutoReloadAssembliesCommand}"
|
||||
Header="_Auto Reload Test Assemblies"
|
||||
IsCheckable="True"
|
||||
IsChecked="{Binding AutoReloadAssemblies}" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Project">
|
||||
<MenuItem Header="_Open" />
|
||||
<MenuItem Header="_Recent" />
|
||||
<Separator />
|
||||
<MenuItem Header="_Close" />
|
||||
<Separator />
|
||||
<MenuItem Header="_Save" />
|
||||
<MenuItem Header="Save _As..." />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" MinWidth="200px" />
|
||||
<ColumnDefinition Width="2*" MinWidth="200px" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<GroupBox
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="3"
|
||||
Header="Refinements">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Content="Search:" />
|
||||
<TextBox Grid.Row="1" Text="{Binding FilterString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
|
||||
<Label Grid.Row="2" Content="Assemblies:" />
|
||||
<ListBox
|
||||
Grid.Row="3"
|
||||
Height="175"
|
||||
ItemsSource="{Binding Assemblies}"
|
||||
SelectionMode="Extended">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="vm:TestAssemblyViewModel">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding DisplayName}" />
|
||||
|
||||
<TextBlock
|
||||
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>
|
||||
</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>
|
||||
|
||||
<Label Grid.Row="4" Content="Traits:" />
|
||||
|
||||
<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>
|
||||
</GroupBox>
|
||||
|
||||
<Grid Grid.Row="1" Grid.Column="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button
|
||||
Grid.Column="0"
|
||||
Margin="10,0,0,0"
|
||||
Command="{Binding RunAllCommand}"
|
||||
Content="_Run All" />
|
||||
<Button
|
||||
Grid.Column="1"
|
||||
Command="{Binding RunSelectedCommand}"
|
||||
Content="Run _Selected" />
|
||||
<Button
|
||||
Grid.Column="2"
|
||||
Margin="0,0,10,0"
|
||||
Command="{Binding CancelCommand}"
|
||||
Content="_Cancel" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="0" Grid.Column="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" MinHeight="200px" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="*" MinHeight="200px" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<GroupBox
|
||||
Grid.Row="0"
|
||||
Margin="3"
|
||||
Header="{Binding TestCasesCaption}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ToggleButton
|
||||
Grid.Column="0"
|
||||
Margin="0,4,2,4"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Command="{Binding TestFilterChanged}"
|
||||
IsChecked="{Binding FilterPassedTests}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Artwork\Passed_large.png" />
|
||||
<TextBlock
|
||||
Margin="4,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
Text="{Binding TestsPassed, StringFormat={}{0:#\,0}}" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
<ToggleButton
|
||||
Grid.Column="1"
|
||||
Margin="2,4"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Command="{Binding TestFilterChanged}"
|
||||
IsChecked="{Binding FilterFailedTests}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Artwork\Failed_large.png" />
|
||||
<TextBlock
|
||||
Margin="4,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
Text="{Binding TestsFailed, StringFormat={}{0:#\,0}}" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
<ToggleButton
|
||||
Grid.Column="2"
|
||||
Margin="2,4,0,4"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Command="{Binding TestFilterChanged}"
|
||||
IsChecked="{Binding FilterSkippedTests}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Artwork\Skipped_large.png" />
|
||||
<TextBlock
|
||||
Margin="4,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
Text="{Binding TestsSkipped, StringFormat={}{0:#\,0}}" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
|
||||
<ToggleButton
|
||||
Grid.Column="2"
|
||||
Margin="2,4,0,4"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Command="{Binding TestFilterChanged}"
|
||||
IsChecked="{Binding FilterRunningTests}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="Artwork\Running_large.png" />
|
||||
<TextBlock
|
||||
Margin="4,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
Text="{Binding TestsRunning, StringFormat={}{0:#\,0}}" />
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
|
||||
<ListBox
|
||||
x:Name="TestCases"
|
||||
Grid.Row="1"
|
||||
ItemsSource="{Binding FilteredTestCases}"
|
||||
SelectedItem="{Binding SelectedTestCase, Mode=TwoWay}"
|
||||
SelectionChanged="TestCases_SelectionChanged"
|
||||
SelectionMode="Extended">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="vm:TestCaseViewModel">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="20" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Image
|
||||
Grid.Column="0"
|
||||
Width="16"
|
||||
Margin="0,0,2,0"
|
||||
Source="{Binding Path=State, Mode=OneWay, Converter={StaticResource TestStateConverter}}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding DisplayName}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
<ListBox.InputBindings>
|
||||
<KeyBinding Key="Enter" Command="{Binding RunSelectedCommand}" />
|
||||
</ListBox.InputBindings>
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="MouseDoubleClick">
|
||||
<cmd:EventToCommand Command="{Binding Path=RunSelectedCommand}" PassEventArgsToCommand="False" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<GridSplitter
|
||||
Grid.Row="1"
|
||||
Height="3"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="White" />
|
||||
|
||||
<GroupBox
|
||||
Grid.Row="2"
|
||||
Margin="3"
|
||||
Header="Output">
|
||||
<TextBox
|
||||
FontFamily="Consolas"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding Output}"
|
||||
VerticalScrollBarVisibility="Visible" />
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
|
||||
<GridSplitter
|
||||
Grid.Column="0"
|
||||
Width="3"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="White" />
|
||||
|
||||
<ProgressBar
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="3"
|
||||
Foreground="{Binding Path=CurrentRunState, Converter={StaticResource TestStateConverter}, Mode=OneWay}"
|
||||
Maximum="{Binding MaximumProgress}"
|
||||
Minimum="0"
|
||||
Value="{Binding Path=TestsCompleted}" />
|
||||
</Grid>
|
||||
|
||||
<StackPanel Grid.Row="2">
|
||||
<Border
|
||||
Margin="3"
|
||||
BorderBrush="LightGray"
|
||||
BorderThickness="1" />
|
||||
<StatusBar>
|
||||
<StatusBar.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="16" />
|
||||
</Grid.ColumnDefinitions>
|
||||
</Grid>
|
||||
</ItemsPanelTemplate>
|
||||
</StatusBar.ItemsPanel>
|
||||
|
||||
<StatusBarItem Grid.Column="1">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock 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>
|
||||
</StatusBar>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using Xunit.Runner.Wpf.Persistence;
|
||||
|
||||
namespace Xunit.Runner.Wpf
|
||||
{
|
||||
using ViewModel;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public static Window Instance { get; private set; }
|
||||
|
||||
// WPF generates fields that are marked as non-nullable, but not definitely initialized.
|
||||
#pragma warning disable CS8618
|
||||
public MainWindow()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
#pragma warning restore
|
||||
|
||||
protected override void OnSourceInitialized(EventArgs e)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,9 @@ namespace Xunit.Runner.Wpf.Persistence
|
||||
|
||||
private static string GetWindowLayoutFileName(Window window) => $"{window.Name}_{WindowLayoutFileName}";
|
||||
|
||||
private static IsolatedStorageFile GetStorageFile() => IsolatedStorageFile.GetUserStoreForAssembly();
|
||||
private static IsolatedStorageFile GetStorageFile() => IsolatedStorageFile.GetUserStoreForDomain();
|
||||
|
||||
public static XmlTextReader OpenXmlFile(string fileName)
|
||||
public static XmlTextReader? OpenXmlFile(string fileName)
|
||||
{
|
||||
var storage = GetStorageFile();
|
||||
if (!storage.FileExists(fileName))
|
||||
@@ -4,24 +4,24 @@ using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// 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("Launcher")]
|
||||
[assembly: AssemblyTitle("xunit.runner.wpf")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Launcher")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2020")]
|
||||
[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
|
||||
// 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
|
||||
//inside a <PropertyGroup>. For example, if you are using US english
|
||||
//in your source files, set the <UICulture> to en-US. Then uncomment
|
||||
@@ -33,10 +33,10 @@ using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(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)
|
||||
)]
|
||||
|
||||
@@ -44,11 +44,11 @@ using System.Windows;
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// 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")]
|
||||
@@ -0,0 +1,63 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <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 {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Xunit.Runner.Wpf.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,9 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
public class AssemblyAndConfigFile
|
||||
{
|
||||
public string AssemblyFileName { get; }
|
||||
public string ConfigFileName { get; }
|
||||
public string? ConfigFileName { get; }
|
||||
|
||||
public AssemblyAndConfigFile(string assemblyFileName, string configFileName)
|
||||
public AssemblyAndConfigFile(string assemblyFileName, string? configFileName)
|
||||
{
|
||||
this.AssemblyFileName = Path.GetFullPath(assemblyFileName);
|
||||
if (configFileName != null)
|
||||
@@ -0,0 +1,923 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.CommandWpf;
|
||||
using Microsoft.Win32;
|
||||
using Microsoft.WindowsAPICodePack.Taskbar;
|
||||
using Xunit.Runner.Data;
|
||||
using Xunit.Runner.Wpf.Persistence;
|
||||
|
||||
namespace Xunit.Runner.Wpf.ViewModel
|
||||
{
|
||||
public class MainViewModel : ViewModelBase
|
||||
{
|
||||
private readonly Settings settings;
|
||||
|
||||
private readonly ITestUtil testUtil;
|
||||
private readonly ITestAssemblyWatcher assemblyWatcher;
|
||||
private readonly HashSet<string> allTestCaseUniqueIDs = new HashSet<string>();
|
||||
private readonly ObservableCollection<TestCaseViewModel> allTestCases = new ObservableCollection<TestCaseViewModel>();
|
||||
private readonly TraitCollectionView traitCollectionView = new TraitCollectionView();
|
||||
private readonly HashSet<string> runningTestSet = new HashSet<string>();
|
||||
|
||||
private CancellationTokenSource filterCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
private CancellationTokenSource? cancellationTokenSource;
|
||||
private bool isBusy;
|
||||
private SearchQuery searchQuery = new SearchQuery();
|
||||
private bool autoReloadAssemblies;
|
||||
|
||||
public ObservableCollection<TestAssemblyViewModel> Assemblies { get; } = new ObservableCollection<TestAssemblyViewModel>();
|
||||
public FilteredCollectionView<TestCaseViewModel, SearchQuery> FilteredTestCases { get; }
|
||||
public ObservableCollection<TraitViewModel> Traits => this.traitCollectionView.Collection;
|
||||
public bool AutoReloadAssemblies
|
||||
{
|
||||
get => autoReloadAssemblies;
|
||||
set
|
||||
{
|
||||
var oldVal = autoReloadAssemblies;
|
||||
autoReloadAssemblies = value;
|
||||
RaisePropertyChanged(nameof(AutoReloadAssemblies), oldVal, autoReloadAssemblies);
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<RecentAssemblyViewModel> RecentAssemblies { get; } = new ObservableCollection<RecentAssemblyViewModel>();
|
||||
|
||||
private ImmutableList<TestCaseViewModel>? 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 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()
|
||||
{
|
||||
this.settings = Settings.Load();
|
||||
|
||||
if (IsInDesignMode)
|
||||
{
|
||||
this.Assemblies.Add(new TestAssemblyViewModel(new AssemblyAndConfigFile(@"C:\Code\TestAssembly.dll", null)));
|
||||
}
|
||||
|
||||
CommandBindings = CreateCommandBindings();
|
||||
|
||||
this.testUtil = new Xunit.Runner.Wpf.Impl.RemoteTestUtil(Dispatcher.CurrentDispatcher);
|
||||
this.assemblyWatcher = new Impl.TestAssemblyWatcher(Dispatcher.CurrentDispatcher);
|
||||
this.TestCasesCaption = "Test Cases (0)";
|
||||
|
||||
this.FilteredTestCases = new FilteredCollectionView<TestCaseViewModel, SearchQuery>(
|
||||
allTestCases, TestCaseMatches, searchQuery, TestComparer.Instance);
|
||||
|
||||
this.FilteredTestCases.CollectionChanged += TestCases_CollectionChanged;
|
||||
|
||||
this.ExitCommand = new RelayCommand(OnExecuteExit);
|
||||
this.WindowLoadedCommand = new RelayCommand(OnExecuteWindowLoaded);
|
||||
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.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)
|
||||
{
|
||||
if (testCase.DisplayName.IndexOf(searchQuery.SearchString, StringComparison.CurrentCultureIgnoreCase) < 0)
|
||||
{
|
||||
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)
|
||||
{
|
||||
case TestState.Running:
|
||||
return noFilter || searchQuery.FilterRunningTests;
|
||||
|
||||
case TestState.Passed:
|
||||
return noFilter || searchQuery.FilterPassedTests;
|
||||
|
||||
case TestState.Skipped:
|
||||
return noFilter || searchQuery.FilterSkippedTests;
|
||||
|
||||
case TestState.Failed:
|
||||
return noFilter || searchQuery.FilterFailedTests;
|
||||
|
||||
case TestState.NotRun:
|
||||
return noFilter;
|
||||
|
||||
default:
|
||||
Debug.Assert(false, "What state is this test case in?");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void TestCases_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
UpdateTestCaseInfo(useSelected: false);
|
||||
ClearSelectionFlags();
|
||||
}
|
||||
|
||||
private void ClearSelectionFlags()
|
||||
{
|
||||
foreach (var test in this.allTestCases)
|
||||
{
|
||||
test.IsSelected = false;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateTestCaseInfo(bool useSelected)
|
||||
{
|
||||
var count = FilteredTestCases.Count;
|
||||
if (useSelected)
|
||||
{
|
||||
var selected = FilteredTestCases.Count(tc => tc.IsSelected);
|
||||
if (selected > 0)
|
||||
{
|
||||
count = selected;
|
||||
}
|
||||
}
|
||||
|
||||
TestCasesCaption = $"Test Cases ({count:#,0})";
|
||||
MaximumProgress = count;
|
||||
}
|
||||
|
||||
public List<TestAssemblyViewModel> SelectedAssemblies
|
||||
{
|
||||
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;
|
||||
public int TestsCompleted
|
||||
{
|
||||
get { return testsCompleted; }
|
||||
|
||||
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;
|
||||
public int TestsPassed
|
||||
{
|
||||
get { return testsPassed; }
|
||||
set { Set(ref testsPassed, value); }
|
||||
}
|
||||
|
||||
private int testsFailed = 0;
|
||||
public int TestsFailed
|
||||
{
|
||||
get { return testsFailed; }
|
||||
set { Set(ref testsFailed, value); }
|
||||
}
|
||||
|
||||
private int testsSkipped = 0;
|
||||
public int TestsSkipped
|
||||
{
|
||||
get { return testsSkipped; }
|
||||
set { Set(ref testsSkipped, value); }
|
||||
}
|
||||
|
||||
private int maximumProgress = int.MaxValue;
|
||||
public int MaximumProgress
|
||||
{
|
||||
get { return maximumProgress; }
|
||||
|
||||
set
|
||||
{
|
||||
Set(ref maximumProgress, value);
|
||||
UpdateProgress();
|
||||
}
|
||||
}
|
||||
|
||||
private TestState currentRunState;
|
||||
public TestState CurrentRunState
|
||||
{
|
||||
get { return currentRunState; }
|
||||
|
||||
set
|
||||
{
|
||||
Set(ref currentRunState, value);
|
||||
UpdateProgress();
|
||||
}
|
||||
}
|
||||
|
||||
private string output = string.Empty;
|
||||
public string Output
|
||||
{
|
||||
get { return output; }
|
||||
set { Set(ref output, value); }
|
||||
}
|
||||
|
||||
public string FilterString
|
||||
{
|
||||
get { return searchQuery.SearchString; }
|
||||
set
|
||||
{
|
||||
if (Set(ref searchQuery.SearchString, value))
|
||||
{
|
||||
FilterAfterDelay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FilterAfterDelay()
|
||||
{
|
||||
filterCancellationTokenSource.Cancel();
|
||||
filterCancellationTokenSource = new CancellationTokenSource();
|
||||
var token = filterCancellationTokenSource.Token;
|
||||
|
||||
Task
|
||||
.Delay(TimeSpan.FromMilliseconds(500), token)
|
||||
.ContinueWith(
|
||||
x =>
|
||||
{
|
||||
FilteredTestCases.FilterArgument = searchQuery;
|
||||
},
|
||||
token,
|
||||
TaskContinuationOptions.None,
|
||||
TaskScheduler.FromCurrentSynchronizationContext());
|
||||
}
|
||||
|
||||
private CommandBindingCollection CreateCommandBindings()
|
||||
{
|
||||
var openBinding = new CommandBinding(ApplicationCommands.Open, OnExecuteOpen);
|
||||
CommandManager.RegisterClassCommandBinding(typeof(MainViewModel), openBinding);
|
||||
|
||||
return new CommandBindingCollection
|
||||
{
|
||||
openBinding,
|
||||
};
|
||||
}
|
||||
|
||||
private async void OnExecuteOpen(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
var fileDialog = new OpenFileDialog
|
||||
{
|
||||
Filter = "Unit Test Assemblies|*.dll",
|
||||
Multiselect = true
|
||||
};
|
||||
|
||||
if (fileDialog.ShowDialog(Application.Current.MainWindow) != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var assemblies = fileDialog.FileNames.Select(x => new AssemblyAndConfigFile(x, configFileName: null));
|
||||
await AddAssemblies(assemblies);
|
||||
}
|
||||
|
||||
private async Task AddAssemblies(IEnumerable<AssemblyAndConfigFile> assemblies)
|
||||
{
|
||||
if (!assemblies.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var newAssemblyViewModels = new List<TestAssemblyViewModel>();
|
||||
|
||||
try
|
||||
{
|
||||
await this.ExecuteTestSessionOperation(() =>
|
||||
{
|
||||
var taskList = new List<Task>();
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
taskList.Add(this.testUtil.Discover(assembly.AssemblyFileName, this.OnTestsDiscovered, CancellationToken));
|
||||
|
||||
var assemblyViewModel = new TestAssemblyViewModel(assembly);
|
||||
|
||||
newAssemblyViewModels.Add(assemblyViewModel);
|
||||
this.Assemblies.Add(assemblyViewModel);
|
||||
this.settings.AddRecentAssembly(assembly.AssemblyFileName);
|
||||
|
||||
assemblyViewModel.State = AssemblyState.Loading;
|
||||
}
|
||||
|
||||
return taskList;
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var assemblyViewModel in newAssemblyViewModels)
|
||||
{
|
||||
assemblyViewModel.State = AssemblyState.Ready;
|
||||
assemblyWatcher.AddAssembly(assemblyViewModel.FileName);
|
||||
}
|
||||
|
||||
RebuildRecentAssembliesMenu();
|
||||
}
|
||||
}
|
||||
|
||||
private CancellationToken CancellationToken
|
||||
=> cancellationTokenSource == null
|
||||
? CancellationToken.None
|
||||
: cancellationTokenSource.Token;
|
||||
|
||||
public bool ReloadAssemblies(IEnumerable<string> assemblies)
|
||||
{
|
||||
if (IsBusy)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var testAssemblies = Assemblies.Where(assembly => assemblies.Contains(assembly.FileName));
|
||||
Application.Current.Dispatcher.InvokeAsync(() => ReloadAssemblies(testAssemblies));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task ReloadAssemblies(IEnumerable<TestAssemblyViewModel> assemblies)
|
||||
{
|
||||
try
|
||||
{
|
||||
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, CancellationToken));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsBusy
|
||||
{
|
||||
get { return isBusy; }
|
||||
set
|
||||
{
|
||||
isBusy = value;
|
||||
RunAllCommand.RaiseCanExecuteChanged();
|
||||
RunSelectedCommand.RaiseCanExecuteChanged();
|
||||
CancelCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnExecuteExit()
|
||||
{
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
|
||||
private async void OnExecuteWindowLoaded()
|
||||
{
|
||||
await AddAssemblies(ParseCommandLine(Environment.GetCommandLineArgs().Skip(1)));
|
||||
}
|
||||
|
||||
private void OnExecuteWindowClosing(CancelEventArgs e)
|
||||
{
|
||||
this.settings.Save();
|
||||
}
|
||||
|
||||
private IEnumerable<AssemblyAndConfigFile> ParseCommandLine(IEnumerable<string> enumerable)
|
||||
{
|
||||
while (enumerable.Any())
|
||||
{
|
||||
var assemblyFileName = enumerable.First();
|
||||
enumerable = enumerable.Skip(1);
|
||||
|
||||
var configFileName = (string?)null;
|
||||
if (IsConfigFile(enumerable.FirstOrDefault()))
|
||||
{
|
||||
configFileName = enumerable.First();
|
||||
enumerable = enumerable.Skip(1);
|
||||
}
|
||||
|
||||
yield return new AssemblyAndConfigFile(assemblyFileName, configFileName);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsConfigFile(string fileName)
|
||||
=> (fileName?.EndsWith(".config", StringComparison.OrdinalIgnoreCase) ?? false) ||
|
||||
(fileName?.EndsWith(".json", StringComparison.OrdinalIgnoreCase) ?? false);
|
||||
|
||||
private bool CanExecuteRunAll()
|
||||
=> !IsBusy && FilteredTestCases.Any();
|
||||
|
||||
private bool CanExecuteRunSelected()
|
||||
=> !IsBusy && SelectedTestCase != null;
|
||||
|
||||
private async void OnExecuteRunAll()
|
||||
{
|
||||
Debug.Assert(this.testsToRun == null);
|
||||
UpdateTestCaseInfo(useSelected: false);
|
||||
|
||||
await ExecuteTestSessionOperation(RunFilteredTests);
|
||||
|
||||
this.testsToRun = null;
|
||||
}
|
||||
|
||||
private List<Task> RunFilteredTests()
|
||||
{
|
||||
return RunTests(FilteredTestCases.ToImmutableList());
|
||||
}
|
||||
|
||||
private async void OnExecuteRunSelected()
|
||||
{
|
||||
Debug.Assert(this.testsToRun == null);
|
||||
Debug.Assert(this.SelectedTestCase != null);
|
||||
UpdateTestCaseInfo(useSelected: true);
|
||||
|
||||
await ExecuteTestSessionOperation(RunSelectedTests);
|
||||
|
||||
this.testsToRun = null;
|
||||
}
|
||||
|
||||
private List<Task> RunSelectedTests()
|
||||
{
|
||||
return RunTests(ImmutableList.CreateRange(this.FilteredTestCases.Where(tc => tc.IsSelected)));
|
||||
}
|
||||
|
||||
private List<Task> RunTests(ImmutableList<TestCaseViewModel> tests)
|
||||
{
|
||||
Debug.Assert(this.isBusy);
|
||||
Debug.Assert(this.cancellationTokenSource != null);
|
||||
Debug.Assert(this.testsToRun == null);
|
||||
|
||||
TestsCompleted = 0;
|
||||
TestsRunning = 0;
|
||||
TestsPassed = 0;
|
||||
TestsFailed = 0;
|
||||
TestsSkipped = 0;
|
||||
CurrentRunState = TestState.NotRun;
|
||||
Output = string.Empty;
|
||||
|
||||
this.testsToRun = tests;
|
||||
|
||||
foreach (var tc in this.testsToRun)
|
||||
{
|
||||
tc.State = TestState.NotRun;
|
||||
}
|
||||
|
||||
var runAll = this.testsToRun.Count == this.allTestCases.Count;
|
||||
var testSessionList = new List<Task>();
|
||||
|
||||
foreach (var assemblyFileName in this.testsToRun.Select(x => x.AssemblyFileName).Distinct())
|
||||
{
|
||||
Task task;
|
||||
if (runAll)
|
||||
{
|
||||
task = this.testUtil.RunAll(assemblyFileName, OnTestStateChange, CancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
var builder = ImmutableArray.CreateBuilder<string>();
|
||||
|
||||
foreach (var testCase in this.testsToRun)
|
||||
{
|
||||
if (testCase.AssemblyFileName == assemblyFileName)
|
||||
{
|
||||
builder.Add(testCase.UniqueID);
|
||||
}
|
||||
}
|
||||
|
||||
task = this.testUtil.RunSpecific(assemblyFileName, builder.ToImmutable(), OnTestStateChange, CancellationToken);
|
||||
}
|
||||
|
||||
testSessionList.Add(task);
|
||||
}
|
||||
|
||||
return testSessionList;
|
||||
}
|
||||
|
||||
private async Task ExecuteTestSessionOperation(Func<List<Task>> operation)
|
||||
{
|
||||
Debug.Assert(!this.IsBusy);
|
||||
Debug.Assert(this.cancellationTokenSource == null);
|
||||
|
||||
try
|
||||
{
|
||||
this.IsBusy = true;
|
||||
this.cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
var taskList = operation();
|
||||
await Task.WhenAll(taskList);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.cancellationTokenSource?.Cancel();
|
||||
MessageBox.Show(Application.Current.MainWindow, ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.cancellationTokenSource = null;
|
||||
this.IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTestsDiscovered(IEnumerable<TestCaseData> testCases)
|
||||
{
|
||||
var traitWorkerList = new List<TraitViewModel>();
|
||||
|
||||
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 OnTestStateChange(IEnumerable<TestResultData> testResultData)
|
||||
{
|
||||
Debug.Assert(this.testsToRun != null);
|
||||
|
||||
foreach (var result in testResultData)
|
||||
{
|
||||
var testCase = this.testsToRun.Single(x => x.UniqueID == result.TestCaseUniqueID);
|
||||
testCase.State = result.TestState;
|
||||
|
||||
if (result.TestState == TestState.Running)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanExecuteCancel()
|
||||
{
|
||||
return this.cancellationTokenSource != null && !this.cancellationTokenSource.IsCancellationRequested;
|
||||
}
|
||||
|
||||
private void OnExecuteCancel()
|
||||
{
|
||||
Debug.Assert(CanExecuteCancel());
|
||||
cancellationTokenSource?.Cancel();
|
||||
}
|
||||
|
||||
private void OnExecuteTraitCheckedChanged(TraitViewModel trait)
|
||||
{
|
||||
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
|
||||
{
|
||||
if (Set(ref searchQuery.FilterRunningTests, value))
|
||||
{
|
||||
FilterAfterDelay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool FilterPassedTests
|
||||
{
|
||||
get { return searchQuery.FilterPassedTests; }
|
||||
set
|
||||
{
|
||||
if (Set(ref searchQuery.FilterPassedTests, value))
|
||||
{
|
||||
FilterAfterDelay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool FilterFailedTests
|
||||
{
|
||||
get { return searchQuery.FilterFailedTests; }
|
||||
set
|
||||
{
|
||||
if (Set(ref searchQuery.FilterFailedTests, value))
|
||||
{
|
||||
FilterAfterDelay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool FilterSkippedTests
|
||||
{
|
||||
get { return searchQuery.FilterSkippedTests; }
|
||||
set
|
||||
{
|
||||
if (Set(ref searchQuery.FilterSkippedTests, value))
|
||||
{
|
||||
FilterAfterDelay();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TestComparer : IComparer<TestCaseViewModel>
|
||||
{
|
||||
public static TestComparer Instance { get; } = new TestComparer();
|
||||
|
||||
public int Compare(TestCaseViewModel x, TestCaseViewModel y)
|
||||
{
|
||||
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() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using GalaSoft.MvvmLight;
|
||||
using Xunit.Runner.Data;
|
||||
|
||||
namespace Xunit.Runner.Wpf.ViewModel
|
||||
{
|
||||
public class TestCaseViewModel : ViewModelBase
|
||||
{
|
||||
private TestState _state = TestState.NotRun;
|
||||
|
||||
public string DisplayName { get; }
|
||||
public string UniqueID { get; }
|
||||
public string SkipReason { get; }
|
||||
public string AssemblyFileName { get; }
|
||||
public ImmutableArray<TraitViewModel> Traits { get; }
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public bool HasSkipReason => !string.IsNullOrEmpty(this.SkipReason);
|
||||
|
||||
public TestState State
|
||||
{
|
||||
get { return _state; }
|
||||
set { Set(ref _state, value); }
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
if (!string.IsNullOrEmpty(skipReason))
|
||||
{
|
||||
_state = TestState.Skipped;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,9 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
|
||||
private class TraitViewModelComparer : IEqualityComparer<TraitViewModel>, IComparer<TraitViewModel>
|
||||
{
|
||||
public int Compare(TraitViewModel x, TraitViewModel y) => StringComparer.Ordinal.Compare(x.FullText, y.FullText);
|
||||
public bool Equals(TraitViewModel x, TraitViewModel y) => StringComparer.Ordinal.Equals(x.FullText, y.FullText);
|
||||
public int GetHashCode(TraitViewModel obj) => obj.FullText.GetHashCode();
|
||||
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); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
In App.xaml:
|
||||
<Application.Resources>
|
||||
<vm:ViewModelLocator xmlns:vm="clr-namespace:Xunit.Runner.Wpf"
|
||||
<vm:ViewModelLocator xmlns:vm="clr-namespace:xunit.runner.wpf"
|
||||
x:Key="Locator" />
|
||||
</Application.Resources>
|
||||
|
||||
@@ -12,9 +12,8 @@
|
||||
See http://www.galasoft.ch/mvvm
|
||||
*/
|
||||
|
||||
using CommonServiceLocator;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.Ioc;
|
||||
using Microsoft.Practices.ServiceLocation;
|
||||
|
||||
namespace Xunit.Runner.Wpf.ViewModel
|
||||
{
|
||||
@@ -46,13 +45,8 @@ namespace Xunit.Runner.Wpf.ViewModel
|
||||
}
|
||||
|
||||
public MainViewModel Main
|
||||
{
|
||||
get
|
||||
{
|
||||
return ServiceLocator.Current.GetInstance<MainViewModel>();
|
||||
}
|
||||
}
|
||||
|
||||
=> ServiceLocator.Current.GetInstance<MainViewModel>();
|
||||
|
||||
public static void Cleanup()
|
||||
{
|
||||
// TODO Clear the ViewModels
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="CommonServiceLocator" version="1.3" targetFramework="net452" />
|
||||
<package id="MvvmLight" version="5.3.0.0" targetFramework="net46" />
|
||||
<package id="MvvmLightLibs" version="5.3.0.0" targetFramework="net46" />
|
||||
<package id="NuGet.CommandLine" version="2.8.3" targetFramework="net46" />
|
||||
<package id="System.Collections" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Collections.Immutable" version="1.4.0-preview1-25305-02" targetFramework="net46" />
|
||||
<package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Globalization" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Linq" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Runtime" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Threading" version="4.3.0" targetFramework="net46" />
|
||||
<package id="Tvl.NuGet.BuildTasks" version="1.0.0-alpha002" targetFramework="net46" developmentDependency="true" />
|
||||
<package id="WindowsAPICodePack-Core" version="1.1.2" targetFramework="net46" />
|
||||
<package id="WindowsAPICodePack-Shell" version="1.1.1" targetFramework="net46" />
|
||||
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
|
||||
<package id="xunit.runner.utility" version="2.1.0" targetFramework="net46" />
|
||||
</packages>
|
||||
@@ -0,0 +1,220 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.props" Condition="Exists('..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{34FB519C-FB49-4B31-ACA2-7F7879311BCF}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Xunit.Runner.Wpf</RootNamespace>
|
||||
<AssemblyName>xunit.runner.wpf</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
<TargetFrameworkProfile />
|
||||
<NuPkgVersion Condition="'$(appveyor_build_version)' != ''">$(appveyor_build_version)</NuPkgVersion>
|
||||
<NuPkgVersion Condition="'$(NuPkgVersion)' == ''">1.0.0-local</NuPkgVersion>
|
||||
</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>
|
||||
<ApplicationIcon>Artwork\Application.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="GalaSoft.MvvmLight, Version=5.3.0.19026, Culture=neutral, PublicKeyToken=e7570ab207bcb616, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MvvmLightLibs.5.3.0.0\lib\net45\GalaSoft.MvvmLight.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="GalaSoft.MvvmLight.Extras, Version=5.3.0.19032, Culture=neutral, PublicKeyToken=669f0b5e8f868abf, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MvvmLightLibs.5.3.0.0\lib\net45\GalaSoft.MvvmLight.Extras.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="GalaSoft.MvvmLight.Platform, Version=5.3.0.19032, Culture=neutral, PublicKeyToken=5f873c45e98af8a1, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MvvmLightLibs.5.3.0.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="Microsoft.WindowsAPICodePack, Version=1.1.2.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\WindowsAPICodePack-Core.1.1.2\lib\Microsoft.WindowsAPICodePack.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.WindowsAPICodePack.Shell, Version=1.1.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\WindowsAPICodePack-Shell.1.1.1\lib\Microsoft.WindowsAPICodePack.Shell.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Collections.Immutable, Version=1.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Collections.Immutable.1.4.0-preview1-25305-02\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ComponentModel.Composition" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MvvmLightLibs.5.3.0.0\lib\net45\System.Windows.Interactivity.dll</HintPath>
|
||||
<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" />
|
||||
<Reference Include="xunit.runner.utility.desktop, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.runner.utility.2.1.0\lib\net35\xunit.runner.utility.desktop.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="App.xaml">
|
||||
<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.BackgroundRunner.cs" />
|
||||
<Compile Include="Impl\RemoteTestUtil.cs" />
|
||||
<Compile Include="Impl\TestAssemblyWatcher.cs" />
|
||||
<Compile Include="ITestAssemblyWatcher.cs" />
|
||||
<Compile Include="ITestUtil.cs" />
|
||||
<Compile Include="Persistence\Settings.cs" />
|
||||
<Compile Include="Persistence\Storage.cs" />
|
||||
<Compile Include="Persistence\Storage.WindowPlacement.cs" />
|
||||
<Compile Include="Extensions.FuncComparer.cs" />
|
||||
<Compile Include="ViewModel\RecentAssemblyViewModel.cs" />
|
||||
<Compile Include="ViewModel\TraitCollectionView.cs" />
|
||||
<Compile Include="ViewModel\AssemblyAndConfigFile.cs" />
|
||||
<Compile Include="ViewModel\MainViewModel.cs" />
|
||||
<Compile Include="ViewModel\SearchQuery.cs" />
|
||||
<Compile Include="ViewModel\TestCaseViewModel.cs" />
|
||||
<Compile Include="ViewModel\TestAssemblyViewModel.cs" />
|
||||
<Compile Include="ViewModel\TraitViewModel.Comparer.cs" />
|
||||
<Compile Include="ViewModel\TraitViewModel.cs" />
|
||||
<Compile Include="ViewModel\ViewModelLocator.cs" />
|
||||
<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>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<None Include="packages.config" />
|
||||
<AppDesigner Include="Properties\" />
|
||||
<None Include="xunit.runner.wpf.targets">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<NuGetManifest Include="xunit.runner.wpf.nuspec">
|
||||
<Version>$(NuPkgVersion)</Version>
|
||||
<PackageAnalysis>False</PackageAnalysis>
|
||||
</NuGetManifest>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SampleTestAssembly\SampleTestAssembly.csproj">
|
||||
<Project>{bdafb5dd-ffb3-4a94-a312-dfb080010846}</Project>
|
||||
<Name>SampleTestAssembly</Name>
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\xunit.runner.data\xunit.runner.data.csproj">
|
||||
<Project>{a1f579f4-443e-4f64-bc55-998ab86ff293}</Project>
|
||||
<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>
|
||||
<Resource Include="Artwork\Passed_small.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Artwork\Passed_large.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Artwork\Failed_small.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Artwork\Failed_large.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Artwork\Skipped_small.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Artwork\Skipped_large.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Artwork\Application.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Artwork\Running_large.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Artwork\Running_small.png" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.props'))" />
|
||||
<Error Condition="!Exists('..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.targets'))" />
|
||||
</Target>
|
||||
<Import Project="..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.targets" Condition="Exists('..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.targets')" />
|
||||
</Project>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0"?>
|
||||
<package >
|
||||
<metadata>
|
||||
<id>xunit.runner.wpf</id>
|
||||
<version>$version$</version>
|
||||
<title>xUnit.Runner.WPF</title>
|
||||
<authors>Pilchie</authors>
|
||||
<owners>Pilchie</owners>
|
||||
<projectUrl>https://github.com/Pilchie/xunit.runner.wpf</projectUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>XUnit Gui written in WPF</description>
|
||||
<tags>XUnit Gui test runner</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="bin\$Configuration$\*.dll" target="tools\"/>
|
||||
<file src="xunit.runner.wpf.targets" target="build\net45"/>
|
||||
<file src="bin\$Configuration$\*.exe" target="tools\" exclude="**\*vshost*"/>
|
||||
<file src="bin\$Configuration$\*.config" target="tools\" exclude="**\*vshost*"/>
|
||||
</files>
|
||||
</package>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<StartAction Condition="'$(StartActions)' == ''">Program</StartAction>
|
||||
<StartProgram Condition="'$(StartProgram)' == ''">$(MSBuildThisFileDirectory)..\tools\xunit.runner.wpf.exe</StartProgram>
|
||||
<StartArguments Condition="'$(StartArguments)' == ''">$(AssemblyName).dll</StartArguments>
|
||||
<StartWorkingDirectory Condition="'$(StartWorkingDirectory)' == ''">$(OutputDirectory)</StartWorkingDirectory>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||