113 Commits

Author SHA1 Message Date
Kevin Pilch a96ce278d2 Fix nullability warnings 2018-05-05 16:21:00 -07:00
Kevin Pilch a10c13626c Remove compilers package 2018-05-05 15:22:08 -07:00
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
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
82 changed files with 2567 additions and 1087 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)
+8 -11
View File
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\xunit.core.2.1.0-beta4-build3109\build\portable-net45+netcore45+wp8+wpa81\xunit.core.props" Condition="Exists('..\packages\xunit.core.2.1.0-beta4-build3109\build\portable-net45+netcore45+wp8+wpa81\xunit.core.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -45,12 +44,16 @@
<HintPath>..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.assert, Version=2.1.0.3109, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.assert.2.1.0-beta4-build3109\lib\dotnet\xunit.assert.dll</HintPath>
<Reference Include="xunit.assert, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.core, Version=2.1.0.3109, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.core.2.1.0-beta4-build3109\lib\dotnet\xunit.core.dll</HintPath>
<Reference Include="xunit.core, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.execution.desktop, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
@@ -62,12 +65,6 @@
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\xunit.core.2.1.0-beta4-build3109\build\portable-net45+netcore45+wp8+wpa81\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.1.0-beta4-build3109\build\portable-net45+netcore45+wp8+wpa81\xunit.core.props'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
+5 -4
View File
@@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="xunit" version="2.1.0-beta4-build3109" targetFramework="net452" />
<package id="xunit" version="2.1.0" targetFramework="net452" />
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
<package id="xunit.assert" version="2.1.0-beta4-build3109" targetFramework="net452" />
<package id="xunit.core" version="2.1.0-beta4-build3109" targetFramework="net452" />
<package id="xunit.extensibility.core" version="2.1.0-beta4-build3109" targetFramework="net452" />
<package id="xunit.assert" version="2.1.0" targetFramework="net452" />
<package id="xunit.core" version="2.1.0" targetFramework="net452" />
<package id="xunit.extensibility.core" version="2.1.0" targetFramework="net452" />
<package id="xunit.extensibility.execution" version="2.1.0" targetFramework="net452" />
</packages>
+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

+2 -6
View File
@@ -1,17 +1,13 @@
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
{
private readonly BinaryReader _reader;
private bool _closed;
private Exception _exception;
private Exception? _exception;
public bool IsConnected => !_closed;
+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,5 +1,4 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
+41 -17
View File
@@ -1,45 +1,69 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
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, Dictionary<string, List<string>> traitMap)
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 formatter = new BinaryFormatter();
var serializedForm = reader.ReadString();
var displayName = reader.ReadString();
var uniqueID = reader.ReadString();
var skipReason = reader.ReadString();
var assemblyPath = reader.ReadString();
var traitMap = (Dictionary<string, List<string>>)formatter.Deserialize(reader.BaseStream);
return new TestCaseData(serializedForm, displayName, assemblyPath, traitMap);
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)
{
var formatter = new BinaryFormatter();
writer.Write(SerializedForm);
writer.Write(DisplayName);
writer.Write(UniqueID);
writer.Write(SkipReason ?? string.Empty);
writer.Write(AssemblyPath);
formatter.Serialize(writer.BaseStream, TraitMap);
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);
}
+1 -1
View File
@@ -7,7 +7,7 @@
<ProjectGuid>{A1F579F4-443E-4F64-BC55-998AB86FF293}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>xunit.runner.data</RootNamespace>
<RootNamespace>Xunit.Runner.Data</RootNamespace>
<AssemblyName>xunit.runner.data</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
+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>
+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()
+26 -35
View File
@@ -1,62 +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;
private readonly Dictionary<string, List<string>> _traitMap;
internal Impl(ITestFrameworkDiscoverer discoverer, ClientWriter writer)
internal TestDiscoverySink(ClientWriter writer)
{
_discoverer = discoverer;
_writer = writer;
_traitMap = new Dictionary<string, List<string>>(StringComparer.Ordinal);
}
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,
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,52 @@
using System;
using Xunit.Abstractions;
namespace Xunit.Runner.Worker.MessageSinks
{
/// <summary>
/// An Xunit <see cref="IMessageSink"/> implementation without the dispatch overhead of <see cref="TestMessageVisitor"/>
/// and <see cref="TestMessageVisitor{TCompleteMessage}"/>.
/// </summary>
internal abstract class BaseMessageSink : LongLivedMarshalByRefObject, IMessageSink, IDisposable
{
private bool _disposed;
protected BaseMessageSink()
{
}
~BaseMessageSink()
{
Dispose(false);
}
protected virtual void DisposeCore(bool disposing)
{
}
private void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
DisposeCore(disposing);
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(true);
}
protected abstract bool OnMessage(IMessageSinkMessage message);
bool IMessageSink.OnMessage(IMessageSinkMessage message)
{
return OnMessage(message);
}
}
}
@@ -0,0 +1,40 @@
using System.Threading;
using Xunit.Abstractions;
namespace Xunit.Runner.Worker.MessageSinks
{
internal abstract class BaseTestDiscoverySink : BaseMessageSink
{
public ManualResetEvent Finished { get; }
protected BaseTestDiscoverySink()
{
Finished = new ManualResetEvent(false);
}
protected override void DisposeCore(bool disposing)
{
Finished.Dispose();
}
protected override bool OnMessage(IMessageSinkMessage message)
{
var discoveryMessage = message as ITestCaseDiscoveryMessage;
if (discoveryMessage != null)
{
OnTestDiscovered(discoveryMessage);
}
if (message is IDiscoveryCompleteMessage)
{
Finished.Set();
}
return ShouldContinue;
}
protected virtual bool ShouldContinue => true;
protected abstract void OnTestDiscovered(ITestCaseDiscoveryMessage testCaseDiscovered);
}
}
@@ -0,0 +1,61 @@
using System.Threading;
using Xunit.Abstractions;
namespace Xunit.Runner.Worker.MessageSinks
{
internal abstract class BaseTestRunSink : BaseMessageSink
{
public ManualResetEvent Finished { get; }
protected BaseTestRunSink()
{
Finished = new ManualResetEvent(false);
}
protected override void DisposeCore(bool disposing)
{
Finished.Dispose();
}
protected override bool OnMessage(IMessageSinkMessage message)
{
var testStarted = message as ITestStarting;
if (testStarted != null)
{
OnTestStarted(testStarted);
}
var testFailed = message as ITestFailed;
if (testFailed != null)
{
OnTestFailed(testFailed);
}
var testPassed = message as ITestPassed;
if (testPassed != null)
{
OnTestPassed(testPassed);
}
var testSkipped = message as ITestSkipped;
if (testSkipped != null)
{
OnTestSkipped(testSkipped);
}
if (message is ITestAssemblyFinished)
{
Finished.Set();
}
return ShouldContinue;
}
protected virtual bool ShouldContinue => true;
protected abstract void OnTestStarted(ITestStarting testStarted);
protected abstract void OnTestFailed(ITestFailed testFailed);
protected abstract void OnTestPassed(ITestPassed testPassed);
protected abstract void OnTestSkipped(ITestSkipped testSkipped);
}
}
@@ -0,0 +1,12 @@
using Xunit.Abstractions;
namespace Xunit.Runner.Worker.MessageSinks
{
internal class DiagnosticSink : BaseMessageSink
{
protected override bool OnMessage(IMessageSinkMessage message)
{
return true;
}
}
}
-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,5 +1,4 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
+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.SubscribeResolve())
using (var xunit = new XunitFrontController(appDomainSupport, assemblyFileName, shadowCopy: false))
using (var writer = new ClientWriter(stream))
{
var configuration = ConfigReader.Load(assemblyFileName);
action(xunit, configuration, writer);
}
}
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
<package id="xunit.runner.utility" version="2.1.0-beta4-build3109" targetFramework="net452" />
<package id="xunit.runner.utility" version="2.1.0" targetFramework="net46" />
</packages>
+14 -5
View File
@@ -7,11 +7,14 @@
<ProjectGuid>{9DF97A2B-0EB5-4B12-9F81-69DFAC979814}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>xunit.runner.worker</RootNamespace>
<RootNamespace>Xunit.Runner.Worker</RootNamespace>
<AssemblyName>xunit.runner.worker</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -44,17 +47,23 @@
<Reference Include="xunit.abstractions">
<HintPath>..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
</Reference>
<Reference Include="xunit.runner.utility.desktop">
<HintPath>..\packages\xunit.runner.utility.2.1.0-beta4-build3109\lib\net35\xunit.runner.utility.desktop.dll</HintPath>
<Reference Include="xunit.runner.utility.desktop, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.runner.utility.2.1.0\lib\net35\xunit.runner.utility.desktop.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Connection.cs" />
<Compile Include="DiscoverUtil.cs" />
<Compile Include="MessageVisitor.cs" />
<Compile Include="Listener.cs" />
<Compile Include="MessageSinks\BaseMessageSink.cs" />
<Compile Include="MessageSinks\BaseTestRunSink.cs" />
<Compile Include="MessageSinks\DiagnosticSink.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RunUtil.cs" />
<Compile Include="MessageSinks\BaseTestDiscoverySink.cs" />
<Compile Include="XunitUtil.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
+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
+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>
+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

