2 Commits

Author SHA1 Message Date
Kevin Pilch a96ce278d2 Fix nullability warnings 2018-05-05 16:21:00 -07:00
Kevin Pilch a10c13626c Remove compilers package 2018-05-05 15:22:08 -07:00
98 changed files with 3716 additions and 3092 deletions
-201
View File
@@ -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.
-8
View File
@@ -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>
-26
View File
@@ -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();
}
}
}
-96
View File
@@ -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>
-71
View File
@@ -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;
}
}
}
}
-30
View File
@@ -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;
}
}
}
}
-7
View File
@@ -1,7 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>
+5 -4
View File
@@ -1,9 +1,10 @@
# 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 version, currently in WIP, uses the [AssemblyRunner](xunit.v3.runner.utility) found in xunit.v3.runner.utility to run the tests so that the external dlls are loaded 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).
Coming soon, a xunit runner for Revit.
![Screenshot](docs/screenshot.png)
[![Build status](https://ci.appveyor.com/api/projects/status/13dshnyj592mwe9e/branch/master?svg=true)](https://ci.appveyor.com/project/Pilchie/xunit-runner-wpf/branch/master)
+2 -2
View File
@@ -21,8 +21,8 @@ namespace SampleTestAssembly
//[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")]
+11 -19
View File
@@ -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">
+6 -7
View File
@@ -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>
-52
View File
@@ -1,52 +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:
- master
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: |
nuget push -ApiKey $env:APIKEY -Source https://api.nuget.org/v3/index.json $(Build.ArtifactStagingDirectory)/**/*.nupkg
env:
APIKEY: $(nuget-apikey)
+13
View File
@@ -0,0 +1,13 @@
Steps to make a new release
===========================
1. Make sure no one else is planning on doing anything that would trigger a build
2. Check the "next build number" on [AppVeyor](https://ci.appveyor.com/project/Pilchie/xunit-runner-wpf/settings)
3. Click on [releases](https://github.com/Pilchie/xunit.runner.wpf/releases) -> `Draft a new release`
4. Set the version to `v1.0.nextbuildnumber` from 2
5. Set the title to `v1.0.nextbuildnumber - Some reason for the release to exist`
6. Click `Publish`
7. This will create the release, and start a new build on AppVeyor
8. Download the .nupkg from the AppVeyor artifacts page for that new build - (e.g. https://ci.appveyor.com/project/Pilchie/xunit-runner-wpf/build/1.0.15/artifacts)
9. Go back to the release you created in 6, and add the nupkg, and write a changelog
10. Tell [@Pilchie](https://github.com/Pilchie) to upload the nupkg to NuGet.org
Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

-37
View File
@@ -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
-384
View File
@@ -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, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
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")));
}
}
}
-114
View File
@@ -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);
}
}
-421
View File
@@ -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;
}
}
}
-18
View File
@@ -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
}
}
-45
View File
@@ -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,992 +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.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;
var assemblies = StartupAssemblies.Select(x => new AssemblyAndConfigFile(x, 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].AssemblyFileName, 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,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,78 +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.3</Version>
</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>
+86
View File
@@ -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
}
}
+78
View File
@@ -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
}
}
+12
View File
@@ -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")]
+69
View File
@@ -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);
}
}
}
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace Xunit.Runner.Data
{
public enum TestDataKind
{
Value = 1,
EndOfData = 2
}
}
+51
View File
@@ -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>
+93
View File
@@ -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()
{
}
}
}
+53
View File
@@ -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);
}
});
}
}
}
+117
View File
@@ -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;
}
}
}
+44
View File
@@ -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")]
+160
View File
@@ -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);
}
});
}
}
}
+21
View File
@@ -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);
}
}
}
}
+5
View File
@@ -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>
+40
View File
@@ -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>
+14
View File
@@ -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>
+8
View File
@@ -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);
}
}
}
+100
View File
@@ -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(); }
}
+37
View File
@@ -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();
}
}
}
}
+153
View File
@@ -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)
{
+428
View File
@@ -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>
+59
View File
@@ -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")]
+63
View File
@@ -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)
+923
View File
@@ -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;
}
}
}
}
@@ -2,13 +2,12 @@
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 readonly TraitViewModel? _parent;
private bool? _isChecked;
private bool _isExpanded;
private string _text;
@@ -20,7 +19,7 @@ namespace Xunit.Runner.Wpf.ViewModel
{
}
private TraitViewModel(TraitViewModel parent, string text)
private TraitViewModel(TraitViewModel? parent, string text)
{
this._parent = parent;
this._isChecked = false;
@@ -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
+21
View File
@@ -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>
+220
View File
@@ -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>
+20
View File
@@ -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>