129 Commits

Author SHA1 Message Date
Kevin Pilch 41c9282023 Update to latest xunit, and target net472 and netstandard2.0 2018-12-02 17:01:12 -08:00
Kevin Pilch b7293a15f7 Generate AssemblyInfos, get rid of App.config, and change NuGet package to global tool 2018-12-02 16:22:07 -08:00
Kevin Pilch 8e6a0ea916 Don't create windows for worker processes 2018-12-01 12:56:01 -08:00
Kevin Pilch 709b166870 Revert to .NET 4.5.2 and xunit.runner.utility 2.1.0 2018-12-01 12:50:29 -08:00
Kevin Pilch 2251487778 Convert the rest of the projectS 2018-12-01 12:50:29 -08:00
Kevin Pilch 5e3aa442b8 Convert to .NET Core 3.0 2018-12-01 12:46:52 -08:00
Kevin Pilch c28059ca4c Merge pull request #88 from Pilchie/NoCompilerPackage
Stop using the compiler package
2018-12-01 12:42:52 -08:00
Kevin Pilch d93c706a3d Stop using the compiler package 2018-12-01 12:36:23 -08:00
Kevin Pilch e9dfb23e18 Merge pull request #84 from Eawvv/gui-enhancements
Gui enhancements - Run selected on enter or double click
2018-02-16 09:05:30 -08:00
Edwin Roerdink f6e8918bef execute test on enter key 2018-02-04 01:42:25 +01:00
Edwin Roerdink 18275067e9 execute test on double click 2018-02-04 01:19:04 +01:00
Kevin Pilch 39f89587fc Merge pull request #81 from heejaechang/fixDiscovery
made discovery to use same setting as run so that same tests are disc…
2017-11-01 09:24:29 -07:00
Heejae Chang 8c3c86d918 made wpf xunit runner to support showing/filtering running tests so that if we get deadlock or hang tests, we can see what the test it was running easily. 2017-10-31 17:23:22 -07:00
Heejae Chang 761f801e43 made discovery to use same setting as run so that same tests are discovered. 2017-10-31 14:56:33 -07:00
Kevin Pilch 643d9061c8 Merge pull request #79 from 333fred/fix-autoreload
Fix first open and default autoreload case
2017-06-05 16:17:11 -07:00
Fredric Silberberg 6d4a249a97 Fix first open and default autoreload case 2017-06-05 15:49:35 -07:00
Kevin Pilch 5ed3579b30 Merge pull request #78 from 333fred/screenshot
Added screenshot to readme.
2017-06-05 11:22:21 -07:00
Fredric Silberberg 1f55afd2e9 Added screenshot to readme. 2017-06-05 10:49:13 -07:00
Kevin Pilch c6c48067a7 Merge pull request #77 from 333fred/update-readme
Add nuget to readme
2017-06-05 10:32:28 -07:00
Fredric Silberberg ab5b3afea2 Add nuget to readme 2017-06-05 10:30:00 -07:00
Kevin Pilch feb8588961 Merge pull request #76 from sharwell/open-2017
Open solution in Visual Studio 2017
2017-06-03 14:42:28 -07:00
Kevin Pilch 08907d707c Merge pull request #75 from sharwell/include-binaries
Include binaries in the NuGet package
2017-06-03 14:42:07 -07:00
Sam Harwell de354d8095 Open solution in Visual Studio 2017
Since the project is using features from C# 7, update the solution to open
by default in a version of Visual Studio that supports them.
2017-06-03 16:40:14 -05:00
Sam Harwell dd668f753b Move .targets into a framework-specific folder
Fixes installation via project.json.
2017-06-03 16:38:56 -05:00
Sam Harwell a5712e104a Disable NuGet package analysis since this is a non-standard package layout 2017-06-03 16:30:06 -05:00
Sam Harwell f2e1c0fb5e Include binaries in NuGet package 2017-06-03 16:27:16 -05:00
Kevin Pilch 80fc0702d1 Merge pull request #74 from 333fred/autoreload
Autoreload
2017-06-03 13:57:52 -07:00
Fredric Silberberg 39c5a33d56 Use Microsoft.Net.Compilers, update all base system libraries 2017-06-02 16:40:48 -07:00
Fredric Silberberg 686404bb7f Remove from watch when removed from the program. 2017-06-02 16:25:17 -07:00
Fredric Silberberg e41071ddb0 Added autoreloading of test assemblies 2017-06-02 16:19:08 -07:00
Fredric Silberberg 13f0cedb64 Fix whitespace 2017-06-02 16:18:53 -07:00
Kevin Pilch 93601521fc Merge pull request #72 from sharwell/improved-sort
Implemented sorting for tests
2017-04-12 16:25:30 -07:00
Kevin Pilch 24522e8749 Merge pull request #71 from sharwell/cancellation
Propagate CancellationToken where applicable
2017-04-12 16:20:59 -07:00
Kevin Pilch 070d3bcb5e Merge pull request #70 from sharwell/unique-id
Fix crashes related to multiple tests with the same name
2017-04-12 16:20:16 -07:00
Sam Harwell 78501d237a Applied sort order for tests with and without filters applied 2017-03-25 17:36:20 -05:00
Sam Harwell 1e2fe65dfa Improve sort order to prefer to ignore case 2017-03-25 17:22:33 -05:00
Sam Harwell 0a52154c60 Propagate CancellationToken where applicable 2017-03-25 17:16:23 -05:00
Sam Harwell d26343057f Avoid adding duplicate tests
Fixes a crash when attempting to run tests in an assembly containing one or
more parameterized tests with duplicated parameter sets.
2017-03-25 16:06:16 -05:00
Kevin Pilch ba398d66ad Merge pull request #68 from sharwell/nuget-pack
Improved NuGet build integration
2017-03-25 12:01:23 -07:00
Sam Harwell 412c9b78fe Use the unique ID of test cases instead of display name for lookups
Fixes a crash when attempting to run the StyleCop Analyzers test suite.
2017-03-25 12:25:49 -05:00
Kevin Pilch 8eea004410 Merge pull request #69 from sharwell/fix-help
Fix unnecessarily loud typo in help text
2017-03-25 09:13:19 -07:00
Sam Harwell e52d9195ef Fix unnecessarily loud typo in help text 2017-03-25 09:02:06 -05:00
Sam Harwell 80a52c0f30 Improved NuGet build integration 2017-03-25 08:58:24 -05:00
Kevin Pilch a3ceb785fa Merge pull request #66 from mattwar/Updates
Fix progress bar and add support for running selected tests
2016-10-29 11:06:52 -07:00
Matt Warren 9a94bfd08d clear select flags on collection reset 2016-10-29 02:39:43 +00:00
Matt Warren cd411e5d56 support running selected tests 2016-10-29 02:33:26 +00:00
Matt Warren ed8641a896 Fix progress updates 2016-10-28 22:16:17 +00:00
Dustin Campbell bb9ed9106b Merge pull request #64 from DustinCampbell/fix-duplicate-recent-assembly
Fix off by one error resulting in duplicate assemblies in recent assembly list
2016-07-06 14:08:28 -07:00
Dustin Campbell 5e966ff461 Fix off by one error resulting in duplicate assemblies in recent assembly list 2016-07-06 14:02:36 -07:00
Kevin Pilch-Bisson 83bd1be9bf Add instructions on creating a release 2016-05-28 15:03:01 -07:00
Kevin Pilch-Bisson 84b0c7b4c8 Merge pull request #63 from Pilchie/nuget-props
Add targets file to make it easier to launch this from projects
2016-05-28 14:41:34 -07:00
Kevin Pilch-Bisson 7dc9b5d7d1 Add targets file to make it easier to launch this from projects 2016-05-28 14:32:33 -07:00
Kevin Pilch-Bisson 5de08e12a3 Merge pull request #62 from Pilchie/nuget-versioning
Use the appveyor version for the nupkg if present.
2016-05-28 13:36:07 -07:00
Kevin Pilch-Bisson c7c33af554 Use the appveyor version for the nupkg if present.
Also, do package analysis.
2016-05-28 13:27:08 -07:00
Kevin Pilch-Bisson fcff5239c9 Update AppVeyor badge 2016-05-28 12:57:35 -07:00
Kevin Pilch-Bisson cf72130426 Create README.md 2016-05-28 12:41:50 -07:00
Dustin Campbell c25bc6fe0e Merge pull request #59 from DustinCampbell/recent-assembly-menu
Add Assembly->Recent submenu
2016-05-20 11:32:51 -07:00
Dustin Campbell 9ff7831bff Add Assembly->Recent submenu 2016-05-20 11:11:23 -07:00
Kevin Pilch-Bisson 59139518da Merge pull request #58 from jmarolf/nuget-packages
Update nuget packages and generate nuget package on build
2016-05-19 17:06:49 -07:00
Jonathon Marolf a5b8c5506d making nuspec better 2016-05-18 19:01:27 -07:00
Jonathon Marolf ada4e79e6d Update nuget packages and generate nuget package on build 2016-05-18 17:30:49 -07:00
Kevin Pilch-Bisson ed5a4b83ba Merge pull request #57 from jmarolf/make-gridsplitter-selectable
making everything re-sizeable
2016-05-05 19:04:00 -07:00
Jonathon Marolf b7632c7294 making everything re-sizeable 2016-05-05 17:25:58 -07:00
Kevin Pilch-Bisson 3e5e987161 Merge pull request #56 from jmarolf/make-gridsplitter-selectable
give the gridsplitter a height so it is selectable
2016-05-05 13:44:36 -07:00
Jonathon Marolf b77a0cc857 give the gridsplitter a height so it is selectable 2016-05-05 13:32:41 -07:00
Dustin Campbell 7b292eee4d Merge pull request #53 from DustinCampbell/test-state-filters
Invert test case filter buttons and mark skipped tests during discovery
2015-12-06 11:06:54 -08:00
Dustin Campbell 50eb75cff6 Ensure that we don't crash during a run if a test is filtered out during the run 2015-12-06 11:02:14 -08:00
Dustin Campbell 4bc641e575 Invert test case filter buttons and mark skipped tests during discovery
The filter buttons are now unchecked by default, which has the meaning of no filter applied. Clicking the filter buttons the applies that filter. In addition, we now mark skipped tests when they are discovered.
2015-12-06 11:02:01 -08:00
Dustin Campbell ba48e2cb24 Merge pull request #52 from DustinCampbell/clean-up
Lots of clean up
2015-12-06 11:00:46 -08:00
Dustin Campbell cdf37c40ad Moar clean up 2015-12-06 10:57:45 -08:00
Dustin Campbell fece9168ca Clean up and refactor xunit.runner.worker code a bit 2015-12-06 10:56:43 -08:00
Dustin Campbell d4122f3a0c Correct namespaces to be pascal-cased
Conflicts:
	xunit.runner.wpf/ViewModel/MainViewModel.cs
2015-12-06 10:56:21 -08:00
Dustin Campbell 4f1e23d036 Merge pull request #51 from DustinCampbell/multi-select-traits
Allow multi-selecting traits for filtering
2015-12-06 10:51:56 -08:00
Dustin Campbell dbbb6b3fad Actually make trait filter work and get rid of lots unnecessary allocation 2015-12-06 07:14:36 -08:00
Kevin Pilch-Bisson 67184a4069 Merge pull request #50 from mattwar/taskbar
Add support for taskbar progress
2015-12-04 17:00:43 -08:00
Matt Warren 89d919dceb Add support for taskbar progress 2015-12-04 16:52:04 -08:00
Dustin Campbell f1879e1a06 Display traits in tree view with check boxes 2015-12-04 14:30:45 -08:00
Kevin Pilch-Bisson 8ceecdcf03 Merge pull request #49 from jasonmalinowski/add-scrollbar-to-output-pane
Show a scroll bar on the test output pane
2015-12-03 16:13:19 -08:00
Jason Malinowski d9667f4da0 Show a scroll bar on the test output pane 2015-12-03 16:07:06 -08:00
Kevin Pilch-Bisson a2636ff762 Merge pull request #47 from DustinCampbell/storage
Save and restore the position of the Main window
2015-12-03 15:58:28 -08:00
Kevin Pilch-Bisson d5d832535d Merge pull request #48 from mattwar/reload
Enable Unload and Reload menu items
2015-12-03 15:53:59 -08:00
Matt Warren db0ca68d65 Enable unload and reload menu items 2015-12-03 15:46:46 -08:00
Dustin Campbell 135eccb795 Save and restore the position of the Main window
It's pretty annoying that the XUnit runner always starts with the same width and height. This
change adds code to save and restore the position. It uses the Win32 `GetWindowPlacement` and
`SetWindowPlacement` APIs to ensure that the normal position is properly saved and restored
regardless of whether the window is maximized or not.
2015-12-03 08:37:59 -08:00
Kevin Pilch-Bisson bb5668e555 Merge pull request #46 from DustinCampbell/config-file-support
Support test assembly configuration files
2015-11-30 13:50:52 -08:00
Dustin Campbell 6a8f13dda2 Support test assembly configuration files 2015-11-30 13:45:41 -08:00
Kevin Pilch-Bisson 1b6ef5fd9a Merge pull request #44 from DustinCampbell/improve-start-run-perf
Improve start up performance of test runs
2015-11-05 10:18:43 -08:00
Kevin Pilch-Bisson 6f0ae76ad4 Merge pull request #43 from DustinCampbell/output-in-fixed-width-font
Update output window to use fixed-width font
2015-11-05 10:15:54 -08:00
Dustin Campbell 96428a7a96 Improve start up performance of test runs
Before a test run, we collect all of the test cases to use in the run. Unfortuantely, we were using the same slower test discovery mechanism that we previously used when loading test assemblies. This change switches that code path to use the faster mechanism that we've already put in place.
2015-11-05 06:56:58 -08:00
Dustin Campbell 1a347531d9 Update output window to use fixed-width font 2015-11-05 06:56:34 -08:00
Kevin Pilch-Bisson cd64e95e73 Merge pull request #42 from rchande/caseinsensitivefiltering
Make test name filtering case-insensitive
2015-10-23 10:33:00 -07:00
Ravi Chande e7cb648b28 Make test name filtering case-insensitive 2015-10-23 12:28:06 -05:00
Kevin Pilch-Bisson 8ff92c76d3 Merge pull request #41 from nguerrera/coreclr-worker
Make worker source compatible with .NET Core
2015-10-17 18:49:15 -07:00
Nick Guerrera 58e14e7b3d Make worker source compatible with .NET Core
* Work around lack of Environment.Exit
* Eliminate dependency on binary serializer
* Close -> Dispose
* Inline NamedPipeServerStream.MaxAllowedServerInstances = -1
2015-10-16 22:21:48 -07:00
Dustin Campbell 3128c8d5b5 Merge pull request #40 from Pilchie/recycle-worker-process
Recycle worker process on test discovery to enable us to disable AppDomains for that scenario
2015-10-14 16:14:30 -07:00
Dustin Campbell 0137b1a60b recycle worker process on test discovery to enable us to disable AppDomains for that scenario 2015-10-14 16:12:47 -07:00
Dustin Campbell 5d1a11eec7 Merge pull request #38 from CyrusNajmabadi/uiUpdate
Update UI more sparingly.  Avoid hotspot in Xunit discovery
2015-10-14 16:09:36 -07:00
Cyrus Najmabadi 836a4f59d6 Handle messages in batches 2015-10-14 15:06:25 -07:00
Cyrus Najmabadi cf33c99ea3 Update UI more sparingly. Avoid hotspot in Xunit discovery 2015-10-14 14:17:51 -07:00
Dustin Campbell 045fec126a Merge pull request #37 from Pilchie/serialize-less
No need to serialize the entire test case if we don't use it
2015-10-14 13:54:27 -07:00
Dustin Campbell e9735dfb2b No need to serialize the entire test case if we don't use it 2015-10-14 13:41:19 -07:00
Dustin Campbell 77ab0cbffe Merge pull request #36 from Pilchie/assembly-reload
Fix assembly reloading
2015-10-14 13:11:10 -07:00
Dustin Campbell bb1a2206b4 Fix assembly reloading
* Assembly reload didn't remove all test cases properly as it needed to compare file names with a case-insensitive match.
* Add "Discovering tests..." text to assembly view while loading tests.
2015-10-14 13:08:48 -07:00
Kevin Pilch-Bisson b232c55813 Merge branch 'dpoeschl-master' 2015-10-13 11:08:40 -07:00
Kevin Pilch-Bisson 95dc906281 Merge branch 'master' of https://github.com/dpoeschl/xunit.runner.wpf into dpoeschl-master
Conflicts:
	xunit.runner.wpf/Converters/TestStateConverter.cs