+5 -12
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
{
@@ -21,16 +16,14 @@ namespace xunit.runner.wpf
}
}
public static CommandBindingCollection GetRegistration(UIElement element)
public static CommandBindingCollection? GetRegistration(UIElement element)
=> (element != null ? (CommandBindingCollection)element.GetValue(Registration) : null);
private static void OnRegistrationChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
UIElement element = sender as UIElement;
if (element != null)
if (sender is UIElement element)
{
CommandBindingCollection bindings = e.NewValue as CommandBindingCollection;
if (bindings != null)
if (e.NewValue is CommandBindingCollection bindings)
{
element.CommandBindings.AddRange(bindings);
}
@@ -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)
@@ -33,19 +33,21 @@ namespace xunit.runner.wpf.Converters
return image;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var state = (TestState)value;
if (targetType == typeof(Brush))
{
switch (state)
{
case TestState.Running:
return Brushes.Blue;
case TestState.Failed:
return Brushes.Red;
case TestState.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);
}
}
}
+25 -17
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)
{
@@ -281,7 +283,13 @@ namespace xunit.runner.wpf
object IList.this[int index]
{
get { return this[index]; }
get
{
// Can't figure out how to not get a warning here.
#pragma warning disable CS8603
return this[index];
#pragma warning restore
}
set { throw new NotSupportedException(); }
}
+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 -10
View File
@@ -1,32 +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>
Task Discover(string assemblyPath, Action<TestCaseData> callback, 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>
Task RunAll(string assemblyPath, Action<TestResultData> callback, 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>
Task RunSpecific(string assemblyPath, ImmutableArray<string> testCaseDisplayNames, Action<TestResultData> callback, CancellationToken cancellationToken = default(CancellationToken));
Task RunSpecific(
string assemblyFileName,
ImmutableArray<string> testCasesToRun,
Action<IEnumerable<TestResultData>> testsFinished,
CancellationToken cancellationToken = default(CancellationToken));
}
}
@@ -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 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
{
@@ -114,9 +111,9 @@ namespace xunit.runner.wpf.Impl
private const int MaxResultPerTick = 1000;
private readonly Connection _connection;
private readonly ConcurrentQueue<T> _queue;
private readonly ConcurrentQueue<T?> _queue;
private readonly DispatcherTimer _timer;
private readonly Action<T> _callback;
private readonly Action<List<T>> _callback;
private readonly int _maxPerTick;
private readonly TaskCompletionSource<bool> _taskCompletionSource;
@@ -125,8 +122,8 @@ namespace xunit.runner.wpf.Impl
internal BackgroundProducer(
Connection connection,
Dispatcher dispatcher,
ConcurrentQueue<T> queue,
Action<T> callback,
ConcurrentQueue<T?> queue,
Action<List<T>> callback,
int maxResultPerTick = MaxResultPerTick,
TimeSpan? interval = null)
{
@@ -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);
@@ -147,8 +144,7 @@ namespace xunit.runner.wpf.Impl
var i = 0;
var list = new List<T>();
var isDone = false;
T value;
while (i < _maxPerTick && _queue.TryDequeue(out value))
while (i < _maxPerTick && _queue.TryDequeue(out T? value))
{
if (value == null)
{
@@ -159,10 +155,7 @@ namespace xunit.runner.wpf.Impl
list.Add(value);
}
foreach (var cur in list)
{
_callback(cur);
}
_callback(list);
if (isDone)
{
@@ -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 NamedPipeClientStream? _stream;
private ClientReader _reader;
internal NamedPipeClientStream Stream => _stream;
internal NamedPipeClientStream Stream => _stream ?? throw new ObjectDisposedException(nameof(Connection));
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()
+100 -67
View File
@@ -3,116 +3,149 @@ 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 Task Discover(string assemblyPath, Action<TestCaseData> callback, CancellationToken cancellationToken)
private string GetPipeName()
{
var connection = StartWorkerProcess(Constants.ActionDiscover, assemblyPath);
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, callback);
return backgroundProducer.Task;
}
private Task RunCore(string actionName, string assemblyPath, ImmutableArray<string> testCaseDisplayNames, Action<TestResultData> callback, CancellationToken cancellationToken)
{
var connection = StartWorkerProcess(Constants.ActionRunAll, assemblyPath);
var queue = CreateRunQueue(connection, testCaseDisplayNames, cancellationToken);
var backgroundProducer = new BackgroundProducer<TestResultData>(connection, _dispatcher, queue, callback);
return 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> CreateRunQueue(Connection connection, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken)
{
var queue = new ConcurrentQueue<TestResultData>();
var unused = CreateRunQueueCore(queue, connection, testCaseDisplayNames, cancellationToken);
return queue;
}
private static async Task CreateRunQueueCore(ConcurrentQueue<TestResultData> queue, Connection connection, ImmutableArray<string> testCaseDisplayNames, CancellationToken cancellationToken)
{
try
var process = _processInfo?.Process;
if (process != null && !process.HasExited)
{
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();
return _processInfo.Value.PipeName;
}
catch (Exception ex)
_processInfo = StartWorkerProcess();
return _processInfo.Value.PipeName;
}
private static ProcessInfo StartWorkerProcess()
{
var pipeName = $"xunit.runner.wpf.pipe.{Guid.NewGuid()}";
var processStartInfo = new ProcessStartInfo();
processStartInfo.FileName = typeof(Xunit.Runner.Worker.Program).Assembly.Location;
processStartInfo.Arguments = $"{pipeName} {Process.GetCurrentProcess().Id}";
processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
var process = Process.Start(processStartInfo);
return new ProcessInfo(pipeName, process);
}
private void RecycleProcess()
{
var process = _processInfo?.Process;
if (process != null && !process.HasExited)
{
Debug.Fail(ex.Message);
// Signal data completed
queue.Enqueue(null);
process.Kill();
}
_processInfo = StartWorkerProcess();
}
private async Task Discover(string assemblyPath, Action<List<TestCaseData>> callback, CancellationToken cancellationToken)
{
var connection = await CreateConnection(Constants.ActionDiscover, assemblyPath, cancellationToken);
await ProcessResultsCore(connection, r => r.ReadTestCaseData(), callback, cancellationToken);
RecycleProcess();
}
private async Task RunCore(string actionName, string assemblyPath, ImmutableArray<string> testCaseDisplayNames, Action<List<TestResultData>> callback, CancellationToken cancellationToken)
{
var connection = await CreateConnection(actionName, assemblyPath, cancellationToken);
if (!testCaseDisplayNames.IsDefaultOrEmpty)
{
var backgroundWriter = new BackgroundWriter<string>(new ClientWriter(connection.Stream), testCaseDisplayNames, (w, s) => w.Write(s), cancellationToken);
await backgroundWriter.WriteAsync();
}
await ProcessResultsCore(connection, r => r.ReadTestResultData(), callback, cancellationToken);
}
private async Task ProcessResultsCore<T>(Connection connection, Func<ClientReader, T> readValue, Action<List<T>> callback, CancellationToken cancellationToken)
where T : class
{
var queue = new ConcurrentQueue<T?>();
var backgroundReader = new BackgroundReader<T>(queue, new ClientReader(connection.Stream), readValue);
var backgroundProducer = new BackgroundProducer<T>(connection, _dispatcher, queue, callback);
await backgroundReader.ReadAsync(cancellationToken);
await backgroundProducer.Task;
}
#region ITestUtil
Task ITestUtil.Discover(string assemblyPath, Action<TestCaseData> callback, CancellationToken cancellationToken)
Task ITestUtil.Discover(string assemblyFileName, Action<IEnumerable<TestCaseData>> testsDiscovered, CancellationToken cancellationToken)
{
return Discover(assemblyPath, callback, cancellationToken);
return Discover(assemblyFileName, testsDiscovered, cancellationToken);
}
Task ITestUtil.RunAll(string assemblyPath, Action<TestResultData> callback, CancellationToken cancellationToken)
Task ITestUtil.RunAll(string assemblyFileName, Action<IEnumerable<TestResultData>> testsFinished, CancellationToken cancellationToken)
{
return RunCore(Constants.ActionRunAll, assemblyPath, ImmutableArray<string>.Empty, callback, cancellationToken);
return RunCore(Constants.ActionRunAll, assemblyFileName, ImmutableArray<string>.Empty, testsFinished, cancellationToken);
}
Task ITestUtil.RunSpecific(string assemblyPath, ImmutableArray<string> testCaseDisplayNames, Action<TestResultData> callback, CancellationToken cancellationToken)
Task ITestUtil.RunSpecific(string assemblyFileName, ImmutableArray<string> testCases, Action<IEnumerable<TestResultData>> testsFinished, CancellationToken cancellationToken)
{
return RunCore(Constants.ActionRunSpecific, assemblyPath, testCaseDisplayNames, callback, 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));
}
}
+302 -139
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,137 +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}"/>
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Reload" Command="{Binding AssemblyReloadCommand}" />
<MenuItem Header="Reload All" Command="{Binding AssemblyReloadAllCommand}" />
<MenuItem Command="{Binding AssemblyReloadCommand}" Header="Reload" />
<MenuItem Command="{Binding AssemblyReloadAllCommand}" Header="Reload All" />
<Separator />
<MenuItem Header="Remove" Command="{Binding AssemblyRemoveCommand}" />
<MenuItem Header="Remove All" Command="{Binding AssemblyRemoveAllCommand}" />
<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"
ItemsSource="{Binding Traits}">
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:TraitViewModel">
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
<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>
</ListBox.ItemContainerStyle>
<ListBox.ContextMenu>
</TreeView.ItemContainerStyle>
<TreeView.ContextMenu>
<ContextMenu>
<MenuItem Header="Clear" Command="{Binding TraitsClearCommand}" />
<MenuItem Command="{Binding TraitsClearCommand}" Header="Clear" />
</ContextMenu>
</ListBox.ContextMenu>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding TraitSelectionChangedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
</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>
@@ -219,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>
+44 -17
View File
@@ -1,32 +1,59 @@
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 ViewModel;
public partial class MainWindow : Window
{
public static Window Instance { get; private set; }
// WPF generates fields that are marked as non-nullable, but not definitely initialized.
#pragma warning disable CS8618
public MainWindow()
{
Instance = this;
InitializeComponent();
}
#pragma warning restore
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);
}
}
}
}
+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,13 +1,13 @@
using System.IO;
namespace xunit.runner.wpf.ViewModel
namespace Xunit.Runner.Wpf.ViewModel
{
public class AssemblyAndConfigFile
{
public string AssemblyFileName { get; }
public string ConfigFileName { get; }
public string? ConfigFileName { get; }
public AssemblyAndConfigFile(string assemblyFileName, string configFileName)
public AssemblyAndConfigFile(string assemblyFileName, string? configFileName)
{
this.AssemblyFileName = Path.GetFullPath(assemblyFileName);
if (configFileName != null)
+449 -126
View File
@@ -1,65 +1,137 @@
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.CommandWpf;
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 CancellationTokenSource? cancellationTokenSource;
private bool isBusy;
private SearchQuery searchQuery = new SearchQuery();
private bool autoReloadAssemblies;
public ObservableCollection<TestAssemblyViewModel> Assemblies { get; } = new ObservableCollection<TestAssemblyViewModel>();
public FilteredCollectionView<TestCaseViewModel, SearchQuery> FilteredTestCases { get; }
public ObservableCollection<TraitViewModel> Traits => this.traitCollectionView.Collection;
public bool AutoReloadAssemblies
{
get => autoReloadAssemblies;
set
{
var oldVal = autoReloadAssemblies;
autoReloadAssemblies = value;
RaisePropertyChanged(nameof(AutoReloadAssemblies), oldVal, autoReloadAssemblies);
}
}
public ObservableCollection<RecentAssemblyViewModel> RecentAssemblies { get; } = new ObservableCollection<RecentAssemblyViewModel>();
private ImmutableList<TestCaseViewModel>? testsToRun;
public ICommand ExitCommand { get; }
public ICommand WindowLoadedCommand { get; }
public RelayCommand<CancelEventArgs> WindowClosingCommand { get; }
public RelayCommand RunAllCommand { get; }
public RelayCommand RunSelectedCommand { get; }
public RelayCommand CancelCommand { get; }
public ICommand TraitCheckedChangedCommand { get; }
public ICommand TraitsClearCommand { get; }
public ICommand AssemblyReloadCommand { get; }
public ICommand AssemblyReloadAllCommand { get; }
public ICommand AssemblyRemoveCommand { get; }
public ICommand AssemblyRemoveAllCommand { get; }
public ICommand AutoReloadAssembliesCommand { get; }
public CommandBindingCollection CommandBindings { get; }
public MainViewModel()
{
this.settings = Settings.Load();
if (IsInDesignMode)
{
this.Assemblies.Add(new TestAssemblyViewModel(new AssemblyAndConfigFile(@"C:\Code\TestAssembly.dll", null)));
}
CommandBindings = CreateCommandBindings();
this.testUtil = new xunit.runner.wpf.Impl.RemoteTestUtil(Dispatcher.CurrentDispatcher);
this.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.TraitSelectionChangedCommand = new RelayCommand(OnExecuteTraitSelectionChanged);
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;
}
@@ -82,19 +154,24 @@ namespace xunit.runner.wpf.ViewModel
}
}
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?");
@@ -104,40 +181,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 ICommand TraitSelectionChangedCommand { get; }
public ICommand TraitsClearCommand { get; }
public ICommand AssemblyReloadCommand { get; }
public ICommand AssemblyReloadAllCommand { get; }
public ICommand AssemblyRemoveCommand { get; }
public ICommand AssemblyRemoveAllCommand { get; }
private void ClearSelectionFlags()
{
foreach (var test in this.allTestCases)
{
test.IsSelected = false;
}
}
public CommandBindingCollection CommandBindings { get; }
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 methodsCaption;
public string MethodsCaption
private string? testCasesCaption;
public string? TestCasesCaption
{
get { return methodsCaption; }
private set { Set(ref methodsCaption, value); }
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;
@@ -165,14 +299,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;
@@ -205,7 +349,7 @@ namespace xunit.runner.wpf.ViewModel
.ContinueWith(
x =>
{
TestCases.FilterArgument = searchQuery;
FilteredTestCases.FilterArgument = searchQuery;
},
token,
TaskContinuationOptions.None,
@@ -223,15 +367,12 @@ namespace xunit.runner.wpf.ViewModel
};
}
public ObservableCollection<TestAssemblyViewModel> Assemblies { get; } = new ObservableCollection<TestAssemblyViewModel>();
public FilteredCollectionView<TestCaseViewModel, SearchQuery> TestCases { get; }
public ObservableCollection<TraitViewModel> Traits => this.traitCollectionView.Collection;
private async void OnExecuteOpen(object sender, ExecutedRoutedEventArgs e)
{
var fileDialog = new OpenFileDialog
{
Filter = "Unit Test Assemblies|*.dll",
Multiselect = true
};
if (fileDialog.ShowDialog(Application.Current.MainWindow) != true)
@@ -239,8 +380,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)
@@ -250,17 +391,24 @@ 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 taskList = new List<Task>();
foreach (var assembly in assemblies)
{
var assemblyPath = assembly.AssemblyFileName;
taskList.Add(this.testUtil.Discover(assemblyPath, OnTestDiscovered, cancellationTokenSource.Token));
Assemblies.Add(new TestAssemblyViewModel(assembly));
taskList.Add(this.testUtil.Discover(assembly.AssemblyFileName, this.OnTestsDiscovered, CancellationToken));
var assemblyViewModel = new TestAssemblyViewModel(assembly);
newAssemblyViewModels.Add(assemblyViewModel);
this.Assemblies.Add(assemblyViewModel);
this.settings.AddRecentAssembly(assembly.AssemblyFileName);
assemblyViewModel.State = AssemblyState.Loading;
}
return taskList;
@@ -268,24 +416,49 @@ namespace xunit.runner.wpf.ViewModel
}
finally
{
loadingDialog.Close();
foreach (var assemblyViewModel in newAssemblyViewModels)
{
assemblyViewModel.State = AssemblyState.Ready;
assemblyWatcher.AddAssembly(assemblyViewModel.FileName);
}
RebuildRecentAssembliesMenu();
}
}
private CancellationToken CancellationToken
=> cancellationTokenSource == null
? CancellationToken.None
: cancellationTokenSource.Token;
public bool ReloadAssemblies(IEnumerable<string> assemblies)
{
if (IsBusy)
{
return false;
}
var testAssemblies = Assemblies.Where(assembly => assemblies.Contains(assembly.FileName));
Application.Current.Dispatcher.InvokeAsync(() => ReloadAssemblies(testAssemblies));
return true;
}
private async Task ReloadAssemblies(IEnumerable<TestAssemblyViewModel> assemblies)
{
var loadingDialog = new LoadingDialog { Owner = MainWindow.Instance };
try
{
await ExecuteTestSessionOperation(() =>
{
var taskList = new List<Task>();
foreach (var assembly in assemblies)
foreach (var assemblyViewModel in assemblies)
{
var assemblyPath = assembly.FileName;
RemoveAssemblyTestCases(assemblyPath);
assemblyViewModel.State = AssemblyState.Loading;
taskList.Add(this.testUtil.Discover(assemblyPath, OnTestDiscovered, cancellationTokenSource.Token));
var assemblyFileName = assemblyViewModel.FileName;
RemoveAssemblyTestCases(assemblyFileName);
taskList.Add(this.testUtil.Discover(assemblyFileName, OnTestsDiscovered, CancellationToken));
}
return taskList;
@@ -295,7 +468,10 @@ namespace xunit.runner.wpf.ViewModel
}
finally
{
loadingDialog.Close();
foreach (var assemblyViewModel in assemblies)
{
assemblyViewModel.State = AssemblyState.Ready;
}
}
}
@@ -303,6 +479,7 @@ namespace xunit.runner.wpf.ViewModel
{
foreach (var assembly in assemblies.ToList())
{
assemblyWatcher.RemoveAssembly(assembly.FileName);
RemoveAssemblyTestCases(assembly.FileName);
Assemblies.Remove(assembly);
}
@@ -315,8 +492,9 @@ namespace xunit.runner.wpf.ViewModel
var i = 0;
while (i < this.allTestCases.Count)
{
if (this.allTestCases[i].AssemblyFileName == assemblyPath)
if (string.Compare(this.allTestCases[i].AssemblyFileName, assemblyPath, StringComparison.OrdinalIgnoreCase) == 0)
{
this.allTestCaseUniqueIDs.Remove(this.allTestCases[i].UniqueID);
this.allTestCases.RemoveAt(i);
}
else
@@ -327,17 +505,17 @@ namespace xunit.runner.wpf.ViewModel
}
/// <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
/// 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
/// way to
/// </summary>
private void RebuildTraits()
{
this.traitCollectionView.Collection.Clear();
foreach (var testCase in this.allTestCases)
{
this.traitCollectionView.Add(testCase.Traits);
this.traitCollectionView.AddRange(testCase.Traits);
}
}
@@ -347,7 +525,8 @@ namespace xunit.runner.wpf.ViewModel
set
{
isBusy = value;
RunCommand.RaiseCanExecuteChanged();
RunAllCommand.RaiseCanExecuteChanged();
RunSelectedCommand.RaiseCanExecuteChanged();
CancelCommand.RaiseCanExecuteChanged();
}
}
@@ -362,6 +541,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())
@@ -369,7 +553,7 @@ namespace xunit.runner.wpf.ViewModel
var assemblyFileName = enumerable.First();
enumerable = enumerable.Skip(1);
var configFileName = (string)null;
var configFileName = (string?)null;
if (IsConfigFile(enumerable.FirstOrDefault()))
{
configFileName = enumerable.First();
@@ -384,48 +568,87 @@ 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<Task> 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;
}
var runAll = TestCases.Count == this.allTestCases.Count;
var runAll = this.testsToRun.Count == this.allTestCases.Count;
var testSessionList = new List<Task>();
foreach (var assemblyPath in TestCases.Select(x => x.AssemblyFileName).Distinct())
foreach (var assemblyFileName in this.testsToRun.Select(x => x.AssemblyFileName).Distinct())
{
Task task;
if (runAll)
{
task = this.testUtil.RunAll(assemblyPath, OnTestFinished, this.cancellationTokenSource.Token);
task = this.testUtil.RunAll(assemblyFileName, OnTestStateChange, CancellationToken);
}
else
{
var testCaseDisplayNames = TestCases
.Where(x => x.AssemblyFileName == assemblyPath)
.Select(x => x.DisplayName)
.ToImmutableArray();
task = this.testUtil.RunSpecific(assemblyPath, testCaseDisplayNames, OnTestFinished, 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, CancellationToken);
}
testSessionList.Add(task);
@@ -459,38 +682,95 @@ namespace xunit.runner.wpf.ViewModel
}
}
private void OnTestDiscovered(TestCaseData testCaseData)
private void OnTestsDiscovered(IEnumerable<TestCaseData> testCases)
{
var traitList = testCaseData.TraitMap.Count == 0
? ImmutableArray<TraitViewModel>.Empty
: testCaseData.TraitMap.SelectMany(pair => pair.Value.Select(value => new TraitViewModel(pair.Key, value))).ToImmutableArray();
this.allTestCases.Add(new TestCaseViewModel(testCaseData.SerializedForm, testCaseData.DisplayName, testCaseData.AssemblyPath, traitList));
this.traitCollectionView.Add(traitList);
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(TestResultData testResultData)
private void OnTestStateChange(IEnumerable<TestResultData> testResultData)
{
var testCase = TestCases.Single(x => x.DisplayName == testResultData.TestCaseDisplayName);
testCase.State = testResultData.TestState;
Debug.Assert(this.testsToRun != null);
TestsCompleted++;
switch (testResultData.TestState)
foreach (var result in testResultData)
{
case TestState.Passed:
TestsPassed++;
break;
case TestState.Failed:
TestsFailed++;
Output = Output + testResultData.Output;
break;
case TestState.Skipped:
TestsSkipped++;
break;
}
var testCase = this.testsToRun.Single(x => x.UniqueID == result.TestCaseUniqueID);
testCase.State = result.TestState;
if (testResultData.TestState > CurrentRunState)
{
CurrentRunState = testResultData.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;
}
}
}
@@ -502,14 +782,12 @@ namespace xunit.runner.wpf.ViewModel
private void OnExecuteCancel()
{
Debug.Assert(CanExecuteCancel());
this.cancellationTokenSource.Cancel();
cancellationTokenSource?.Cancel();
}
private void OnExecuteTraitSelectionChanged()
private void OnExecuteTraitCheckedChanged(TraitViewModel trait)
{
this.searchQuery.TraitSet = new HashSet<TraitViewModel>(
this.traitCollectionView.Collection.Where(x => x.IsSelected),
TraitViewModelComparer.Instance);
this.searchQuery.TraitSet = this.traitCollectionView.GetCheckedTraits();
FilterAfterDelay();
}
@@ -517,7 +795,7 @@ namespace xunit.runner.wpf.ViewModel
{
foreach (var cur in this.traitCollectionView.Collection)
{
cur.IsSelected = false;
cur.IsChecked = false;
}
}
@@ -550,37 +828,72 @@ namespace xunit.runner.wpf.ViewModel
{
RemoveAssemblies(Assemblies.ToArray());
}
public bool IncludePassedTests
private void OnToggleAutoReloadAssemblies()
{
get { return searchQuery.IncludePassedTests; }
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();
}
@@ -593,7 +906,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 HashSet<TraitViewModel> TraitSet = new HashSet<TraitViewModel>(TraitViewModelComparer.Instance);
public ISet<TraitViewModel> TraitSet = new HashSet<TraitViewModel>(TraitViewModel.EqualityComparer);
}
}
@@ -1,17 +1,13 @@
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 bool _isSelected;
private AssemblyState _state;
public TestAssemblyViewModel(AssemblyAndConfigFile assembly)
{
@@ -27,5 +23,17 @@ namespace xunit.runner.wpf.ViewModel
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
}
}
+23 -21
View File
@@ -1,28 +1,22 @@
using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using xunit.runner.data;
using GalaSoft.MvvmLight;
using Xunit.Runner.Data;
namespace xunit.runner.wpf.ViewModel
namespace Xunit.Runner.Wpf.ViewModel
{
public class TestCaseViewModel : ViewModelBase
{
private TestState _state = TestState.NotRun;
public TestCaseViewModel(string testCase, string displayName, string assemblyFileName, ImmutableArray<TraitViewModel> traits)
{
this.TestCase = testCase;
this.DisplayName = displayName;
this.AssemblyFileName = assemblyFileName;
this.Traits = traits;
}
public string DisplayName { get; }
public string UniqueID { get; }
public string SkipReason { get; }
public string AssemblyFileName { get; }
public ImmutableArray<TraitViewModel> Traits { get; }
public bool IsSelected { get; set; }
public bool HasSkipReason => !string.IsNullOrEmpty(this.SkipReason);
public TestState State
{
@@ -30,10 +24,18 @@ namespace xunit.runner.wpf.ViewModel
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; }
public ImmutableArray<TraitViewModel> Traits { get; }
if (!string.IsNullOrEmpty(skipReason))
{
_state = TestState.Skipped;
}
}
}
}
@@ -1,59 +1,51 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace xunit.runner.wpf.ViewModel
namespace Xunit.Runner.Wpf.ViewModel
{
public sealed partial class TraitCollectionView
{
private readonly TraitViewModelComparer _comparer = TraitViewModelComparer.Instance;
private readonly ObservableCollection<TraitViewModel> _collection = new ObservableCollection<TraitViewModel>();
public ObservableCollection<TraitViewModel> Collection { get; } = new ObservableCollection<TraitViewModel>();
public ObservableCollection<TraitViewModel> Collection => _collection;
public TraitCollectionView()
public void AddRange(IEnumerable<TraitViewModel> traits)
{
}
public void Add(ImmutableArray<TraitViewModel> traitList)
{
if (traitList.IsDefaultOrEmpty)
foreach (var trait in traits)
{
return;
}
foreach (var traitViewModel in traitList)
{
InsertIfNotPresent(traitViewModel);
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));
}
}
}
private void InsertIfNotPresent(TraitViewModel trait)
public TraitViewModel GetOrAdd(string text)
{
// TODO: make it a binary search
for (int i = 0; i < _collection.Count; i++)
{
var current = _collection[i];
var result = _comparer.Compare(trait, current);
if (result < 0)
{
_collection.Insert(i, trait);
return;
}
var index = this.Collection.BinarySearch(text, StringComparer.Ordinal, vm => vm.Text);
if (result == 0)
{
return;
}
if (index < 0)
{
var viewModel = new TraitViewModel(text);
this.Collection.Insert(~index, viewModel);
return viewModel;
}
_collection.Add(trait);
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();
}
}
}
+109 -18
View File
@@ -1,31 +1,122 @@
using GalaSoft.MvvmLight;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using GalaSoft.MvvmLight;
namespace xunit.runner.wpf.ViewModel
namespace Xunit.Runner.Wpf.ViewModel
{
public sealed class TraitViewModel : ViewModelBase
public partial class TraitViewModel : ViewModelBase
{
private bool _isSelected;
private readonly TraitViewModel? _parent;
private bool? _isChecked;
private bool _isExpanded;
private string _text;
public string Name { get; }
public string Value { get; }
public string DisplayName { get; }
public ObservableCollection<TraitViewModel> Children { get; }
public bool IsSelected
public TraitViewModel(string text)
: this(null, text)
{
get { return _isSelected; }
set { Set(ref _isSelected, value); }
}
public TraitViewModel(string name, string value)
private TraitViewModel(TraitViewModel? parent, string text)
{
Name = name;
Value = value;
DisplayName = $"{Name}={Value}";
this._parent = parent;
this._isChecked = false;
this._isExpanded = true;
this._text = text;
this.Children = new ObservableCollection<TraitViewModel>();
}
private void SetIsChecked(bool? value, bool updateChildren, bool updateParent)
{
if (value == this._isChecked)
{
return;
}
this._isChecked = value;
if (updateChildren && value != null)
{
foreach (var child in this.Children)
{
child.SetIsChecked(value, updateChildren: true, updateParent: false);
}
}
if (updateParent && _parent != null)
{
_parent.VerifyCheckState();
}
this.RaisePropertyChanged(nameof(IsChecked));
}
private void VerifyCheckState()
{
bool? state = null;
var isFirst = true;
foreach (var child in this.Children)
{
if (isFirst)
{
state = child.IsChecked;
isFirst = false;
}
else if (state != child.IsChecked)
{
state = null;
break;
}
}
this.SetIsChecked(state, updateChildren: false, updateParent: true);
}
public void AddValues(IEnumerable<string> values)
{
foreach (var value in values)
{
var index = this.Children.BinarySearch(value, StringComparer.Ordinal.Compare, v => v.Text);
if (index < 0)
{
this.Children.Insert(~index, new TraitViewModel(this, value));
}
}
}
public TraitViewModel GetOrAdd(string text)
{
var index = this.Children.BinarySearch(text, StringComparer.Ordinal, vm => vm.Text);
if (index < 0)
{
var viewModel = new TraitViewModel(this, text);
this.Children.Insert(~index, viewModel);
return viewModel;
}
return this.Children[index];
}
public bool? IsChecked
{
get { return _isChecked; }
set { SetIsChecked(value, updateChildren: true, updateParent: true); }
}
public bool IsExpanded
{
get { return _isExpanded; }
set { Set(ref _isExpanded, value); }
}
public string Text
{
get { return _text; }
set { Set(ref _text, value); }
}
}
}
@@ -1,38 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace xunit.runner.wpf.ViewModel
{
internal sealed class TraitViewModelComparer : IEqualityComparer<TraitViewModel>, IComparer<TraitViewModel>
{
internal static readonly TraitViewModelComparer Instance = new TraitViewModelComparer();
private readonly StringComparer _comparer = StringComparer.Ordinal;
public int Compare(TraitViewModel x, TraitViewModel y)
{
var result = _comparer.Compare(x.Name, y.Name);
if (result != 0)
{
return result;
}
return _comparer.Compare(x.Value, y.Value);
}
public bool Equals(TraitViewModel x, TraitViewModel y)
{
return _comparer.Equals(x.Name, y.Name)
&& _comparer.Equals(x.Value, y.Value);
}
public int GetHashCode(TraitViewModel obj)
{
return obj.Name.GetHashCode();
}
}
}
@@ -12,11 +12,10 @@
See http://www.galasoft.ch/mvvm
*/
using GalaSoft.MvvmLight;
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
+16 -4
View File
@@ -1,9 +1,21 @@
<?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="MvvmLight" version="5.3.0.0" targetFramework="net46" />
<package id="MvvmLightLibs" version="5.3.0.0" targetFramework="net46" />
<package id="NuGet.CommandLine" version="2.8.3" targetFramework="net46" />
<package id="System.Collections" version="4.3.0" targetFramework="net46" />
<package id="System.Collections.Immutable" version="1.4.0-preview1-25305-02" targetFramework="net46" />
<package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net46" />
<package id="System.Globalization" version="4.3.0" targetFramework="net46" />
<package id="System.Linq" version="4.3.0" targetFramework="net46" />
<package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net46" />
<package id="System.Runtime" version="4.3.0" targetFramework="net46" />
<package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net46" />
<package id="System.Threading" version="4.3.0" targetFramework="net46" />
<package id="Tvl.NuGet.BuildTasks" version="1.0.0-alpha002" targetFramework="net46" developmentDependency="true" />
<package id="WindowsAPICodePack-Core" version="1.1.2" targetFramework="net46" />
<package id="WindowsAPICodePack-Shell" version="1.1.1" targetFramework="net46" />
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
<package id="xunit.runner.utility" version="2.1.0-beta4-build3109" targetFramework="net452" />
<package id="xunit.runner.utility" version="2.1.0" targetFramework="net46" />
</packages>
+81 -42
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.props" Condition="Exists('..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -7,15 +8,18 @@
<ProjectGuid>{34FB519C-FB49-4B31-ACA2-7F7879311BCF}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>xunit.runner.wpf</RootNamespace>
<RootNamespace>Xunit.Runner.Wpf</RootNamespace>
<AssemblyName>xunit.runner.wpf</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<TargetFrameworkProfile />
<NuPkgVersion Condition="'$(appveyor_build_version)' != ''">$(appveyor_build_version)</NuPkgVersion>
<NuPkgVersion Condition="'$(NuPkgVersion)' == ''">1.0.0-local</NuPkgVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -36,31 +40,42 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>Artwork\Application.ico</ApplicationIcon>
</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>
<Reference Include="GalaSoft.MvvmLight, Version=5.3.0.19026, Culture=neutral, PublicKeyToken=e7570ab207bcb616, processorArchitecture=MSIL">
<HintPath>..\packages\MvvmLightLibs.5.3.0.0\lib\net45\GalaSoft.MvvmLight.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="GalaSoft.MvvmLight.Extras, Version=5.1.1.35049, Culture=neutral, PublicKeyToken=669f0b5e8f868abf, processorArchitecture=MSIL">
<HintPath>..\packages\MvvmLightLibs.5.1.1.0\lib\net45\GalaSoft.MvvmLight.Extras.dll</HintPath>
<Reference Include="GalaSoft.MvvmLight.Extras, Version=5.3.0.19032, Culture=neutral, PublicKeyToken=669f0b5e8f868abf, processorArchitecture=MSIL">
<HintPath>..\packages\MvvmLightLibs.5.3.0.0\lib\net45\GalaSoft.MvvmLight.Extras.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="GalaSoft.MvvmLight.Platform, Version=5.1.1.35053, Culture=neutral, PublicKeyToken=5f873c45e98af8a1, processorArchitecture=MSIL">
<HintPath>..\packages\MvvmLightLibs.5.1.1.0\lib\net45\GalaSoft.MvvmLight.Platform.dll</HintPath>
<Reference Include="GalaSoft.MvvmLight.Platform, Version=5.3.0.19032, Culture=neutral, PublicKeyToken=5f873c45e98af8a1, processorArchitecture=MSIL">
<HintPath>..\packages\MvvmLightLibs.5.3.0.0\lib\net45\GalaSoft.MvvmLight.Platform.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Practices.ServiceLocation, Version=1.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="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>
<Reference Include="Microsoft.WindowsAPICodePack, Version=1.1.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\WindowsAPICodePack-Core.1.1.2\lib\Microsoft.WindowsAPICodePack.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.WindowsAPICodePack.Shell, Version=1.1.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\WindowsAPICodePack-Shell.1.1.1\lib\Microsoft.WindowsAPICodePack.Shell.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Collections.Immutable, Version=1.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.1.4.0-preview1-25305-02\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Data" />
<Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\MvvmLightLibs.5.1.1.0\lib\net45\System.Windows.Interactivity.dll</HintPath>
<HintPath>..\packages\MvvmLightLibs.5.3.0.0\lib\net45\System.Windows.Interactivity.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml" />
@@ -75,6 +90,10 @@
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="xunit.runner.utility.desktop, Version=2.1.0.3179, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.runner.utility.2.1.0\lib\net35\xunit.runner.utility.desktop.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
@@ -88,23 +107,23 @@
<Compile Include="Impl\RemoteTestUtil.Connection.cs" />
<Compile Include="Impl\RemoteTestUtil.BackgroundRunner.cs" />
<Compile Include="Impl\RemoteTestUtil.cs" />
<Compile Include="Impl\TestAssemblyWatcher.cs" />
<Compile Include="ITestAssemblyWatcher.cs" />
<Compile Include="ITestUtil.cs" />
<Compile Include="LoadingDialog.xaml.cs">
<DependentUpon>LoadingDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Persistence\Settings.cs" />
<Compile Include="Persistence\Storage.cs" />
<Compile Include="Persistence\Storage.WindowPlacement.cs" />
<Compile Include="Extensions.FuncComparer.cs" />
<Compile Include="ViewModel\RecentAssemblyViewModel.cs" />
<Compile Include="ViewModel\TraitCollectionView.cs" />
<Compile Include="ViewModel\AssemblyAndConfigFile.cs" />
<Compile Include="ViewModel\MainViewModel.cs" />
<Compile Include="ViewModel\SearchQuery.cs" />
<Compile Include="ViewModel\TestCaseViewModel.cs" />
<Compile Include="ViewModel\TestAssemblyViewModel.cs" />
<Compile Include="ViewModel\TraitViewModel.Comparer.cs" />
<Compile Include="ViewModel\TraitViewModel.cs" />
<Compile Include="ViewModel\TraitViewModelComparer.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>
@@ -127,31 +146,26 @@
<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\" />
<None Include="xunit.runner.wpf.targets">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<NuGetManifest Include="xunit.runner.wpf.nuspec">
<Version>$(NuPkgVersion)</Version>
<PackageAnalysis>False</PackageAnalysis>
</NuGetManifest>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<Resource Include="Artwork\xunit-dot-net-small-logo.png" />
<Resource Include="Artwork\Passed.ico" />
<Resource Include="Artwork\Skipped.ico" />
<Resource Include="Artwork\Failed.ico" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SampleTestAssembly\SampleTestAssembly.csproj">
<Project>{bdafb5dd-ffb3-4a94-a312-dfb080010846}</Project>
@@ -168,14 +182,39 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<WCFMetadata Include="Service References\" />
<Resource Include="Artwork\Passed_small.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Artwork\Passed_large.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Artwork\Failed_small.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Artwork\Failed_large.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Artwork\Skipped_small.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Artwork\Skipped_large.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Artwork\Application.ico" />
</ItemGroup>
<ItemGroup>
<Resource Include="Artwork\Running_large.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Artwork\Running_small.png" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 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 Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.props'))" />
<Error Condition="!Exists('..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.targets'))" />
</Target>
<Target Name="AfterBuild">
</Target>
-->
<Import Project="..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.targets" Condition="Exists('..\packages\Tvl.NuGet.BuildTasks.1.0.0-alpha002\build\Tvl.NuGet.BuildTasks.targets')" />
</Project>
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0"?>
<package >
<metadata>
<id>xunit.runner.wpf</id>
<version>$version$</version>
<title>xUnit.Runner.WPF</title>
<authors>Pilchie</authors>
<owners>Pilchie</owners>
<projectUrl>https://github.com/Pilchie/xunit.runner.wpf</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>XUnit Gui written in WPF</description>
<tags>XUnit Gui test runner</tags>
</metadata>
<files>
<file src="bin\$Configuration$\*.dll" target="tools\"/>
<file src="xunit.runner.wpf.targets" target="build\net45"/>
<file src="bin\$Configuration$\*.exe" target="tools\" exclude="**\*vshost*"/>
<file src="bin\$Configuration$\*.config" target="tools\" exclude="**\*vshost*"/>
</files>
</package>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<StartAction Condition="'$(StartActions)' == ''">Program</StartAction>
<StartProgram Condition="'$(StartProgram)' == ''">$(MSBuildThisFileDirectory)..\tools\xunit.runner.wpf.exe</StartProgram>
<StartArguments Condition="'$(StartArguments)' == ''">$(AssemblyName).dll</StartArguments>
<StartWorkingDirectory Condition="'$(StartWorkingDirectory)' == ''">$(OutputDirectory)</StartWorkingDirectory>
</PropertyGroup>
</Project>