2015-10-13 11:08:06 -07:00
Kevin Pilch-Bisson 984beba184 Merge pull request #33 from Pilchie/application-icon
Use proper xUnit application icon
2015-10-13 11:03:16 -07:00
Kevin Pilch-Bisson 8b0fdadc3f Merge pull request #30 from Pilchie/xunit-2.1
Update to released xunit 2.1
2015-10-13 11:02:38 -07:00
Dustin Campbell 0cc8f3aba8 Use proper xUnit application icon 2015-10-13 09:21:17 -07:00
Kevin Pilch-Bisson fd72605d9d Update to released xunit 2.1 2015-10-12 12:13:26 -07:00
Kevin Pilch-Bisson cb39c7af29 Merge pull request #27 from Pilchie/ui-tweaks
A few UI tweaks
2015-10-12 09:23:59 -07:00
Kevin Pilch-Bisson 6aa5a6c0cd Merge pull request #23 from Pilchie/oneproc
Couple of fixes
2015-10-12 09:21:31 -07:00
Jared Parsons 90643fbf8f Worker can process tasks in parallel 2015-10-12 09:20:44 -07:00
Jared Parsons 0d96b29a7a Allow opening of multiple assemblies at a time 2015-10-12 09:20:42 -07:00
Jared Parsons d861f4a6ee Made connection async
The connection to the worker process was sync on the UI thread which
could lead to hangs.  Made it an async operation instead.

This did require me to upgrade to the 4.6 framework to use the
NamedPipeClientStream::ConnectAsync method.  If that is a blocker I can
downgrade back to 4.5.2 and use Thread tricks to get a similar effect.
2015-10-12 09:20:41 -07:00
Jared Parsons 3f894e0e7c Correct debugging workflow
There is now a single worker process model for all requests.  Debugging
workflow is to attach to this process and debug there.

closes #20
2015-10-12 09:19:44 -07:00
Kevin Pilch-Bisson 4371a4e837 Merge pull request #26 from Pilchie/issue-25
Fix issue where running a specific test was treated as running all of them
2015-10-12 09:11:57 -07:00
Dustin Campbell 65adde358c A few UI tweaks
Notably, the images for passed, failed and skipped have been replaced with images from the Visual Studio Image Library.
2015-10-12 08:58:11 -07:00
Dustin Campbell ae5e46d772 Fix issue where running a specific test was treated as running all of them 2015-10-12 06:28:42 -07:00
Jared Parsons 9934a70f52 Merge pull request #22 from Pilchie/reload
Implement Assembly reload and remove support
2015-08-29 10:10:55 -07:00
Jared Parsons 13afb6eea5 Fixed the race condition in the run and discover tasks 2015-08-27 17:13:55 -07:00
Jared Parsons e3a17c5308 Respond to PR feedback
Handled everything but the race condition.  Going to fix that in a
separate commit.
2015-08-27 16:41:14 -07:00
Jared Parsons 89be98bebc Naming consistency 2015-08-23 23:09:23 -07:00
Jared Parsons 9e5ac70234 Fix reload bugs
Fixes a couple of bugs in the Reload / Remove logic.
2015-08-23 23:08:00 -07:00
Jared Parsons b35da545d6 Added remove all assemblies menu item 2015-08-23 23:01:54 -07:00
Jared Parsons 3a7d01b87e Add Assembly reload support
Can now reload individual or all currently loaded assemblies.

closes #2
2015-08-23 21:25:21 -07:00
Jared Parsons 0e24227de0 Ability to clear trait selection
This should fulfill issue #7
2015-08-23 21:00:28 -07:00
Jared Parsons 65f3fc970e Test run can be filtered by traits 2015-08-23 20:50:59 -07:00
Jared Parsons 06f1c8c703 Traits displaying in the UI 2015-08-23 19:20:34 -07:00
Jared Parsons 29aa127230 Merge pull request #19 from Pilchie/remote
Move discover and execution to a remote process
2015-08-22 12:02:28 -07:00
David Poeschl 0e20339f7b Use darker yellow for "skipped" ProgressBar foreground
Fixes #15

Switch the yellow color used in the ProgressBar for skipped tests from
Brushes.Yellow (#FFFF00) to #EBCA00.
2015-08-17 09:17:36 -07:00
84 changed files with 2745 additions and 1593 deletions
+1
View File
@@ -214,3 +214,4 @@ FakesAssemblies/
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
/Settings.XamlStyle
+10
View File
@@ -0,0 +1,10 @@
# xunit.runner.wpf
XUnit Gui written in WPF
A simple replacement for the old winforms xunit.gui with support for xunit 2.0.
Find it on NuGet at [xunit.runner.wpf](https://www.nuget.org/packages/xunit.runner.wpf).
![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)
@@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("SampleTestAssembly")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SampleTestAssembly")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("bdafb5dd-ffb3-4a94-a312-dfb080010846")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
+6 -73
View File
@@ -1,78 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\xunit.core.2.1.0-beta4-build3109\build\portable-net45+netcore45+wp8+wpa81\xunit.core.props" Condition="Exists('..\packages\xunit.core.2.1.0-beta4-build3109\build\portable-net45+netcore45+wp8+wpa81\xunit.core.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{BDAFB5DD-FFB3-4A94-A312-DFB080010846}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SampleTestAssembly</RootNamespace>
<AssemblyName>SampleTestAssembly</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TargetFramework>netstandard2.0</TargetFramework>
</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, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.assert, Version=2.1.0.3109, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.assert.2.1.0-beta4-build3109\lib\dotnet\xunit.assert.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.core, Version=2.1.0.3109, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.core.2.1.0-beta4-build3109\lib\dotnet\xunit.core.dll</HintPath>
<Private>True</Private>
</Reference>
<PackageReference Include="xunit" Version="2.4.1" />
</ItemGroup>
<ItemGroup>
<Compile Include="Class1.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\xunit.core.2.1.0-beta4-build3109\build\portable-net45+netcore45+wp8+wpa81\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.1.0-beta4-build3109\build\portable-net45+netcore45+wp8+wpa81\xunit.core.props'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="xunit" version="2.1.0-beta4-build3109" targetFramework="net452" />
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
<package id="xunit.assert" version="2.1.0-beta4-build3109" targetFramework="net452" />
<package id="xunit.core" version="2.1.0-beta4-build3109" targetFramework="net452" />
<package id="xunit.extensibility.core" version="2.1.0-beta4-build3109" targetFramework="net452" />
</packages>
+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

+1 -5
View File
@@ -1,11 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace xunit.runner.data
namespace Xunit.Runner.Data
{
public sealed class ClientReader : IDisposable
{
+1 -5
View File
@@ -1,11 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace xunit.runner.data
namespace Xunit.Runner.Data
{
public sealed class ClientWriter : IDisposable
{
+2 -6
View File
@@ -1,10 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text;
namespace xunit.runner.data
namespace Xunit.Runner.Data
{
public static class Constants
{
@@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("xunit.runner.data")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("xunit.runner.data")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("a1f579f4-443e-4f64-bc55-998ab86ff293")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
+43 -12
View File
@@ -1,38 +1,69 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace xunit.runner.data
namespace Xunit.Runner.Data
{
public sealed class TestCaseData
{
public string SerializedForm { get; set; }
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 serializedForm, string displayName, string assemblyPath)
public TestCaseData(string displayName, string uniqueID, string skipReason, string assemblyPath, Dictionary<string, List<string>> traitMap)
{
SerializedForm = serializedForm;
DisplayName = displayName;
UniqueID = uniqueID;
SkipReason = skipReason;
AssemblyPath = assemblyPath;
TraitMap = traitMap;
}
public static TestCaseData ReadFrom(BinaryReader reader)
{
var serializedForm = reader.ReadString();
var displayName = reader.ReadString();
var uniqueID = reader.ReadString();
var skipReason = reader.ReadString();
var assemblyPath = reader.ReadString();
return new TestCaseData(serializedForm, displayName, assemblyPath);
var count = reader.ReadInt32();
var traitMap = new Dictionary<string, List<string>>(count);
for (int i = 0; i < count; i++)
{
var key = reader.ReadString();
var valueCount = reader.ReadInt32();
var values = new List<string>(valueCount);
for (int j = 0; j < valueCount; j++)
{
values.Add(reader.ReadString());
}
traitMap.Add(key, values);
}
return new TestCaseData(displayName, uniqueID, skipReason, assemblyPath, traitMap);
}
public void WriteTo(BinaryWriter writer)
{
writer.Write(SerializedForm);
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);
}
}
}
}
}
+1 -7
View File
@@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace xunit.runner.data
namespace Xunit.Runner.Data
{
public enum TestDataKind
{
+9 -9
View File
@@ -1,11 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace xunit.runner.data
namespace Xunit.Runner.Data
{
/// <summary>
/// Note: More severe states are higher numbers.
@@ -15,6 +10,7 @@ namespace xunit.runner.data
{
All = 0,
NotRun,
Running,
Passed,
Skipped,
Failed,
@@ -23,12 +19,14 @@ namespace xunit.runner.data
public sealed class TestResultData
{
public string TestCaseDisplayName { get; set; }
public string TestCaseUniqueID { get; set; }
public TestState TestState { get; set; }
public string Output { get; set; }
public TestResultData(string displayName, TestState state, string output = "")
public TestResultData(string displayName, string uniqueID, TestState state, string output = "")
{
TestCaseDisplayName = displayName;
TestCaseUniqueID = uniqueID;
TestState = state;
Output = output;
}
@@ -36,14 +34,16 @@ namespace xunit.runner.data
public static TestResultData ReadFrom(BinaryReader reader)
{
var displayName = reader.ReadString();
var uniqueID = reader.ReadString();
var state = (TestState)reader.ReadInt32();
var output = reader.ReadString();
return new TestResultData(displayName, state, output);
return new TestResultData(displayName, uniqueID, state, output);
}
public void WriteTo(BinaryWriter writer)
{
writer.Write(TestCaseDisplayName);
writer.Write(TestCaseUniqueID);
writer.Write((int)TestState);
writer.Write(Output);
}
+5 -56
View File
@@ -1,59 +1,8 @@
<?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')" />
<Project Sdk="Microsoft.NET.Sdk">
<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>
<RootNamespace>Xunit.Runner.Data</RootNamespace>
<TargetFramework>netstandard2.0</TargetFramework>
</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>
+3 -3
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
</startup>
</configuration>
</configuration>
+110
View File
@@ -0,0 +1,110 @@
#if NETFRAMEWORK
// Taken from https://github.com/xunit/xunit/blob/master/src/common/AssemblyResolution/AssemblyHelper_Desktop.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Xunit
{
/// <summary>
/// This class provides assistance with assembly resolution for missing assemblies.
/// </summary>
class AssemblyHelper : LongLivedMarshalByRefObject, IDisposable
{
static readonly string[] Extensions = { ".dll", ".exe" };
readonly string directory;
readonly IMessageSink internalDiagnosticsMessageSink;
readonly Dictionary<string, Assembly> lookupCache = new Dictionary<string, Assembly>();
/// <summary>
/// Constructs an instance using the given <paramref name="directory"/> for resolution.
/// </summary>
/// <param name="directory">The directory to use for resolving assemblies.</param>
public AssemblyHelper(string directory) : this(directory, null) { }
/// <summary>
/// Constructs an instance using the given <paramref name="directory"/> for resolution.
/// </summary>
/// <param name="directory">The directory to use for resolving assemblies.</param>
/// <param name="internalDiagnosticsMessageSink">The message sink to send internal diagnostics messages to</param>
public AssemblyHelper(string directory, IMessageSink internalDiagnosticsMessageSink)
{
this.directory = directory;
this.internalDiagnosticsMessageSink = internalDiagnosticsMessageSink;
AppDomain.CurrentDomain.AssemblyResolve += Resolve;
}
/// <inheritdoc/>
public void Dispose()
=> AppDomain.CurrentDomain.AssemblyResolve -= Resolve;
Assembly LoadAssembly(AssemblyName assemblyName)
{
if (lookupCache.TryGetValue(assemblyName.Name, out var result))
return result;
var path = Path.Combine(directory, assemblyName.Name);
result = ResolveAndLoadAssembly(path, out var resolvedAssemblyPath);
if (internalDiagnosticsMessageSink != null)
{
if (result == null)
internalDiagnosticsMessageSink.OnMessage(new DiagnosticMessage($"[AssemblyHelper_Desktop.LoadAssembly] Resolution for '{assemblyName.Name}' failed, passed down to next resolver"));
else
internalDiagnosticsMessageSink.OnMessage(new DiagnosticMessage($"[AssemblyHelper_Desktop.LoadAssembly] Resolved '{assemblyName.Name}' to '{resolvedAssemblyPath}'"));
}
lookupCache[assemblyName.Name] = result;
return result;
}
Assembly Resolve(object sender, ResolveEventArgs args)
=> LoadAssembly(new AssemblyName(args.Name));
Assembly ResolveAndLoadAssembly(string pathWithoutExtension, out string resolvedAssemblyPath)
{
foreach (var extension in Extensions)
{
resolvedAssemblyPath = pathWithoutExtension + extension;
try
{
if (File.Exists(resolvedAssemblyPath))
return Assembly.LoadFrom(resolvedAssemblyPath);
}
catch { }
}
resolvedAssemblyPath = null;
return null;
}
/// <summary>
/// Subscribes to the appropriate assembly resolution event, to provide automatic assembly resolution for
/// an assembly and any of its dependencies. Depending on the target platform, this may include the use
/// of the .deps.json file generated during the build process.
/// </summary>
/// <returns>An object which, when disposed, un-subscribes.</returns>
public static IDisposable SubscribeResolveForAssembly(string assemblyFileName, IMessageSink internalDiagnosticsMessageSink = null)
=> new AssemblyHelper(Path.GetDirectoryName(Path.GetFullPath(assemblyFileName)), internalDiagnosticsMessageSink);
/// <summary>
/// Subscribes to the appropriate assembly resolution event, to provide automatic assembly resolution for
/// an assembly and any of its dependencies. Depending on the target platform, this may include the use
/// of the .deps.json file generated during the build process.
/// </summary>
/// <returns>An object which, when disposed, un-subscribes.</returns>
public static IDisposable SubscribeResolveForAssembly(Type typeInAssembly, IMessageSink internalDiagnosticsMessageSink = null)
=> new AssemblyHelper(Path.GetDirectoryName(typeInAssembly.Assembly.Location), internalDiagnosticsMessageSink);
}
}
#endif
+2 -6
View File
@@ -1,12 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace xunit.runner.worker
namespace Xunit.Runner.Worker
{
internal abstract class Connection : IDisposable
{
@@ -56,7 +52,7 @@ namespace xunit.runner.worker
protected override void DisposeCore()
{
_stream.Close();
_stream.Dispose();
}
internal override void WaitForClientConnect()
+28 -34
View File
@@ -1,59 +1,53 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using System.IO;
using Xunit.Abstractions;
using xunit.runner.data;
using Xunit.Runner.Data;
using Xunit.Runner.Worker.MessageSinks;
namespace xunit.runner.worker
namespace Xunit.Runner.Worker
{
internal sealed class DiscoverUtil
internal sealed class DiscoverUtil : XunitUtil
{
private sealed class Impl : TestMessageVisitor<IDiscoveryCompleteMessage>
private sealed class TestDiscoverySink : BaseTestDiscoverySink
{
private readonly ITestFrameworkDiscoverer _discoverer;
private readonly ClientWriter _writer;
internal Impl(ITestFrameworkDiscoverer discoverer, ClientWriter writer)
internal TestDiscoverySink(ClientWriter writer)
{
_discoverer = discoverer;
_writer = writer;
}
protected override bool Visit(ITestCaseDiscoveryMessage testCaseDiscovered)
protected override bool ShouldContinue => _writer.IsConnected;
protected override void OnTestDiscovered(ITestCaseDiscoveryMessage testCaseDiscovered)
{
var testCase = testCaseDiscovered.TestCase;
var testCaseData = new TestCaseData(
_discoverer.Serialize(testCase),
testCase.DisplayName,
testCaseDiscovered.TestAssembly.Assembly.AssemblyPath);
testCase.UniqueID,
testCase.SkipReason,
testCaseDiscovered.TestAssembly.Assembly.AssemblyPath,
testCase.Traits);
Console.WriteLine(testCase.DisplayName);
_writer.Write(TestDataKind.Value);
_writer.Write(testCaseData);
return _writer.IsConnected;
}
}
internal static void Go(string fileName, Stream stream)
internal static void Go(string assemblyFileName, Stream stream)
{
using (AssemblyHelper.SubscribeResolve())
using (var xunit = new XunitFrontController(
useAppDomain: true,
assemblyFileName: fileName,
diagnosticMessageSink: new MessageVisitor(),
shadowCopy: false))
using (var writer = new ClientWriter(stream))
using (var impl = new Impl(xunit, writer))
{
xunit.Find(includeSourceInformation: false, messageSink: impl, discoveryOptions: TestFrameworkOptions.ForDiscovery());
impl.Finished.WaitOne();
writer.Write(TestDataKind.EndOfData);
}
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,53 @@
using System;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Xunit.Runner.Worker.MessageSinks
{
/// <summary>
/// An Xunit <see cref="IMessageSink"/> implementation without the dispatch overhead of <see cref="TestMessageVisitor"/>
/// and <see cref="TestMessageVisitor{TCompleteMessage}"/>.
/// </summary>
internal abstract class BaseMessageSink : LongLivedMarshalByRefObject, IMessageSink, IDisposable
{
private bool _disposed;
protected BaseMessageSink()
{
}
~BaseMessageSink()
{
Dispose(false);
}
protected virtual void DisposeCore(bool disposing)
{
}
private void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
DisposeCore(disposing);
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(true);
}
protected abstract bool OnMessage(IMessageSinkMessage message);
bool IMessageSink.OnMessage(IMessageSinkMessage message)
{
return OnMessage(message);
}
}
}
@@ -0,0 +1,40 @@
using System.Threading;
using Xunit.Abstractions;
namespace Xunit.Runner.Worker.MessageSinks
{
internal abstract class BaseTestDiscoverySink : BaseMessageSink
{
public ManualResetEvent Finished { get; }
protected BaseTestDiscoverySink()
{
Finished = new ManualResetEvent(false);
}
protected override void DisposeCore(bool disposing)
{
Finished.Dispose();
}
protected override bool OnMessage(IMessageSinkMessage message)
{
var discoveryMessage = message as ITestCaseDiscoveryMessage;
if (discoveryMessage != null)
{
OnTestDiscovered(discoveryMessage);
}
if (message is IDiscoveryCompleteMessage)
{
Finished.Set();
}
return ShouldContinue;
}
protected virtual bool ShouldContinue => true;
protected abstract void OnTestDiscovered(ITestCaseDiscoveryMessage testCaseDiscovered);
}
}
@@ -0,0 +1,61 @@
using System.Threading;
using Xunit.Abstractions;
namespace Xunit.Runner.Worker.MessageSinks
{
internal abstract class BaseTestRunSink : BaseMessageSink
{
public ManualResetEvent Finished { get; }
protected BaseTestRunSink()
{
Finished = new ManualResetEvent(false);
}
protected override void DisposeCore(bool disposing)
{
Finished.Dispose();
}
protected override bool OnMessage(IMessageSinkMessage message)
{
var testStarted = message as ITestStarting;
if (testStarted != null)
{
OnTestStarted(testStarted);
}
var testFailed = message as ITestFailed;
if (testFailed != null)
{
OnTestFailed(testFailed);
}
var testPassed = message as ITestPassed;
if (testPassed != null)
{
OnTestPassed(testPassed);
}
var testSkipped = message as ITestSkipped;
if (testSkipped != null)
{
OnTestSkipped(testSkipped);
}
if (message is ITestAssemblyFinished)
{
Finished.Set();
}
return ShouldContinue;
}
protected virtual bool ShouldContinue => true;
protected abstract void OnTestStarted(ITestStarting testStarted);
protected abstract void OnTestFailed(ITestFailed testFailed);
protected abstract void OnTestPassed(ITestPassed testPassed);
protected abstract void OnTestSkipped(ITestSkipped testSkipped);
}
}
@@ -0,0 +1,12 @@
using Xunit.Abstractions;
namespace Xunit.Runner.Worker.MessageSinks
{
internal class DiagnosticSink : BaseMessageSink
{
protected override bool OnMessage(IMessageSinkMessage message)
{
return true;
}
}
}
-18
View File
@@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
namespace xunit.runner.worker
{
internal sealed class MessageVisitor : TestMessageVisitor
{
public override bool OnMessage(IMessageSinkMessage message)
{
return base.OnMessage(message);
}
}
}
+14 -76
View File
@@ -1,13 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading.Tasks;
using xunit.runner.data;
namespace xunit.runner.worker
namespace Xunit.Runner.Worker
{
public static class Program
{
@@ -16,91 +11,34 @@ namespace xunit.runner.worker
public static int Main(string[] args)
{
if (args.Length < 3)
if (args.Length < 2)
{
Usage();
return ExitError;
}
string pipeName = args[0];
string action = args[1];
string argument = args[2];
try
var pipeName = args[0];
var parentPid = Int32.Parse(args[1]);
var process = Process.GetProcessById(parentPid);
if (process == null)
{
using (var connection = CreateConnection(pipeName))
{
connection.WaitForClientConnect();
var stream = connection.Stream;
switch (action)
{
case Constants.ActionDiscover:
Discover(stream, argument);
break;
case Constants.ActionRunAll:
RunAll(stream, argument);
break;
case Constants.ActionRunSpecific:
RunSpecific(stream, argument);
break;
default:
Usage();
return ExitError;
}
connection.WaitForClientDone();
}
}
catch (Exception ex)
{
// Errors will happen during a rude shut down from the client. Print out to the screen
// for diagnostics and continue on.
Console.Error.WriteLine(ex.Message);
Console.WriteLine($"Invalid parent pid {parentPid}");
return ExitError;
}
Task.WaitAny(
Task.Run(() => process.WaitForExit()),
Task.Run(() => new Listener(pipeName).Go()));
return ExitSuccess;
}
private static Connection CreateConnection(string pipeName)
{
if (pipeName == "test")
{
return new TestConnection();
}
return new NamedPipeConnection(pipeName);
}
private static void Discover(Stream stream, string assemblyPath)
{
Console.WriteLine($"discover started: {assemblyPath}");
DiscoverUtil.Go(assemblyPath, stream);
Console.WriteLine("discover ended");
}
private static void RunAll(Stream stream, string assemblyPath)
{
Console.WriteLine($"run all started: {assemblyPath}");
RunUtil.RunAll(assemblyPath, stream);
Console.WriteLine("run all ended");
}
private static void RunSpecific(Stream stream, string assemblyPath)
{
Console.WriteLine($"run specific started: {assemblyPath}");
RunUtil.RunSpecific(assemblyPath, stream);
Console.WriteLine("run specific ended");
}
private static void Usage()
{
Console.WriteLine("xunit.runner.worker [pipe name] [action] [assembly path]");
Console.WriteLine("\tpipe name: Name of the pipe this worker should communicate on");
Console.WriteLine("\taction: Action performed by the worker (run or discover tests");
Console.WriteLine("\assembly path: Path of assembly to perform the action against");
Console.WriteLine("\taction: Action performed by the worker (run or discover tests)");
Console.WriteLine("\tassembly path: Path of assembly to perform the action against");
}
}
}
@@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("xunit.runner.worker")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("xunit.runner.worker")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("9df97a2b-0eb5-4b12-9f81-69dfac979814")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
+70 -66
View File
@@ -1,36 +1,41 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using xunit.runner.data;
using Xunit;
using Xunit.Runner.Data;
using Xunit.Abstractions;
using Xunit.Runner.Worker.MessageSinks;
namespace xunit.runner.worker
namespace Xunit.Runner.Worker
{
internal sealed class RunUtil
internal sealed class RunUtil : XunitUtil
{
private sealed class TestRunVisitor : TestMessageVisitor<ITestAssemblyFinished>
private sealed class TestRunSink : BaseTestRunSink
{
private readonly ClientWriter _writer;
public TestRunVisitor(ClientWriter writer)
public TestRunSink(ClientWriter writer)
{
_writer = writer;
}
private void Process(string displayName, TestState state, string output = "")
protected override bool ShouldContinue => _writer.IsConnected;
private void Process(string displayName, string uniqueID, TestState state, string output = "")
{
Console.WriteLine($"{state} - {displayName}");
var result = new TestResultData(displayName, state, output);
System.Diagnostics.Trace.WriteLine($"{state} - {displayName}");
var result = new TestResultData(displayName, uniqueID, state, output);
_writer.Write(TestDataKind.Value);
_writer.Write(result);
}
protected override bool Visit(ITestFailed testFailed)
protected override void OnTestStarted(ITestStarting testStarted)
{
Process(testStarted.TestCase.DisplayName, testStarted.TestCase.UniqueID, TestState.Running);
}
protected override void OnTestFailed(ITestFailed testFailed)
{
var displayName = testFailed.TestCase.DisplayName;
var builder = new StringBuilder();
@@ -43,53 +48,48 @@ namespace xunit.runner.worker
builder.AppendLine($"\tException stacktrace");
builder.AppendLine(testFailed.StackTraces[i]);
}
builder.AppendLine();
Process(testFailed.TestCase.DisplayName, TestState.Failed, builder.ToString());
return _writer.IsConnected;
Process(testFailed.TestCase.DisplayName, testFailed.TestCase.UniqueID, TestState.Failed, builder.ToString());
}
protected override bool Visit(ITestPassed testPassed)
protected override void OnTestPassed(ITestPassed testPassed)
{
Process(testPassed.TestCase.DisplayName, TestState.Passed);
return _writer.IsConnected;
Process(testPassed.TestCase.DisplayName, testPassed.TestCase.UniqueID, TestState.Passed);
}
protected override bool Visit(ITestSkipped testSkipped)
protected override void OnTestSkipped(ITestSkipped testSkipped)
{
Process(testSkipped.TestCase.DisplayName, TestState.Skipped);
return _writer.IsConnected;
Process(testSkipped.TestCase.DisplayName, testSkipped.TestCase.UniqueID, TestState.Skipped);
}
}
private sealed class TestCaseDiscoverer : TestMessageVisitor<IDiscoveryCompleteMessage>
private sealed class TestDiscoverySink : BaseTestDiscoverySink
{
private readonly HashSet<string> _testCaseDisplayNameSet;
private readonly HashSet<string> _testCaseUniqueIDSet;
private readonly List<ITestCase> _testCaseList;
internal TestCaseDiscoverer(HashSet<string> testCaseDisplayNameSet, List<ITestCase> testCaseList)
internal TestDiscoverySink(HashSet<string> testCaseUniqueIDSet, List<ITestCase> testCaseList)
{
_testCaseDisplayNameSet = testCaseDisplayNameSet;
_testCaseUniqueIDSet = testCaseUniqueIDSet;
_testCaseList = testCaseList;
}
protected override bool Visit(ITestCaseDiscoveryMessage testCaseDiscovered)
protected override void OnTestDiscovered(ITestCaseDiscoveryMessage testCaseDiscovered)
{
var testCase = testCaseDiscovered.TestCase;
if (_testCaseDisplayNameSet.Contains(testCase.DisplayName))
if (_testCaseUniqueIDSet.Contains(testCase.UniqueID))
{
_testCaseList.Add(testCaseDiscovered.TestCase);
}
return true;
}
}
/// <summary>
/// Read out the set of test case display names to run.
/// Read out the set of test case unique IDs to run.
/// </summary>
private static List<string> ReadTestCaseDisplayNames(Stream stream)
private static List<string> ReadTestCaseUniqueIDs(Stream stream)
{
using (var reader = new ClientReader(stream))
{
@@ -103,54 +103,58 @@ namespace xunit.runner.worker
}
}
private static List<ITestCase> GetTestCaseList(XunitFrontController xunit, Stream stream)
private static List<ITestCase> GetTestCaseList(XunitFrontController xunit, TestAssemblyConfiguration configuration, HashSet<string> testCaseNameSet)
{
var testCaseDisplayNames = ReadTestCaseDisplayNames(stream);
var testCaseDisplayNameSet = new HashSet<string>(testCaseDisplayNames, StringComparer.Ordinal);
var testCaseList = new List<ITestCase>();
using (var discoverer = new TestCaseDiscoverer(testCaseDisplayNameSet, testCaseList))
using (var sink = new TestDiscoverySink(testCaseNameSet, testCaseList))
{
xunit.Find(includeSourceInformation: false, messageSink: discoverer, discoveryOptions: TestFrameworkOptions.ForDiscovery());
discoverer.Finished.WaitOne();
xunit.Find(includeSourceInformation: false, messageSink: sink,
discoveryOptions: TestFrameworkOptions.ForDiscovery(configuration));
sink.Finished.WaitOne();
}
return testCaseList;
}
internal static void RunAll(string assemblyPath, Stream stream)
internal static void RunAll(string assemblyFileName, Stream stream)
{
using (AssemblyHelper.SubscribeResolve())
using (var xunit = new XunitFrontController(
assemblyFileName: assemblyPath,
useAppDomain: true,
shadowCopy: false,
diagnosticMessageSink: new MessageVisitor()))
using (var writer = new ClientWriter(stream))
using (var testRunVisitor = new TestRunVisitor(writer))
{
xunit.RunAll(testRunVisitor, TestFrameworkOptions.ForDiscovery(), TestFrameworkOptions.ForExecution());
testRunVisitor.Finished.WaitOne();
writer.Write(TestDataKind.EndOfData);
}
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 assemblyPath, Stream stream)
internal static void RunSpecific(string assemblyFileName, Stream stream)
{
using (AssemblyHelper.SubscribeResolve())
using (var xunit = new XunitFrontController(
assemblyFileName: assemblyPath,
useAppDomain: true,
shadowCopy: false,
diagnosticMessageSink: new MessageVisitor()))
using (var writer = new ClientWriter(stream))
using (var testRunVisitor = new TestRunVisitor(writer))
{
var testCaseList = GetTestCaseList(xunit, stream);
xunit.RunTests(testCaseList, testRunVisitor, TestFrameworkOptions.ForExecution());
testRunVisitor.Finished.WaitOne();
writer.Write(TestDataKind.EndOfData);
}
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.SubscribeResolveForAssembly(assemblyFileName))
using (var xunit = new XunitFrontController(appDomainSupport, assemblyFileName, shadowCopy: false))
using (var writer = new ClientWriter(stream))
{
var configuration = ConfigReader.Load(assemblyFileName);
action(xunit, configuration, writer);
}
}
}
}
-5
View File
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
<package id="xunit.runner.utility" version="2.1.0-beta4-build3109" targetFramework="net452" />
</packages>
+8 -71
View File
@@ -1,77 +1,14 @@
<?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')" />
<Project Sdk="Microsoft.NET.Sdk">
<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.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<RootNamespace>Xunit.Runner.Worker</RootNamespace>
<TargetFramework>net472</TargetFramework>
</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">
<HintPath>..\packages\xunit.runner.utility.2.1.0-beta4-build3109\lib\net35\xunit.runner.utility.desktop.dll</HintPath>
</Reference>
<PackageReference Include="xunit.runner.utility" Version="2.4.1" />
<ProjectReference Include="..\xunit.runner.data\xunit.runner.data.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Include="Connection.cs" />
<Compile Include="DiscoverUtil.cs" />
<Compile Include="MessageVisitor.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RunUtil.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\xunit.runner.data\xunit.runner.data.csproj">
<Project>{a1f579f4-443e-4f64-bc55-998ab86ff293}</Project>
<Name>xunit.runner.data</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
+2 -2
View File
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.23107.0
# Visual Studio 15
VisualStudioVersion = 15.0.26430.4
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xunit.runner.wpf", "xunit.runner.wpf\xunit.runner.wpf.csproj", "{34FB519C-FB49-4B31-ACA2-7F7879311BCF}"
EndProject
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
</configuration>
+3 -3
View File
@@ -1,7 +1,7 @@
<Application x:Class="xunit.runner.wpf.App"
<Application x:Class="Xunit.Runner.Wpf.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:xunit.runner.wpf"
xmlns:local="clr-namespace:Xunit.Runner.Wpf"
StartupUri="MainWindow.xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d1p1:Ignorable="d"
@@ -9,6 +9,6 @@
<Application.Resources>
<vm:ViewModelLocator x:Key="Locator"
d:IsDataSource="True"
xmlns:vm="clr-namespace:xunit.runner.wpf.ViewModel" />
xmlns:vm="clr-namespace:Xunit.Runner.Wpf.ViewModel" />
</Application.Resources>
</Application>
+2 -11
View File
@@ -1,16 +1,7 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows;
namespace xunit.runner.wpf
namespace Xunit.Runner.Wpf
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

+2 -7
View File
@@ -1,12 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows;
using System.Windows.Input;
namespace xunit.runner.wpf
namespace Xunit.Runner.Wpf
{
class CommandBindings
{
@@ -1,27 +1,27 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using xunit.runner.data;
using xunit.runner.wpf.ViewModel;
using Xunit.Runner.Data;
namespace xunit.runner.wpf.Converters
namespace Xunit.Runner.Wpf.Converters
{
public class TestStateConverter : IValueConverter
{
private static ImageSource passedSource;
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()
{
passedSource = LoadResourceImage("Passed.ico");
failedSource = LoadResourceImage("Failed.ico");
skippedSource = LoadResourceImage("Skipped.ico");
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)
@@ -40,12 +40,14 @@ namespace xunit.runner.wpf.Converters
{
switch (state)
{
case TestState.Running:
return Brushes.Blue;
case TestState.Failed:
return Brushes.Red;
case TestState.Skipped:
return Brushes.Yellow;
case TestState.Passed:
return Brushes.Green;
case TestState.Skipped:
return skippedBrush;
default:
return Brushes.Gray;
}
@@ -54,12 +56,14 @@ namespace xunit.runner.wpf.Converters
{
switch (state)
{
case TestState.Running:
return runningSource;
case TestState.Failed:
return failedSource;
case TestState.Skipped:
return skippedSource;
case TestState.Passed:
return passedSource;
case TestState.Skipped:
return skippedSource;
default:
return null;
}
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
namespace Xunit.Runner.Wpf
{
public static partial class Extensions
{
private class FuncComparer<T> : IComparer<T>
{
private readonly Func<T, T, int> _comparison;
public FuncComparer(Func<T, T, int> comparison)
{
_comparison = comparison;
}
public int Compare(T x, T y) => _comparison(x, y);
}
}
}
+78 -6
View File
@@ -1,15 +1,12 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace xunit.runner.wpf
namespace Xunit.Runner.Wpf
{
public static class Extensions
public static partial class Extensions
{
public static void AddRange<TList, TEnumerable>(this IList<TList> list, IEnumerable<TEnumerable> items) where TEnumerable : TList
public static void AddRange<TList, TEnumerable>(this ICollection<TList> list, IEnumerable<TEnumerable> items) where TEnumerable : TList
{
foreach (var i in items)
{
@@ -24,5 +21,80 @@ namespace xunit.runner.wpf
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);
}
}
}
+18 -16
View File
@@ -8,13 +8,14 @@ using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
namespace xunit.runner.wpf
namespace Xunit.Runner.Wpf
{
public class FilteredCollectionView<T, TFilterArg> : IList<T>, IList, INotifyCollectionChanged, IDisposable
{
private readonly ObservableCollection<T> dataSource;
private readonly List<T> filteredList;
private readonly Func<T, TFilterArg, bool> filter;
private readonly IComparer<T> sort;
public FilteredCollectionView(ObservableCollection<T> dataSource, Func<T, TFilterArg, bool> filter, TFilterArg filterArgument, IComparer<T> sort)
{
@@ -26,6 +27,7 @@ namespace xunit.runner.wpf
this.filter = filter;
this.filterArgument = filterArgument;
this.filteredList = new List<T>();
this.sort = sort;
this.dataSource.CollectionChanged += this.dataSource_CollectionChanged;
@@ -45,11 +47,7 @@ namespace xunit.runner.wpf
protected virtual void OnItemChanged(T sender, PropertyChangedEventArgs args)
{
var itemChanged = this.ItemChanged;
if (itemChanged != null)
{
itemChanged(sender, args);
}
this.ItemChanged?.Invoke(sender, args);
}
private TFilterArg filterArgument;
@@ -75,6 +73,7 @@ namespace xunit.runner.wpf
}
}
this.filteredList.Sort(sort);
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
@@ -119,7 +118,7 @@ namespace xunit.runner.wpf
{
if (this.filter(item, this.filterArgument))
{
int index = this.filteredList.IndexOf(item);
int index = this.filteredList.BinarySearch(item, sort);
if (index < 0)
{
this.filteredList.Insert(~index, item);
@@ -142,7 +141,7 @@ namespace xunit.runner.wpf
observable.PropertyChanged -= this.dataSource_ItemChanged;
}
int index = this.filteredList.IndexOf(item);
int index = this.filteredList.BinarySearch(item, sort);
if (index >= 0)
{
this.filteredList.RemoveAt(index);
@@ -153,7 +152,7 @@ namespace xunit.runner.wpf
private void dataSource_ItemChanged(object sender, PropertyChangedEventArgs e)
{
var item = (T)sender;
int index = this.filteredList.IndexOf(item);
int index = this.filteredList.BinarySearch(item, sort);
if (this.filter(item, this.FilterArgument))
{
if (index < 0)
@@ -187,11 +186,7 @@ namespace xunit.runner.wpf
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
var collectionChanged = this.CollectionChanged;
if (collectionChanged != null)
{
collectionChanged(this, args);
}
this.CollectionChanged?.Invoke(this, args);
}
public void Add(T item)
@@ -204,7 +199,7 @@ namespace xunit.runner.wpf
throw new NotSupportedException();
}
public bool Contains(T item) => this.filteredList.Contains(item);
public bool Contains(T item) => this.filteredList.BinarySearch(item, sort) >= 0;
public void CopyTo(T[] array, int arrayIndex)
{
@@ -224,7 +219,14 @@ namespace xunit.runner.wpf
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
public int IndexOf(T item) => this.filteredList.IndexOf(item);
public int IndexOf(T item)
{
int location = this.filteredList.BinarySearch(item, sort);
if (location < 0)
return -1;
return location;
}
public void Insert(int index, T item)
{
+29
View File
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
namespace Xunit.Runner.Wpf
{
internal interface ITestAssemblyWatcher
{
/// <summary>
/// Adds a new assembly to list of assemblies to be autoreloaded.
/// </summary>
void AddAssembly(string assemblyFileName);
/// <summary>
/// Removes an assembly from the list of assemblies ot be autoreloaded.
/// </summary>
void RemoveAssembly(string assemblyFileName);
/// <summary>
/// Enables watching of all assemblies.
/// </summary>
/// <param name="reloader">Action to perform when a file change is detected</param>
void EnableWatch(Func<IEnumerable<string>, bool> reloader);
/// <summary>
/// Disables watching of all assemblies
/// </summary>
void DisableWatch();
}
}
+15 -69
View File
@@ -1,91 +1,37 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using xunit.runner.data;
using xunit.runner.wpf.ViewModel;
using Xunit.Runner.Data;
namespace xunit.runner.wpf
namespace Xunit.Runner.Wpf
{
internal interface ITestUtil
{
/// <summary>
/// Discover the list of test cases which are available in the specified assembly.
/// </summary>
ITestDiscoverSession Discover(string assemblyPath, CancellationToken cancellationToken = default(CancellationToken));
Task Discover(
string assebmlyFileName,
Action<IEnumerable<TestCaseData>> testsDiscovered,
CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Begin a run of all unit tests for the given assembly.
/// </summary>
ITestRunSession RunAll(string assemblyPath, CancellationToken cancellationToken = default(CancellationToken));
Task RunAll(
string assemblyFileName,
Action<IEnumerable<TestResultData>> testsFinished,
CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Begin a run of specific unit tests for the given assembly.
/// </summary>
ITestRunSession RunSpecific(string assemblyPath, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken = default(CancellationToken));
Task RunSpecific(
string assemblyFileName,
ImmutableArray<string> testCasesToRun,
Action<IEnumerable<TestResultData>> testsFinished,
CancellationToken cancellationToken = default(CancellationToken));
}
internal interface ITestSession
{
/// <summary>
/// Task which will be completed when the session is finished.
/// </summary>
Task Task { get; }
}
internal interface ITestRunSession : ITestSession
{
/// <summary>
/// Raised when an individual test is finished running.
/// </summary>
event EventHandler<TestResultDataEventArgs> TestFinished;
/// <summary>
/// Raised when the session has finished executing all of the specified tests.
/// </summary>
event EventHandler SessionFinished;
}
internal interface ITestDiscoverSession : ITestSession
{
/// <summary>
/// Raised when an individual test is finished running.
/// </summary>
event EventHandler<TestCaseDataEventArgs> TestDiscovered;
/// <summary>
/// Raised when the session has finished executing all of the specified tests.
/// </summary>
event EventHandler SessionFinished;
}
internal sealed class TestCaseDataEventArgs : EventArgs
{
internal readonly TestCaseData TestCaseData;
internal TestCaseDataEventArgs(TestCaseData data)
{
TestCaseData = data;
}
}
internal sealed class TestResultDataEventArgs : EventArgs
{
internal readonly TestResultData TestResultData;
internal string TestCaseDisplayName => TestResultData.TestCaseDisplayName;
internal TestState TestState => TestResultData.TestState;
internal string Output => TestResultData.Output;
internal TestResultDataEventArgs(TestResultData testResultData)
{
TestResultData = testResultData;
}
}
}
@@ -2,18 +2,12 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using xunit.runner.data;
using xunit.runner.wpf.ViewModel;
using Xunit.Runner.Data;
namespace xunit.runner.wpf.Impl
namespace Xunit.Runner.Wpf.Impl
{
internal partial class RemoteTestUtil
{
@@ -54,26 +48,29 @@ namespace xunit.runner.wpf.Impl
}
}
/// <summary>
/// Utility for reading a collection of <see cref="{T}"/> values from the given
/// <see cref="ClientReader"/> value.
/// </summary>
/// <typeparam name="T"></typeparam>
private sealed class BackgroundReader<T> where T : class
{
private readonly ConcurrentQueue<T> _queue;
private readonly ClientReader _reader;
private readonly Func<ClientReader, T> _readValue;
private readonly CancellationToken _cancellationToken;
internal ClientReader Reader => _reader;
internal BackgroundReader(ConcurrentQueue<T> queue, ClientReader reader, Func<ClientReader, T> readValue, CancellationToken cancellationToken)
internal BackgroundReader(ConcurrentQueue<T> queue, ClientReader reader, Func<ClientReader, T> readValue)
{
_queue = queue;
_reader = reader;
_readValue = readValue;
_cancellationToken = cancellationToken;
}
internal Task ReadAsync()
internal Task ReadAsync(CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run(() => GoOnBackground(), _cancellationToken);
return Task.Run(() => GoOnBackground(cancellationToken), cancellationToken);
}
/// <summary>
@@ -81,9 +78,9 @@ namespace xunit.runner.wpf.Impl
/// named pipe client stream.
/// </summary>
/// <returns></returns>
private void GoOnBackground()
private void GoOnBackground(CancellationToken cancellationToken)
{
while (!_cancellationToken.IsCancellationRequested)
while (!cancellationToken.IsCancellationRequested)
{
try
{
@@ -135,7 +132,7 @@ namespace xunit.runner.wpf.Impl
_maxPerTick = maxResultPerTick;
_callback = callback;
_timer = new DispatcherTimer(
interval ?? TimeSpan.FromMilliseconds(100),
interval ?? TimeSpan.FromMilliseconds(500),
DispatcherPriority.Normal,
OnTimerTick,
dispatcher);
@@ -159,16 +156,12 @@ namespace xunit.runner.wpf.Impl
list.Add(value);
}
if (list.Count > 0)
{
_callback(list);
}
_callback(list);
if (isDone)
{
try
{
_callback(null);
_timer.Stop();
_connection.Dispose();
}
@@ -1,65 +1,45 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using xunit.runner.data;
using xunit.runner.wpf.ViewModel;
using Xunit.Runner.Data;
namespace xunit.runner.wpf.Impl
namespace Xunit.Runner.Wpf.Impl
{
internal sealed partial class RemoteTestUtil : ITestUtil
{
private sealed class Connection : IDisposable
{
private NamedPipeClientStream _stream;
private Process _process;
private ClientReader _reader;
internal NamedPipeClientStream Stream => _stream;
internal ClientReader Reader => _reader;
internal Connection(NamedPipeClientStream stream, Process process)
internal Connection(NamedPipeClientStream stream)
{
_stream = stream;
_process = process;
_reader = new ClientReader(stream);
}
internal void Dispose()
{
if (_process != null)
if (_stream == null)
{
Debug.Assert(_stream != null);
try
{
_stream.WriteAsync(new byte[] { 0 }, 0, 1);
}
catch
{
// Signal to server we are done with the connection. Okay to fail because
// it means the server isn't listening anymore.
}
_stream.Close();
try
{
_process.Kill();
}
catch
{
// Inherent race condition shutting down the process.
}
return;
}
try
{
_stream.WriteAsync(new byte[] { 0 }, 0, 1);
}
catch
{
// Signal to server we are done with the connection. Okay to fail because
// it means the server isn't listening anymore.
}
_stream.Close();
_stream = null;
}
void IDisposable.Dispose()
@@ -1,70 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using xunit.runner.data;
using xunit.runner.wpf.ViewModel;
namespace xunit.runner.wpf.Impl
{
internal partial class RemoteTestUtil
{
private sealed class DiscoverSession : ITestDiscoverSession
{
private readonly Task _task;
private event EventHandler<TestCaseDataEventArgs> _testDiscovered;
private event EventHandler _sessionFinished;
internal DiscoverSession(Connection connection, Dispatcher dispatcher, CancellationToken cancellationToken)
{
var queue = new ConcurrentQueue<TestCaseData>();
var backgroundReader = new BackgroundReader<TestCaseData>(queue, new ClientReader(connection.Stream), r => r.ReadTestCaseData(), cancellationToken);
backgroundReader.ReadAsync();
var backgroundProducer = new BackgroundProducer<TestCaseData>(connection, dispatcher, queue, OnDiscovered);
_task = backgroundProducer.Task;
}
private void OnDiscovered(List<TestCaseData> list)
{
Debug.Assert(!_task.IsCompleted);
if (list == null)
{
_sessionFinished?.Invoke(this, EventArgs.Empty);
return;
}
foreach (var cur in list)
{
_testDiscovered?.Invoke(this, new TestCaseDataEventArgs(cur));
}
}
#region ITestRunSession
Task ITestSession.Task => _task;
event EventHandler<TestCaseDataEventArgs> ITestDiscoverSession.TestDiscovered
{
add { _testDiscovered += value; }
remove { _testDiscovered -= value; }
}
event EventHandler ITestDiscoverSession.SessionFinished
{
add { _sessionFinished += value; }
remove { _sessionFinished -= value; }
}
#endregion
}
}
}
@@ -1,100 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using xunit.runner.data;
using xunit.runner.wpf.ViewModel;
namespace xunit.runner.wpf.Impl
{
internal partial class RemoteTestUtil
{
private sealed class RunSession : ITestRunSession
{
private readonly Task _task;
private event EventHandler<TestResultDataEventArgs> _testFinished;
private event EventHandler _sessionFinished;
internal RunSession(Connection connection, Dispatcher dispatcher, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken)
{
var queue = CreateQueue(connection, testCaseDisplayNames, cancellationToken);
var backgroundProducer = new BackgroundProducer<TestResultData>(connection, dispatcher, queue, OnDataProduced);
_task = backgroundProducer.Task;
}
/// <summary>
/// Create the <see cref="ConcurrentQueue{T}"/> which will be populated with the <see cref="TestResultData"/>
/// as it arrives from the worker.
/// </summary>
private static ConcurrentQueue<TestResultData> CreateQueue(Connection connection, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken)
{
var queue = new ConcurrentQueue<TestResultData>();
var unused = CreateQueueCore(queue, connection, testCaseDisplayNames, cancellationToken);
return queue;
}
private static async Task CreateQueueCore(ConcurrentQueue<TestResultData> queue, Connection connection, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken)
{
try
{
if (!testCaseDisplayNames.IsDefaultOrEmpty)
{
var backgroundWriter = new BackgroundWriter<string>(new ClientWriter(connection.Stream), testCaseDisplayNames, (w, s) => w.Write(s), cancellationToken);
await backgroundWriter.WriteAsync();
}
var backgroundReader = new BackgroundReader<TestResultData>(queue, new ClientReader(connection.Stream), r => r.ReadTestResultData(), cancellationToken);
await backgroundReader.ReadAsync();
}
catch (Exception ex)
{
Debug.Fail(ex.Message);
// Signal data completed
queue.Enqueue(null);
}
}
private void OnDataProduced(List<TestResultData> list)
{
Debug.Assert(!_task.IsCompleted);
if (list == null)
{
_sessionFinished?.Invoke(this, EventArgs.Empty);
return;
}
foreach (var cur in list)
{
_testFinished?.Invoke(this, new wpf.TestResultDataEventArgs(cur));
}
}
#region ITestRunSession
Task ITestSession.Task => _task;
event EventHandler<TestResultDataEventArgs> ITestRunSession.TestFinished
{
add { _testFinished += value; }
remove { _testFinished -= value; }
}
event EventHandler ITestRunSession.SessionFinished
{
add { _sessionFinished += value; }
remove { _sessionFinished -= value; }
}
#endregion
}
}
}
+100 -31
View File
@@ -3,82 +3,151 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using xunit.runner.data;
using xunit.runner.wpf.ViewModel;
using Xunit.Runner.Data;
namespace xunit.runner.wpf.Impl
namespace Xunit.Runner.Wpf.Impl
{
internal sealed partial class RemoteTestUtil : ITestUtil
{
private struct ProcessInfo
{
internal readonly string PipeName;
internal readonly Process Process;
internal ProcessInfo(string pipeName, Process process)
{
PipeName = pipeName;
Process = process;
}
}
private readonly Dispatcher _dispatcher;
private ProcessInfo? _processInfo;
internal RemoteTestUtil(Dispatcher dispatcher)
{
_dispatcher = dispatcher;
_processInfo = StartWorkerProcess();
}
private static Connection StartWorkerProcess(string action, string argument)
private async Task<Connection> CreateConnection(string action, string argument, CancellationToken cancellationToken)
{
var pipeName = $"xunit.runner.wpf.pipe.{Guid.NewGuid()}";
var processStartInfo = new ProcessStartInfo();
processStartInfo.FileName = typeof(xunit.runner.worker.Program).Assembly.Location;
processStartInfo.Arguments = $"{pipeName} {action} {argument}";
processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
var pipeName = GetPipeName();
var process = Process.Start(processStartInfo);
try
{
var stream = new NamedPipeClientStream(pipeName);
stream.Connect();
return new Connection(stream, process);
await stream.ConnectAsync(cancellationToken);
var writer = new ClientWriter(stream);
writer.Write(action);
writer.Write(argument);
return new Connection(stream);
}
catch
{
process.Kill();
try
{
_processInfo?.Process.Kill();
}
catch
{
// Inherent race condition here. Just need to make sure the process is
// dead as it can't even handle new connections.
}
throw;
}
}
private DiscoverSession Discover(string assemblyPath, CancellationToken cancellationToken)
private string GetPipeName()
{
var connection = StartWorkerProcess(Constants.ActionDiscover, assemblyPath);
return new DiscoverSession(connection, _dispatcher, cancellationToken);
var process = _processInfo?.Process;
if (process != null && !process.HasExited)
{
return _processInfo.Value.PipeName;
}
_processInfo = StartWorkerProcess();
return _processInfo.Value.PipeName;
}
private RunSession RunAll(string assemblyPath, CancellationToken cancellationToken)
private static ProcessInfo StartWorkerProcess()
{
var connection = StartWorkerProcess(Constants.ActionRunAll, assemblyPath);
return new RunSession(connection, _dispatcher, ImmutableArray<string>.Empty, cancellationToken);
var pipeName = $"xunit.runner.wpf.pipe.{Guid.NewGuid()}";
var processStartInfo = new ProcessStartInfo();
processStartInfo.FileName = typeof(Xunit.Runner.Worker.Program).Assembly.Location;
processStartInfo.Arguments = $"{pipeName} {Process.GetCurrentProcess().Id}";
processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
processStartInfo.UseShellExecute = false;
processStartInfo.CreateNoWindow = true;
var process = Process.Start(processStartInfo);
return new ProcessInfo(pipeName, process);
}
private RunSession RunSpecific(string assemblyPath, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken)
private void RecycleProcess()
{
var connection = StartWorkerProcess(Constants.ActionRunSpecific, assemblyPath);
return new RunSession(connection, _dispatcher, testCaseDisplayNames, cancellationToken);
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
ITestDiscoverSession ITestUtil.Discover(string assemblyPath, CancellationToken cancellationToken)
Task ITestUtil.Discover(string assemblyFileName, Action<IEnumerable<TestCaseData>> testsDiscovered, CancellationToken cancellationToken)
{
return Discover(assemblyPath, cancellationToken);
return Discover(assemblyFileName, testsDiscovered, cancellationToken);
}
ITestRunSession ITestUtil.RunAll(string assemblyPath, CancellationToken cancellationToken)
Task ITestUtil.RunAll(string assemblyFileName, Action<IEnumerable<TestResultData>> testsFinished, CancellationToken cancellationToken)
{
return RunAll(assemblyPath, cancellationToken);
return RunCore(Constants.ActionRunAll, assemblyFileName, ImmutableArray<string>.Empty, testsFinished, cancellationToken);
}
ITestRunSession ITestUtil.RunSpecific(string assemblyPath, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken)
Task ITestUtil.RunSpecific(string assemblyFileName, ImmutableArray<string> testCases, Action<IEnumerable<TestResultData>> testsFinished, CancellationToken cancellationToken)
{
return RunSpecific(assemblyPath, testCaseDisplayNames, cancellationToken);
return RunCore(Constants.ActionRunSpecific, assemblyFileName, testCases, testsFinished, cancellationToken);
}
#endregion
@@ -0,0 +1,206 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
namespace Xunit.Runner.Wpf.Impl
{
internal sealed class TestAssemblyWatcher : ITestAssemblyWatcher
{
private readonly object sync = new object();
private readonly IDictionary<string, FileSystemWatcher> watchedAssemblies = new Dictionary<string, FileSystemWatcher>();
private readonly Dispatcher dispatcher;
private bool isEnabled = false;
private ReloadDebouncer debouncer;
public TestAssemblyWatcher(Dispatcher dispatcher)
{
this.dispatcher = dispatcher;
}
public void AddAssembly(string assemblyFileName)
{
// Assumptions about adding and removing assemblies are broken if this isn't true
Debug.Assert(string.Equals(assemblyFileName, Path.GetFullPath(assemblyFileName), StringComparison.Ordinal));
lock (sync)
{
if (watchedAssemblies.ContainsKey(assemblyFileName))
{
// Already watching this assembly, nothing to do but return
return;
}
FileSystemWatcher watcher = new FileSystemWatcher
{
Path = Path.GetDirectoryName(assemblyFileName),
NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite,
Filter = Path.GetFileName(assemblyFileName)
};
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.Created += new FileSystemEventHandler(OnChanged);
watchedAssemblies[assemblyFileName] = watcher;
if (isEnabled)
{
watcher.EnableRaisingEvents = true;
}
}
}
public void RemoveAssembly(string assemblyFileName)
{
lock (sync)
{
if (watchedAssemblies.ContainsKey(assemblyFileName))
{
watchedAssemblies[assemblyFileName].Dispose();
watchedAssemblies.Remove(assemblyFileName);
}
}
}
public void EnableWatch(Func<IEnumerable<string>, bool> reloader)
{
lock (sync)
{
isEnabled = true;
foreach (var watcher in watchedAssemblies.Values)
{
watcher.EnableRaisingEvents = true;
}
this.debouncer = new ReloadDebouncer(dispatcher, reloader);
}
}
public void DisableWatch()
{
lock (sync)
{
isEnabled = false;
foreach (var watcher in watchedAssemblies.Values)
{
watcher.EnableRaisingEvents = false;
}
this.debouncer?.Cancel();
this.debouncer = null;
}
}
private void OnChanged(object source, FileSystemEventArgs args)
{
debouncer?.AddAssembly(args.FullPath);
}
/// <summary>
/// Because, during a build of a number of projects many file system events will be triggered for potentially many
/// test assemblies, we need to batch our update requests. This class will do this, waiting for 100 ms after receiving
/// a new reload request to send the reload requests. This timer resets every time a reload request is received. Note
/// that if you continuously rebuild, this will technicially never finish batching and nothing will reload, but this
/// assumes that file events will stop at some point.
///
/// If the reloader returns false, meaning that the reload was not kicked off successfully, we back off for a full second
/// before reattempting to queue the updates.
/// </summary>
private class ReloadDebouncer
{
private readonly object sync = new object();
private readonly Dispatcher dispatcher;
private readonly Func<IEnumerable<string>, bool> reloader;
private ISet<string> assembliesToReload = new HashSet<string>();
private bool newAssemblyAdded = false;
private bool running = false;
private bool cancelled = false;
public ReloadDebouncer(Dispatcher dispatcher, Func<IEnumerable<string>, bool> reloader)
{
this.dispatcher = dispatcher;
this.reloader = reloader;
}
public void AddAssembly(string assembly)
{
lock (sync)
{
assembliesToReload.Add(assembly);
if (!Start())
{
newAssemblyAdded = true;
}
}
}
public void Cancel()
{
running = false;
}
private bool Start()
{
if (running)
{
return false;
}
running = true;
Task.Run((Action)Debounce);
return true;
}
private async void Debounce()
{
bool backOff = false;
do
{
await Task.Delay(backOff ? 1000 : 100);
backOff = false;
lock (sync)
{
void Reset()
{
assembliesToReload = new HashSet<string>();
running = false;
}
if (cancelled)
{
Reset();
return;
}
// New assemblies added, so we need to wait again
if (newAssemblyAdded)
{
newAssemblyAdded = false;
continue;
}
// No new assemblies added, time to alert and exit
if (!dispatcher.Invoke(() => reloader(assembliesToReload)))
{
// If the reloader returned false, it's still busy from the last reload request or other user action.
// Back off for a full second to give it time, then continue as previous
backOff = true;
continue;
}
Reset();
}
} while (running);
}
}
}
}
-30
View File
@@ -1,30 +0,0 @@
<Window x:Class="xunit.runner.wpf.LoadingDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:xunit.runner.wpf"
mc:Ignorable="d"
Title="Discovering Tests"
SizeToContent="WidthAndHeight"
ResizeMode="NoResize"
ShowInTaskbar="False"
MinHeight="100"
MinWidth="300"
WindowStartupLocation="CenterOwner">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="Discovering tests in:"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Grid.Row="0"/>
<TextBlock Text="{Binding Path=AssemblyFileName, RelativeSource={RelativeSource AncestorType=Window}}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Grid.Row="1"/>
</Grid>
</Window>
-36
View File
@@ -1,36 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace xunit.runner.wpf
{
/// <summary>
/// Interaction logic for LoadingDialog.xaml
/// </summary>
public partial class LoadingDialog : Window
{
public LoadingDialog()
{
InitializeComponent();
}
public string AssemblyFileName
{
get { return (string)GetValue(AssemblyFileNameProperty); }
set { SetValue(AssemblyFileNameProperty, value); }
}
public static readonly DependencyProperty AssemblyFileNameProperty =
DependencyProperty.Register(nameof(AssemblyFileName), typeof(string), typeof(LoadingDialog));
}
}
+314 -115
View File
@@ -1,25 +1,34 @@
<Window x:Class="xunit.runner.wpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:xunit.runner.wpf"
xmlns:converters="clr-namespace:xunit.runner.wpf.Converters"
xmlns:vm="clr-namespace:xunit.runner.wpf.ViewModel"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform"
mc:Ignorable="d"
DataContext="{Binding Main, Source={StaticResource Locator}}"
Title="xUnit.net Test Runner"
Icon="Artwork\xunit-dot-net-small-logo.png"
ResizeMode="CanResizeWithGrip"
Height="600"
Width="525">
<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>
@@ -35,16 +44,28 @@
<Menu Grid.Row="0">
<MenuItem Header="_File">
<MenuItem Header="E_xit"
Command="{Binding ExitCommand}" />
<MenuItem Command="{Binding ExitCommand}" Header="E_xit" />
</MenuItem>
<MenuItem Header="_Assembly">
<MenuItem Header="_Open"
Command="ApplicationCommands.Open" />
<MenuItem Header="R_ecent" />
<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 Header="_Unload" />
<MenuItem Header="_Reload" />
<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" />
@@ -59,18 +80,20 @@
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="*" MinWidth="200px" />
<ColumnDefinition Width="2*" MinWidth="200px" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<GroupBox Header="Refinements"
Margin="3"
Grid.Column="0"
Grid.Row="0">
<GroupBox
Grid.Row="0"
Grid.Column="0"
Margin="3"
Header="Refinements">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
@@ -80,101 +103,214 @@
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Content="Search:"
Grid.Row="0" />
<TextBox Grid.Row="1"
Text="{Binding FilterString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Label Content="Assemblies:"
Grid.Row="2" />
<ListBox Height="175"
ItemsSource="{Binding Assemblies}"
SelectionMode="Extended"
Grid.Row="3">
<Label Grid.Row="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">
<TextBlock Text="{Binding DisplayName}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DisplayName}" />
<TextBlock
FontStyle="Italic"
Foreground="Gray"
Text=" Discovering tests...">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding State}" Value="{x:Static vm:AssemblyState.Loading}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DataTemplate>
</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 Content="Traits:"
Grid.Row="4" />
<ListBox Grid.Row="5" />
<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.Column="0"
Grid.Row="1">
<Grid Grid.Row="1" Grid.Column="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Content="_Run All"
Command="{Binding RunCommand}"
Margin="3"
Grid.Column="0" />
<Button Content="_Cancel"
Command="{Binding CancelCommand}"
Margin="0,3,3,3"
Grid.Column="1" />
<Button
Grid.Column="0"
Margin="10,0,0,0"
Command="{Binding RunAllCommand}"
Content="_Run All" />
<Button
Grid.Column="1"
Command="{Binding RunSelectedCommand}"
Content="Run _Selected" />
<Button
Grid.Column="2"
Margin="0,0,10,0"
Command="{Binding CancelCommand}"
Content="_Cancel" />
</Grid>
<Grid Grid.Column="1"
Grid.Row="0">
<Grid Grid.Row="0" Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" MinHeight="200px" />
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
<RowDefinition Height="*" MinHeight="200px" />
</Grid.RowDefinitions>
<GroupBox Header="{Binding MethodsCaption}"
Margin="3"
Grid.Row="0">
<GroupBox
Grid.Row="0"
Margin="3"
Header="{Binding TestCasesCaption}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ToggleButton IsChecked="{Binding IncludePassedTests}"
Margin="3">
<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.ico"
Height="16" />
<TextBlock Margin="4,0,0,0"
Text="{Binding TestsPassed}" />
<Image Source="Artwork\Passed_large.png" />
<TextBlock
Margin="4,0"
VerticalAlignment="Center"
FontSize="16"
Text="{Binding TestsPassed, StringFormat={}{0:#\,0}}" />
</StackPanel>
</ToggleButton>
<ToggleButton IsChecked="{Binding IncludeFailedTests}"
Margin="3"
Grid.Column="1">
<ToggleButton
Grid.Column="1"
Margin="2,4"
Background="Transparent"
BorderThickness="0"
Command="{Binding TestFilterChanged}"
IsChecked="{Binding FilterFailedTests}">
<StackPanel Orientation="Horizontal">
<Image Source="Artwork\Failed.ico"
Height="16" />
<TextBlock Margin="4,0,0,0"
Text="{Binding TestsFailed}" />
<Image Source="Artwork\Failed_large.png" />
<TextBlock
Margin="4,0"
VerticalAlignment="Center"
FontSize="16"
Text="{Binding TestsFailed, StringFormat={}{0:#\,0}}" />
</StackPanel>
</ToggleButton>
<ToggleButton IsChecked="{Binding IncludeSkippedTests}"
Margin="3"
Grid.Column="2">
<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.ico"
Height="16" />
<TextBlock Margin="4,0,0,0"
Text="{Binding TestsSkipped}" />
<Image Source="Artwork\Skipped_large.png" />
<TextBlock
Margin="4,0"
VerticalAlignment="Center"
FontSize="16"
Text="{Binding TestsSkipped, StringFormat={}{0:#\,0}}" />
</StackPanel>
</ToggleButton>
</Grid>
<ListBox ItemsSource="{Binding TestCases}"
SelectionMode="Extended"
Grid.Row="1">
<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>
@@ -183,45 +319,108 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Width="16"
Margin="2"
Source="{Binding Path=State, Mode=OneWay, Converter={StaticResource TestStateConverter}}"
Grid.Column="0" />
<TextBlock Text="{Binding DisplayName}"
Grid.Column="1" />
<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" />
<GridSplitter
Grid.Row="1"
Height="3"
HorizontalAlignment="Stretch"
Background="White" />
<GroupBox Header="Output"
Margin="3"
Grid.Row="2">
<TextBox IsReadOnly="True"
Text="{Binding Output}"/>
<GroupBox
Grid.Row="2"
Margin="3"
Header="Output">
<TextBox
FontFamily="Consolas"
HorizontalScrollBarVisibility="Auto"
IsReadOnly="True"
Text="{Binding Output}"
VerticalScrollBarVisibility="Visible" />
</GroupBox>
</Grid>
<ProgressBar Foreground="{Binding Path=CurrentRunState, Converter={StaticResource TestStateConverter}, Mode=OneWay}"
Minimum="0"
Maximum="{Binding MaximumProgress}"
Value="{Binding Path=TestsCompleted}"
Grid.Column="1"
Grid.Row="1"
Margin="3" />
<GridSplitter
Grid.Column="0"
Width="3"
VerticalAlignment="Stretch"
Background="White" />
<ProgressBar
Grid.Row="1"
Grid.Column="1"
Margin="3"
Foreground="{Binding Path=CurrentRunState, Converter={StaticResource TestStateConverter}, Mode=OneWay}"
Maximum="{Binding MaximumProgress}"
Minimum="0"
Value="{Binding Path=TestsCompleted}" />
</Grid>
<StackPanel Grid.Row="2">
<Border BorderBrush="LightGray"
BorderThickness="1"
Margin="3" />
<Border
Margin="3"
BorderBrush="LightGray"
BorderThickness="1" />
<StatusBar>
<StatusBarItem>
<Label />
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="16" />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem Grid.Column="1">
<StackPanel Orientation="Horizontal">
<TextBlock 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>
+42 -17
View File
@@ -1,25 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
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.Persistence;
namespace xunit.runner.wpf
namespace Xunit.Runner.Wpf
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
using GalaSoft.MvvmLight.Command;
using ViewModel;
public partial class MainWindow : Window
{
public static Window Instance { get; private set; }
public MainWindow()
{
Instance = this;
@@ -27,6 +19,39 @@ namespace xunit.runner.wpf
InitializeComponent();
}
public static Window Instance { get; private set; }
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
Storage.RestoreWindowLayout(this);
}
protected override void OnClosing(CancelEventArgs e)
{
Storage.SaveWindowLayout(this);
base.OnClosing(e);
}
private void TestCases_SelectionChanged(Object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
foreach (var item in e.AddedItems)
{
var model = item as TestCaseViewModel;
if (model != null)
{
model.IsSelected = true;
}
}
foreach (var item in e.RemovedItems)
{
var model = item as TestCaseViewModel;
if (model != null)
{
model.IsSelected = false;
}
}
}
}
}
+137
View File
@@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Xml;
using System.Xml.Linq;
namespace Xunit.Runner.Wpf.Persistence
{
internal sealed class Settings
{
private const string SettingsFileName = "settings.xml";
private const string RecentAssembliesElementName = "recent_assemblies";
private const string RecentAssemblyElementName = "recent_assembly";
private const string SettingsElementName = "settings";
private const string VersionAttributeName = "version";
private const string AutoReloadAssembliesElementName = "auto_reload_assemblies";
private const int MaxRecentAssemblies = 10;
private static readonly Version s_latestVersion = new Version(1, 0, 0, 0);
private List<string> recentAssemblies;
private bool autoReloadAssemblies;
private Settings()
{
recentAssemblies = new List<string>();
autoReloadAssemblies = true;
}
public void AddRecentAssembly(string filePath)
{
for (int i = recentAssemblies.Count - 1; i >= 0; i--)
{
if (StringComparer.OrdinalIgnoreCase.Equals(recentAssemblies[i], filePath))
{
recentAssemblies.RemoveAt(i);
}
}
recentAssemblies.Insert(0, filePath);
if (recentAssemblies.Count > MaxRecentAssemblies)
{
recentAssemblies.RemoveRange(MaxRecentAssemblies - 1, recentAssemblies.Count - MaxRecentAssemblies);
}
}
public void ToggleAutoReloadAssemblies()
{
autoReloadAssemblies = !autoReloadAssemblies;
}
public bool GetAutoReloadAssemblies() => autoReloadAssemblies;
public ImmutableArray<string> GetRecentAssemblies()
{
return this.recentAssemblies.ToImmutableArray();
}
public void Save()
{
using (var xmlFile = Storage.CreateXmlFile(SettingsFileName))
{
var xml = new XElement(SettingsElementName, new XAttribute(VersionAttributeName, s_latestVersion));
if (this.recentAssemblies.Count > 0)
{
var recentAssembliesElement = new XElement(RecentAssembliesElementName);
foreach (var recentAssembly in this.recentAssemblies)
{
recentAssembliesElement.Add(new XElement(RecentAssemblyElementName, recentAssembly));
}
xml.Add(recentAssembliesElement);
}
xml.Add(new XElement(AutoReloadAssembliesElementName, autoReloadAssemblies));
xml.Save(xmlFile);
}
}
public static Settings Load()
{
using (var xmlFile = Storage.OpenXmlFile(SettingsFileName))
{
var settings = new Settings();
if (xmlFile == null || xmlFile.EOF)
{
return settings;
}
try
{
xmlFile.MoveToContent();
}
catch (XmlException)
{
return settings;
}
var xml = XElement.Load(xmlFile);
var recentAssembliesElement = xml.Element(RecentAssembliesElementName);
if (recentAssembliesElement != null)
{
var recentAssemblyElements = recentAssembliesElement.Elements(RecentAssemblyElementName);
foreach (var recentAssemblyElement in recentAssemblyElements)
{
var filePath = (string)recentAssemblyElement;
settings.AddRecentAssembly(filePath);
}
}
var autoReloadAssembliesElement = xml.Element(AutoReloadAssembliesElementName);
if (autoReloadAssembliesElement != null)
{
if (!bool.TryParse(autoReloadAssembliesElement.Value, out var autoReloadAssemblies))
{
autoReloadAssemblies = true;
}
settings.autoReloadAssemblies = autoReloadAssemblies;
}
else
{
settings.autoReloadAssemblies = true;
}
return settings;
}
}
}
}
@@ -0,0 +1,105 @@
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Xml.Linq;
namespace Xunit.Runner.Wpf.Persistence
{
internal static partial class Storage
{
private static class WindowPlacement
{
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
private struct WINDOWPLACEMENT
{
public int length;
public int flags;
public int showCmd;
public POINT ptMinPosition;
public POINT ptMaxPosition;
public RECT rcNormalPosition;
}
private const int SW_SHOWNORMAL = 1;
private const int SW_SHOWMINIMIZED = 2;
[DllImport("user32.dll")]
private static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);
[DllImport("user32.dll")]
private static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl);
private const string WindowPlacementElementName = "window_placement";
private const string ShowCommandElementName = "show_command";
private const string MinPositionElementName = "min_position";
private const string MaxPositionElementName = "max_position";
private const string NormalPositionElementName = "normal_position";
private const string XAttributeName = "x";
private const string YAttributeName = "y";
private const string LeftAttributeName = "left";
private const string TopAttributeName = "top";
private const string RightAttributeName = "right";
private const string BottomAttributeName = "bottom";
public static void Restore(Window window, XElement xml)
{
var placement = new WINDOWPLACEMENT();
placement.length = Marshal.SizeOf(typeof(WINDOWPLACEMENT));
placement.flags = 0;
placement.showCmd = (int)xml.Element(ShowCommandElementName);
placement.ptMinPosition.x = (int)xml.Element(MinPositionElementName).Attribute(XAttributeName);
placement.ptMinPosition.y = (int)xml.Element(MinPositionElementName).Attribute(YAttributeName);
placement.ptMaxPosition.x = (int)xml.Element(MaxPositionElementName).Attribute(XAttributeName);
placement.ptMaxPosition.y = (int)xml.Element(MaxPositionElementName).Attribute(YAttributeName);
placement.rcNormalPosition.left = (int)xml.Element(NormalPositionElementName).Attribute(LeftAttributeName);
placement.rcNormalPosition.top = (int)xml.Element(NormalPositionElementName).Attribute(TopAttributeName);
placement.rcNormalPosition.right = (int)xml.Element(NormalPositionElementName).Attribute(RightAttributeName);
placement.rcNormalPosition.bottom = (int)xml.Element(NormalPositionElementName).Attribute(BottomAttributeName);
var windowInteropHelper = new WindowInteropHelper(window);
SetWindowPlacement(windowInteropHelper.Handle, ref placement);
}
public static XElement Save(Window window)
{
var windowInteropHelper = new WindowInteropHelper(window);
var placement = new WINDOWPLACEMENT();
GetWindowPlacement(windowInteropHelper.Handle, out placement);
return
new XElement(WindowPlacementElementName,
new XElement(ShowCommandElementName, (placement.showCmd == SW_SHOWMINIMIZED ? SW_SHOWNORMAL : placement.showCmd)),
new XElement(MinPositionElementName,
new XAttribute(XAttributeName, placement.ptMinPosition.x),
new XAttribute(YAttributeName, placement.ptMinPosition.y)),
new XElement(MaxPositionElementName,
new XAttribute(XAttributeName, placement.ptMaxPosition.x),
new XAttribute(YAttributeName, placement.ptMaxPosition.y)),
new XElement(NormalPositionElementName,
new XAttribute(LeftAttributeName, placement.rcNormalPosition.left),
new XAttribute(TopAttributeName, placement.rcNormalPosition.top),
new XAttribute(RightAttributeName, placement.rcNormalPosition.right),
new XAttribute(BottomAttributeName, placement.rcNormalPosition.bottom)));
}
}
}
}
+86
View File
@@ -0,0 +1,86 @@
using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Text;
using System.Windows;
using System.Xml;
using System.Xml.Linq;
namespace Xunit.Runner.Wpf.Persistence
{
internal static partial class Storage
{
private const string WindowLayoutFileName = "window_layout.xml";
private static string GetWindowLayoutFileName(Window window) => $"{window.Name}_{WindowLayoutFileName}";
private static IsolatedStorageFile GetStorageFile() => IsolatedStorageFile.GetUserStoreForDomain();
public static XmlTextReader OpenXmlFile(string fileName)
{
var storage = GetStorageFile();
if (!storage.FileExists(fileName))
{
return null;
}
var fileStream = storage.OpenFile(fileName, FileMode.Open, FileAccess.Read);
var reader = new XmlTextReader(fileStream);
reader.WhitespaceHandling = WhitespaceHandling.None;
return reader;
}
public static XmlTextWriter CreateXmlFile(string fileName)
{
var storage = GetStorageFile();
var fileStream = storage.CreateFile(fileName);
var writer = new XmlTextWriter(fileStream, Encoding.UTF8);
writer.Formatting = Formatting.Indented;
return writer;
}
public static void RestoreWindowLayout(Window window)
{
if (window == null)
{
throw new ArgumentNullException(nameof(window));
}
if (string.IsNullOrWhiteSpace(window.Name))
{
throw new ArgumentException("Name is not set.", nameof(window));
}
using (var windowLayoutReader = OpenXmlFile(GetWindowLayoutFileName(window)))
{
if (windowLayoutReader != null)
{
windowLayoutReader.MoveToContent();
var xml = XElement.Load(windowLayoutReader);
WindowPlacement.Restore(window, xml);
}
}
}
public static void SaveWindowLayout(Window window)
{
if (window == null)
{
throw new ArgumentNullException(nameof(window));
}
if (string.IsNullOrWhiteSpace(window.Name))
{
throw new ArgumentException("Name is not set.", nameof(window));
}
using (var windowLayoutWriter = CreateXmlFile(GetWindowLayoutFileName(window)))
{
var xml = WindowPlacement.Save(window);
xml.Save(windowLayoutWriter);
}
}
}
}
+1 -36
View File
@@ -1,25 +1,4 @@
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("xunit.runner.wpf")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("xunit.runner.wpf")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
using System.Windows;
//In order to begin building localizable applications, set
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
@@ -39,17 +18,3 @@ using System.Windows;
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
+18 -26
View File
@@ -8,10 +8,10 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace xunit.runner.wpf.Properties
{
namespace Xunit.Runner.Wpf.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
@@ -22,48 +22,40 @@ namespace xunit.runner.wpf.Properties
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ((resourceMan == null))
{
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("xunit.runner.wpf.Properties.Resources", typeof(Resources).Assembly);
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Xunit.Runner.Wpf.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set
{
set {
resourceCulture = value;
}
}
-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 xunit.runner.wpf.Properties
{
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
return defaultInstance;
}
}
}
}
@@ -1,7 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>
@@ -1,6 +1,6 @@
using System.IO;
namespace xunit.runner.wpf.ViewModel
namespace Xunit.Runner.Wpf.ViewModel
{
public class AssemblyAndConfigFile
{
+578 -110
View File
@@ -1,75 +1,178 @@
using GalaSoft.MvvmLight;
using System.Windows.Input;
using System;
using System.Windows;
using GalaSoft.MvvmLight.CommandWpf;
using Microsoft.Win32;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Linq;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System.IO.Pipes;
using System.IO;
using System.Text;
using xunit.runner.data;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using Microsoft.Win32;
using Microsoft.WindowsAPICodePack.Taskbar;
using Xunit.Runner.Data;
using Xunit.Runner.Wpf.Persistence;
namespace xunit.runner.wpf.ViewModel
namespace Xunit.Runner.Wpf.ViewModel
{
public class MainViewModel : ViewModelBase
{
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 TraitSelectionChangedCommand { get; }
public ICommand TraitsClearCommand { get; }
public ICommand AssemblyReloadCommand { get; }
public ICommand AssemblyReloadAllCommand { get; }
public ICommand AssemblyRemoveCommand { get; }
public ICommand AssemblyRemoveAllCommand { get; }
public ICommand AutoReloadAssembliesCommand { get; }
public CommandBindingCollection CommandBindings { get; }
public MainViewModel()
{
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.MethodsCaption = "Methods (0)";
TestCases = new FilteredCollectionView<TestCaseViewModel, SearchQuery>(
this.testUtil = new Xunit.Runner.Wpf.Impl.RemoteTestUtil(Dispatcher.CurrentDispatcher);
this.assemblyWatcher = new Impl.TestAssemblyWatcher(Dispatcher.CurrentDispatcher);
this.TestCasesCaption = "Test Cases (0)";
this.FilteredTestCases = new FilteredCollectionView<TestCaseViewModel, SearchQuery>(
allTestCases, TestCaseMatches, searchQuery, TestComparer.Instance);
this.TestCases.CollectionChanged += TestCases_CollectionChanged;
this.FilteredTestCases.CollectionChanged += TestCases_CollectionChanged;
this.ExitCommand = new RelayCommand(OnExecuteExit);
this.WindowLoadedCommand = new RelayCommand(OnExecuteWindowLoaded);
this.RunCommand = new RelayCommand(OnExecuteRun, CanExecuteRun);
this.WindowClosingCommand = new RelayCommand<CancelEventArgs>(OnExecuteWindowClosing);
this.RunAllCommand = new RelayCommand(OnExecuteRunAll, CanExecuteRunAll);
this.RunSelectedCommand = new RelayCommand(OnExecuteRunSelected, CanExecuteRunSelected);
this.CancelCommand = new RelayCommand(OnExecuteCancel, CanExecuteCancel);
this.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.Contains(searchQuery.SearchString))
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 searchQuery.IncludePassedTests;
return noFilter || searchQuery.FilterPassedTests;
case TestState.Skipped:
return searchQuery.IncludeSkippedTests;
return noFilter || searchQuery.FilterSkippedTests;
case TestState.Failed:
return searchQuery.IncludeFailedTests;
return noFilter || searchQuery.FilterFailedTests;
case TestState.NotRun:
return true;
return noFilter;
default:
Debug.Assert(false, "What state is this test case in?");
@@ -79,29 +182,97 @@ namespace xunit.runner.wpf.ViewModel
private void TestCases_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
MethodsCaption = $"Methods ({TestCases.Count})";
MaximumProgress = TestCases.Count;
UpdateTestCaseInfo(useSelected: false);
ClearSelectionFlags();
}
public ICommand ExitCommand { get; } = new RelayCommand(OnExecuteExit);
public ICommand WindowLoadedCommand { get; }
public RelayCommand RunCommand { get; }
public RelayCommand CancelCommand { get; }
public CommandBindingCollection CommandBindings { get; }
private string methodsCaption;
public string MethodsCaption
private void ClearSelectionFlags()
{
get { return methodsCaption; }
private set { Set(ref methodsCaption, value); }
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); }
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;
@@ -129,14 +300,24 @@ namespace xunit.runner.wpf.ViewModel
public int MaximumProgress
{
get { return maximumProgress; }
set { Set(ref maximumProgress, value); }
set
{
Set(ref maximumProgress, value);
UpdateProgress();
}
}
private TestState currentRunState;
public TestState CurrentRunState
{
get { return currentRunState; }
set { Set(ref currentRunState, value); }
set
{
Set(ref currentRunState, value);
UpdateProgress();
}
}
private string output = string.Empty;
@@ -169,7 +350,7 @@ namespace xunit.runner.wpf.ViewModel
.ContinueWith(
x =>
{
TestCases.FilterArgument = searchQuery;
FilteredTestCases.FilterArgument = searchQuery;
},
token,
TaskContinuationOptions.None,
@@ -187,14 +368,12 @@ namespace xunit.runner.wpf.ViewModel
};
}
public ObservableCollection<TestAssemblyViewModel> Assemblies { get; } = new ObservableCollection<TestAssemblyViewModel>();
public FilteredCollectionView<TestCaseViewModel, SearchQuery> TestCases { get; }
private async void OnExecuteOpen(object sender, ExecutedRoutedEventArgs e)
{
var fileDialog = new OpenFileDialog
{
Filter = "Unit Test Assemblies|*.dll",
Multiselect = true
};
if (fileDialog.ShowDialog(Application.Current.MainWindow) != true)
@@ -202,8 +381,8 @@ namespace xunit.runner.wpf.ViewModel
return;
}
var fileName = fileDialog.FileName;
await AddAssemblies(new[] { new AssemblyAndConfigFile(fileName, configFileName: null) });
var assemblies = fileDialog.FileNames.Select(x => new AssemblyAndConfigFile(x, configFileName: null));
await AddAssemblies(assemblies);
}
private async Task AddAssemblies(IEnumerable<AssemblyAndConfigFile> assemblies)
@@ -213,28 +392,126 @@ namespace xunit.runner.wpf.ViewModel
return;
}
var loadingDialog = new LoadingDialog { Owner = MainWindow.Instance };
var newAssemblyViewModels = new List<TestAssemblyViewModel>();
try
{
await ExecuteTestSessionOperation(() =>
await this.ExecuteTestSessionOperation(() =>
{
var testSessionList = new List<ITestSession>();
var taskList = new List<Task>();
foreach (var assembly in assemblies)
{
var assemblyPath = assembly.AssemblyFileName;
var session = this.testUtil.Discover(assemblyPath, cancellationTokenSource.Token);
session.TestDiscovered += OnTestDiscovered;
taskList.Add(this.testUtil.Discover(assembly.AssemblyFileName, this.OnTestsDiscovered, this.cancellationTokenSource.Token));
testSessionList.Add(session);
Assemblies.Add(new TestAssemblyViewModel(assembly));
var assemblyViewModel = new TestAssemblyViewModel(assembly);
newAssemblyViewModels.Add(assemblyViewModel);
this.Assemblies.Add(assemblyViewModel);
this.settings.AddRecentAssembly(assembly.AssemblyFileName);
assemblyViewModel.State = AssemblyState.Loading;
}
return testSessionList;
return taskList;
});
}
finally
{
loadingDialog.Close();
foreach (var assemblyViewModel in newAssemblyViewModels)
{
assemblyViewModel.State = AssemblyState.Ready;
assemblyWatcher.AddAssembly(assemblyViewModel.FileName);
}
RebuildRecentAssembliesMenu();
}
}
public bool ReloadAssemblies(IEnumerable<string> assemblies)
{
if (IsBusy)
{
return false;
}
var testAssemblies = Assemblies.Where(assembly => assemblies.Contains(assembly.FileName));
Application.Current.Dispatcher.InvokeAsync(() => ReloadAssemblies(testAssemblies));
return true;
}
private async Task ReloadAssemblies(IEnumerable<TestAssemblyViewModel> assemblies)
{
try
{
await ExecuteTestSessionOperation(() =>
{
var taskList = new List<Task>();
foreach (var assemblyViewModel in assemblies)
{
assemblyViewModel.State = AssemblyState.Loading;
var assemblyFileName = assemblyViewModel.FileName;
RemoveAssemblyTestCases(assemblyFileName);
taskList.Add(this.testUtil.Discover(assemblyFileName, OnTestsDiscovered, cancellationTokenSource.Token));
}
return taskList;
});
RebuildTraits();
}
finally
{
foreach (var assemblyViewModel in assemblies)
{
assemblyViewModel.State = AssemblyState.Ready;
}
}
}
private void RemoveAssemblies(IEnumerable<TestAssemblyViewModel> assemblies)
{
foreach (var assembly in assemblies.ToList())
{
assemblyWatcher.RemoveAssembly(assembly.FileName);
RemoveAssemblyTestCases(assembly.FileName);
Assemblies.Remove(assembly);
}
RebuildTraits();
}
private void RemoveAssemblyTestCases(string assemblyPath)
{
var i = 0;
while (i < this.allTestCases.Count)
{
if (string.Compare(this.allTestCases[i].AssemblyFileName, assemblyPath, StringComparison.OrdinalIgnoreCase) == 0)
{
this.allTestCaseUniqueIDs.Remove(this.allTestCases[i].UniqueID);
this.allTestCases.RemoveAt(i);
}
else
{
i++;
}
}
}
/// <summary>
/// Reloading an assembly could have changed the traits. There is no easy way
/// to selectively edit this list (traits can cross assembly boundaries). Just
/// do a full reload instead.
/// way to
/// </summary>
private void RebuildTraits()
{
this.traitCollectionView.Collection.Clear();
foreach (var testCase in this.allTestCases)
{
this.traitCollectionView.AddRange(testCase.Traits);
}
}
@@ -244,7 +521,8 @@ namespace xunit.runner.wpf.ViewModel
set
{
isBusy = value;
RunCommand.RaiseCanExecuteChanged();
RunAllCommand.RaiseCanExecuteChanged();
RunSelectedCommand.RaiseCanExecuteChanged();
CancelCommand.RaiseCanExecuteChanged();
}
}
@@ -259,6 +537,11 @@ namespace xunit.runner.wpf.ViewModel
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())
@@ -281,60 +564,96 @@ namespace xunit.runner.wpf.ViewModel
=> (fileName?.EndsWith(".config", StringComparison.OrdinalIgnoreCase) ?? false) ||
(fileName?.EndsWith(".json", StringComparison.OrdinalIgnoreCase) ?? false);
private bool CanExecuteRun()
=> !IsBusy && TestCases.Any();
private bool CanExecuteRunAll()
=> !IsBusy && FilteredTestCases.Any();
private async void OnExecuteRun()
private bool CanExecuteRunSelected()
=> !IsBusy && SelectedTestCase != null;
private async void OnExecuteRunAll()
{
await ExecuteTestSessionOperation(RunTests);
Debug.Assert(this.testsToRun == null);
UpdateTestCaseInfo(useSelected: false);
await ExecuteTestSessionOperation(RunFilteredTests);
this.testsToRun = null;
}
private List<ITestSession> RunTests()
private List<Task> RunFilteredTests()
{
return RunTests(FilteredTestCases.ToImmutableList());
}
private async void OnExecuteRunSelected()
{
Debug.Assert(this.testsToRun == null);
Debug.Assert(this.SelectedTestCase != null);
UpdateTestCaseInfo(useSelected: true);
await ExecuteTestSessionOperation(RunSelectedTests);
this.testsToRun = null;
}
private List<Task> RunSelectedTests()
{
return RunTests(ImmutableList.CreateRange(this.FilteredTestCases.Where(tc => tc.IsSelected)));
}
private List<Task> RunTests(ImmutableList<TestCaseViewModel> tests)
{
Debug.Assert(this.isBusy);
Debug.Assert(this.cancellationTokenSource != null);
Debug.Assert(this.testsToRun == null);
TestsCompleted = 0;
TestsRunning = 0;
TestsPassed = 0;
TestsFailed = 0;
TestsSkipped = 0;
CurrentRunState = TestState.NotRun;
Output = string.Empty;
foreach (var tc in TestCases)
this.testsToRun = tests;
foreach (var tc in this.testsToRun)
{
tc.State = TestState.NotRun;
}
// TODO: Need a way to filter based on traits
var runAll = this.testsToRun.Count == this.allTestCases.Count;
var testSessionList = new List<Task>();
var runAll = TestCases.Count == this.allTestCases.Count;
var testSessionList = new List<ITestSession>();
foreach (var assemblyPath in TestCases.Select(x => x.AssemblyFileName).Distinct())
foreach (var assemblyFileName in this.testsToRun.Select(x => x.AssemblyFileName).Distinct())
{
ITestRunSession session;
Task task;
if (runAll)
{
session = this.testUtil.RunAll(assemblyPath, this.cancellationTokenSource.Token);
task = this.testUtil.RunAll(assemblyFileName, OnTestStateChange, this.cancellationTokenSource.Token);
}
else
{
var testCaseDisplayNames = TestCases
.Where(x => x.AssemblyFileName == assemblyPath)
.Select(x => x.DisplayName)
.ToImmutableArray();
session = this.testUtil.RunSpecific(assemblyPath, testCaseDisplayNames, this.cancellationTokenSource.Token);
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, this.cancellationTokenSource.Token);
}
session.TestFinished += OnTestFinished;
testSessionList.Add(session);
testSessionList.Add(task);
}
return testSessionList;
}
private async Task ExecuteTestSessionOperation(Func<List<ITestSession>> operation)
private async Task ExecuteTestSessionOperation(Func<List<Task>> operation)
{
Debug.Assert(!this.IsBusy);
Debug.Assert(this.cancellationTokenSource == null);
@@ -344,8 +663,8 @@ namespace xunit.runner.wpf.ViewModel
this.IsBusy = true;
this.cancellationTokenSource = new CancellationTokenSource();
var testSessionList = operation();
await Task.WhenAll(testSessionList.Select(x => x.Task));
var taskList = operation();
await Task.WhenAll(taskList);
}
catch (Exception ex)
{
@@ -359,35 +678,95 @@ namespace xunit.runner.wpf.ViewModel
}
}
private void OnTestDiscovered(object sender, TestCaseDataEventArgs e)
private void OnTestsDiscovered(IEnumerable<TestCaseData> testCases)
{
var t = e.TestCaseData;
allTestCases.Add(new TestCaseViewModel(t.SerializedForm, t.DisplayName, t.AssemblyPath));
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 OnTestFinished(object sender, TestResultDataEventArgs e)
private void OnTestStateChange(IEnumerable<TestResultData> testResultData)
{
var testCase = TestCases.Single(x => x.DisplayName == e.TestCaseDisplayName);
testCase.State = e.TestState;
Debug.Assert(this.testsToRun != null);
TestsCompleted++;
switch (e.TestState)
foreach (var result in testResultData)
{
case TestState.Passed:
TestsPassed++;
break;
case TestState.Failed:
TestsFailed++;
Output = Output + e.Output;
break;
case TestState.Skipped:
TestsSkipped++;
break;
}
var testCase = this.testsToRun.Single(x => x.UniqueID == result.TestCaseUniqueID);
testCase.State = result.TestState;
if (e.TestState > CurrentRunState)
{
CurrentRunState = e.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;
}
}
}
@@ -402,36 +781,115 @@ namespace xunit.runner.wpf.ViewModel
this.cancellationTokenSource.Cancel();
}
public bool IncludePassedTests
private void OnExecuteTraitCheckedChanged(TraitViewModel trait)
{
get { return searchQuery.IncludePassedTests; }
this.searchQuery.TraitSet = this.traitCollectionView.GetCheckedTraits();
FilterAfterDelay();
}
private void OnExecuteTraitsClear()
{
foreach (var cur in this.traitCollectionView.Collection)
{
cur.IsChecked = false;
}
}
private bool CanExecuteAssemblyReload()
{
return SelectedAssemblies.Count > 0;
}
private async void OnExecuteAssemblyReload()
{
await ReloadAssemblies(SelectedAssemblies);
}
private async void OnExecuteAssemblyReloadAll()
{
await ReloadAssemblies(Assemblies);
}
private bool CanExecuteAssemblyRemove()
{
return SelectedAssemblies.Count > 0;
}
private void OnExecuteAssemblyRemove()
{
RemoveAssemblies(SelectedAssemblies);
}
private void OnExecuteAssemblyRemoveAll()
{
RemoveAssemblies(Assemblies.ToArray());
}
private void OnToggleAutoReloadAssemblies()
{
ToggleReloadAssemblies();
}
private void ToggleReloadAssemblies()
{
this.settings.ToggleAutoReloadAssemblies();
AutoReloadAssemblies = this.settings.GetAutoReloadAssemblies();
UpdateAutoReloadStatus();
}
private void UpdateAutoReloadStatus()
{
if (AutoReloadAssemblies)
{
assemblyWatcher.EnableWatch(ReloadAssemblies);
}
else
{
assemblyWatcher.DisableWatch();
}
}
public bool FilterRunningTests
{
get { return searchQuery.FilterRunningTests; }
set
{
if (Set(ref searchQuery.IncludePassedTests, value))
if (Set(ref searchQuery.FilterRunningTests, value))
{
FilterAfterDelay();
}
}
}
public bool IncludeFailedTests
public bool FilterPassedTests
{
get { return searchQuery.IncludeFailedTests; }
get { return searchQuery.FilterPassedTests; }
set
{
if (Set(ref searchQuery.IncludeFailedTests, value))
if (Set(ref searchQuery.FilterPassedTests, value))
{
FilterAfterDelay();
}
}
}
public bool IncludeSkippedTests
public bool FilterFailedTests
{
get { return searchQuery.IncludeSkippedTests; }
get { return searchQuery.FilterFailedTests; }
set
{
if (Set(ref searchQuery.IncludeSkippedTests, value))
if (Set(ref searchQuery.FilterFailedTests, value))
{
FilterAfterDelay();
}
}
}
public bool FilterSkippedTests
{
get { return searchQuery.FilterSkippedTests; }
set
{
if (Set(ref searchQuery.FilterSkippedTests, value))
{
FilterAfterDelay();
}
@@ -444,7 +902,17 @@ namespace xunit.runner.wpf.ViewModel
public static TestComparer Instance { get; } = new TestComparer();
public int Compare(TestCaseViewModel x, TestCaseViewModel y)
=> StringComparer.Ordinal.Compare(x.DisplayName, y.DisplayName);
{
int result = StringComparer.OrdinalIgnoreCase.Compare(x.DisplayName, y.DisplayName);
if (result != 0)
return result;
result = StringComparer.Ordinal.Compare(x.DisplayName, y.DisplayName);
if (result != 0)
return result;
return StringComparer.Ordinal.Compare(x.UniqueID, y.UniqueID);
}
private TestComparer() { }
}
@@ -0,0 +1,16 @@
using System.Windows.Input;
namespace Xunit.Runner.Wpf.ViewModel
{
public class RecentAssemblyViewModel
{
public string FilePath { get; }
public ICommand Command { get; }
public RecentAssemblyViewModel(string filePath, ICommand command)
{
this.FilePath = filePath;
this.Command = command;
}
}
}
+7 -10
View File
@@ -1,17 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace xunit.runner.wpf.ViewModel
namespace Xunit.Runner.Wpf.ViewModel
{
public class SearchQuery
{
public bool IncludeFailedTests = true;
public bool IncludePassedTests = true;
public bool IncludeSkippedTests = true;
public bool FilterRunningTests = false;
public bool FilterFailedTests = false;
public bool FilterPassedTests = false;
public bool FilterSkippedTests = false;
public string SearchString = string.Empty;
public ISet<TraitViewModel> TraitSet = new HashSet<TraitViewModel>(TraitViewModel.EqualityComparer);
}
}
@@ -1,24 +1,39 @@
using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using GalaSoft.MvvmLight;
namespace xunit.runner.wpf.ViewModel
namespace Xunit.Runner.Wpf.ViewModel
{
public class TestAssemblyViewModel : ViewModelBase
{
private readonly AssemblyAndConfigFile assembly;
private readonly AssemblyAndConfigFile _assembly;
private bool _isSelected;
private AssemblyState _state;
public TestAssemblyViewModel(AssemblyAndConfigFile assembly)
{
this.assembly = assembly;
_assembly = assembly;
}
public string FileName => assembly.AssemblyFileName;
public string ConfigFileName => Path.GetFileNameWithoutExtension(assembly.ConfigFileName);
public string DisplayName => Path.GetFileNameWithoutExtension(assembly.AssemblyFileName);
public string FileName => _assembly.AssemblyFileName;
public string ConfigFileName => Path.GetFileNameWithoutExtension(_assembly.ConfigFileName);
public string DisplayName => Path.GetFileNameWithoutExtension(_assembly.AssemblyFileName);
public bool IsSelected
{
get { return _isSelected; }
set { Set(ref _isSelected, value, nameof(IsSelected)); }
}
public AssemblyState State
{
get { return _state; }
set { Set(ref _state, value, nameof(State)); }
}
}
public enum AssemblyState
{
Ready,
Loading
}
}
+27 -20
View File
@@ -1,34 +1,41 @@
using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using xunit.runner.data;
using System.Collections.Generic;
using System.Collections.Immutable;
using GalaSoft.MvvmLight;
using Xunit.Runner.Data;
namespace xunit.runner.wpf.ViewModel
namespace Xunit.Runner.Wpf.ViewModel
{
public class TestCaseViewModel : ViewModelBase
{
public TestCaseViewModel(string testCase, string displayName, string assemblyFileName)
{
this.TestCase = testCase;
this.DisplayName = displayName;
this.AssemblyFileName = assemblyFileName;
}
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);
private TestState state = TestState.NotRun;
public TestState State
{
get { return state; }
set { Set(ref state, value); }
get { return _state; }
set { Set(ref _state, value); }
}
public string AssemblyFileName { get; }
public TestCaseViewModel(string displayName, string uniqueID, string skipReason, string assemblyFileName, IEnumerable<TraitViewModel> traits)
{
this.DisplayName = displayName;
this.UniqueID = uniqueID;
this.SkipReason = skipReason;
this.AssemblyFileName = assemblyFileName;
this.Traits = traits.ToImmutableArray();
public string TestCase { get; }
if (!string.IsNullOrEmpty(skipReason))
{
_state = TestState.Skipped;
}
}
}
}
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace Xunit.Runner.Wpf.ViewModel
{
public sealed partial class TraitCollectionView
{
public ObservableCollection<TraitViewModel> Collection { get; } = new ObservableCollection<TraitViewModel>();
public void AddRange(IEnumerable<TraitViewModel> traits)
{
foreach (var trait in traits)
{
var index = Collection.BinarySearch(trait, TraitViewModel.Comparer);
if (index < 0)
{
Collection.Insert(~index, trait);
}
else
{
// This trait already exists, add more values.
var originalTrait = Collection[index];
originalTrait.AddValues(trait.Children.Select(x => x.Text));
}
}
}
public TraitViewModel GetOrAdd(string text)
{
var index = this.Collection.BinarySearch(text, StringComparer.Ordinal, vm => vm.Text);
if (index < 0)
{
var viewModel = new TraitViewModel(text);
this.Collection.Insert(~index, viewModel);
return viewModel;
}
return this.Collection[index];
}
public ISet<TraitViewModel> GetCheckedTraits()
{
return new HashSet<TraitViewModel>(
Collection.SelectMany(x => x.Children).Where(x => x.IsChecked == true),
comparer: TraitViewModel.EqualityComparer);
}
}
}
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
namespace Xunit.Runner.Wpf.ViewModel
{
public partial class TraitViewModel
{
private static readonly TraitViewModelComparer _comparer = new TraitViewModelComparer();
internal static IComparer<TraitViewModel> Comparer => _comparer;
internal static IEqualityComparer<TraitViewModel> EqualityComparer => _comparer;
private class TraitViewModelComparer : IEqualityComparer<TraitViewModel>, IComparer<TraitViewModel>
{
public int Compare(TraitViewModel x, TraitViewModel y) => StringComparer.Ordinal.Compare(x.Text, y.Text);
public bool Equals(TraitViewModel x, TraitViewModel y) => StringComparer.Ordinal.Equals(x.Text, y.Text);
public int GetHashCode(TraitViewModel obj) => obj.Text.GetHashCode();
}
}
}
@@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using GalaSoft.MvvmLight;
namespace Xunit.Runner.Wpf.ViewModel
{
public partial class TraitViewModel : ViewModelBase
{
private readonly TraitViewModel _parent;
private bool? _isChecked;
private bool _isExpanded;
private string _text;
public ObservableCollection<TraitViewModel> Children { get; }
public TraitViewModel(string text)
: this(null, text)
{
}
private TraitViewModel(TraitViewModel parent, string text)
{
this._parent = parent;
this._isChecked = false;
this._isExpanded = true;
this._text = text;
this.Children = new ObservableCollection<TraitViewModel>();
}
private void SetIsChecked(bool? value, bool updateChildren, bool updateParent)
{
if (value == this._isChecked)
{
return;
}
this._isChecked = value;
if (updateChildren && value != null)
{
foreach (var child in this.Children)
{
child.SetIsChecked(value, updateChildren: true, updateParent: false);
}
}
if (updateParent && _parent != null)
{
_parent.VerifyCheckState();
}
this.RaisePropertyChanged(nameof(IsChecked));
}
private void VerifyCheckState()
{
bool? state = null;
var isFirst = true;
foreach (var child in this.Children)
{
if (isFirst)
{
state = child.IsChecked;
isFirst = false;
}
else if (state != child.IsChecked)
{
state = null;
break;
}
}
this.SetIsChecked(state, updateChildren: false, updateParent: true);
}
public void AddValues(IEnumerable<string> values)
{
foreach (var value in values)
{
var index = this.Children.BinarySearch(value, StringComparer.Ordinal.Compare, v => v.Text);
if (index < 0)
{
this.Children.Insert(~index, new TraitViewModel(this, value));
}
}
}
public TraitViewModel GetOrAdd(string text)
{
var index = this.Children.BinarySearch(text, StringComparer.Ordinal, vm => vm.Text);
if (index < 0)
{
var viewModel = new TraitViewModel(this, text);
this.Children.Insert(~index, viewModel);
return viewModel;
}
return this.Children[index];
}
public bool? IsChecked
{
get { return _isChecked; }
set { SetIsChecked(value, updateChildren: true, updateParent: true); }
}
public bool IsExpanded
{
get { return _isExpanded; }
set { Set(ref _isExpanded, value); }
}
public string Text
{
get { return _text; }
set { Set(ref _text, value); }
}
}
}
@@ -12,11 +12,10 @@
See http://www.galasoft.ch/mvvm
*/
using GalaSoft.MvvmLight;
using CommonServiceLocator;
using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;
namespace xunit.runner.wpf.ViewModel
namespace Xunit.Runner.Wpf.ViewModel
{
/// <summary>
/// This class contains static references to all the view models in the
-9
View File
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="CommonServiceLocator" version="1.3" targetFramework="net452" />
<package id="MvvmLight" version="5.1.1.0" targetFramework="net452" />
<package id="MvvmLightLibs" version="5.1.1.0" targetFramework="net452" />
<package id="System.Collections.Immutable" version="1.1.37" targetFramework="net452" />
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
<package id="xunit.runner.utility" version="2.1.0-beta4-build3109" targetFramework="net452" />
</packages>
+33 -167
View File
@@ -1,180 +1,46 @@
<?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')" />
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<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.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<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>
<TargetFramework>netcoreapp3.0</TargetFramework>
<UseWpf>true</UseWpf>
<RootNamespace>Xunit.Runner.Wpf</RootNamespace>
<NoWarn>NU1701;$(NoWarn)</NoWarn>
<ApplicationIcon>Artwork\Application.ico</ApplicationIcon>
<Authors>Pilchie</Authors>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackAsTool>true</PackAsTool>
<AssetTargetFallback>net472</AssetTargetFallback>
<Description>XUnit Gui written in WPF</Description>
<PackageProjectUrl>https://github.com/Pilchie/xunit.runner.wpf</PackageProjectUrl>
<RepositoryUrl>https://github.com/Pilchie/xunit.runner.wpf</RepositoryUrl>
<PackageTags>XUnit Gui test runner</PackageTags>
<NeutralLanguage>en-us</NeutralLanguage>
<PackageVersion Condition="'$(appveyor_build_vzersion)' != ''">$(appveyor_build_version)</PackageVersion>
<PackageVersion Condition="'$(NuPkgVersion)' == ''">1.0.0-local</PackageVersion>
<AssemblyVersion Condition="'$(appveyor_build_version)' != ''">$(appveyor_build_version)</AssemblyVersion>
<FileVersion Condition="'$(appveyor_build_version)' != ''">$(appveyor_build_version)</FileVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="GalaSoft.MvvmLight, Version=5.1.1.35049, Culture=neutral, PublicKeyToken=e7570ab207bcb616, processorArchitecture=MSIL">
<HintPath>..\packages\MvvmLightLibs.5.1.1.0\lib\net45\GalaSoft.MvvmLight.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="GalaSoft.MvvmLight.Extras, Version=5.1.1.35049, Culture=neutral, PublicKeyToken=669f0b5e8f868abf, processorArchitecture=MSIL">
<HintPath>..\packages\MvvmLightLibs.5.1.1.0\lib\net45\GalaSoft.MvvmLight.Extras.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="GalaSoft.MvvmLight.Platform, Version=5.1.1.35053, Culture=neutral, PublicKeyToken=5f873c45e98af8a1, processorArchitecture=MSIL">
<HintPath>..\packages\MvvmLightLibs.5.1.1.0\lib\net45\GalaSoft.MvvmLight.Platform.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Practices.ServiceLocation, Version=1.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Collections.Immutable, Version=1.1.37.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.1.1.37\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\MvvmLightLibs.5.1.1.0\lib\net45\System.Windows.Interactivity.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="CommandBindings.cs" />
<Compile Include="Converters\TestStateConverter.cs" />
<Compile Include="Extensions.cs" />
<Compile Include="FilteredCollectionView.cs" />
<Compile Include="Impl\RemoteTestUtil.Connection.cs" />
<Compile Include="Impl\RemoteTestUtil.DiscoverSession.cs" />
<Compile Include="Impl\RemoteTestUtil.RunSession.cs" />
<Compile Include="Impl\RemoteTestUtil.BackgroundRunner.cs" />
<Compile Include="Impl\RemoteTestUtil.cs" />
<Compile Include="ITestUtil.cs" />
<Compile Include="LoadingDialog.xaml.cs">
<DependentUpon>LoadingDialog.xaml</DependentUpon>
</Compile>
<Compile Include="ViewModel\AssemblyAndConfigFile.cs" />
<Compile Include="ViewModel\MainViewModel.cs" />
<Compile Include="ViewModel\SearchQuery.cs" />
<Compile Include="ViewModel\TestCaseViewModel.cs" />
<Compile Include="ViewModel\TestAssemblyViewModel.cs" />
<Compile Include="ViewModel\ViewModelLocator.cs" />
<Page Include="LoadingDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<AppDesigner Include="Properties\" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<Resource Include="Artwork\xunit-dot-net-small-logo.png" />
<Resource Include="Artwork\Passed.ico" />
<Resource Include="Artwork\Skipped.ico" />
<Resource Include="Artwork\Failed.ico" />
<PackageReference Include="CommonServiceLocator" Version="2.0.2" />
<PackageReference Include="Microsoft.Windows.Compatibility" Version="2.1.0-preview.18571.3" />
<PackageReference Include="MvvmLightLibs" Version="5.4.1.1" />
<PackageReference Include="System.Collections.Immutable" Version="1.4.0-preview1-25305-02" />
<PackageReference Include="System.Windows.Interactivity.WPF" Version="2.0.20525" />
<PackageReference Include="WindowsAPICodePack" Version="1.1.0" />
</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>
<ProjectReference Include="..\xunit.runner.data\xunit.runner.data.csproj" />
<ProjectReference Include="..\xunit.runner.worker\xunit.runner.worker.csproj" />
</ItemGroup>
<ItemGroup>
<WCFMetadata Include="Service References\" />
<Resource Include="Artwork\**\*.png" />
<Resource Include="Artwork\Application.ico" />
</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>