142 Commits

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

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

closes #20
2015-10-12 09:19:44 -07:00
Kevin Pilch-Bisson 4371a4e837 Merge pull request #26 from Pilchie/issue-25
Fix issue where running a specific test was treated as running all of them
2015-10-12 09:11:57 -07:00
Dustin Campbell 65adde358c A few UI tweaks
Notably, the images for passed, failed and skipped have been replaced with images from the Visual Studio Image Library.
2015-10-12 08:58:11 -07:00
Dustin Campbell ae5e46d772 Fix issue where running a specific test was treated as running all of them 2015-10-12 06:28:42 -07:00
Jared Parsons 9934a70f52 Merge pull request #22 from Pilchie/reload
Implement Assembly reload and remove support
2015-08-29 10:10:55 -07:00
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
100 changed files with 3893 additions and 3037 deletions
+1
View File
@@ -214,3 +214,4 @@ FakesAssemblies/
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
/Settings.XamlStyle
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -1,6 +1,6 @@
<?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.8" />
</startup>
</configuration>
+8
View File
@@ -0,0 +1,8 @@
<Application x:Class="Launcher.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Launcher">
<Application.Resources>
</Application.Resources>
</Application>
+26
View File
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using Xunit.Runner.Wpf;
namespace Launcher
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var main = new MainWindow();
main.Title = "xUnit Revit by Speckle";
main.Show();
}
}
}
+96
View File
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{F7536830-352E-4081-BBBE-F3CF1A2F38FA}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>Launcher</RootNamespace>
<AssemblyName>Launcher</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\speckle.xunit.runner.wpf\speckle.xunit.runner.wpf.csproj">
<Project>{e43230d2-eeb7-45d3-b02c-0c1ef37b7a07}</Project>
<Name>speckle.xunit.runner.wpf</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
@@ -4,24 +4,24 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
// General Information about an assembly is controlled through the following
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("xunit.runner.wpf")]
[assembly: AssemblyTitle("Launcher")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("xunit.runner.wpf")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyProduct("Launcher")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
//In order to begin building localizable applications, set
//In order to begin building localizable applications, set
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
//inside a <PropertyGroup>. For example, if you are using US english
//in your source files, set the <UICulture> to en-US. Then uncomment
@@ -33,10 +33,10 @@ using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
@@ -44,11 +44,11 @@ using System.Windows;
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
+71
View File
@@ -0,0 +1,71 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Launcher.Properties
{
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ((resourceMan == null))
{
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Launcher.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
return resourceCulture;
}
set
{
resourceCulture = value;
}
}
}
}
+30
View File
@@ -0,0 +1,30 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Launcher.Properties
{
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
return defaultInstance;
}
}
}
}
+9
View File
@@ -0,0 +1,9 @@
# xunit.runner.wpf
XUnit Gui written in WPF
Fork of [xunit.runner.wpf](https://www.nuget.org/packages/xunit.runner.wpf).
This version, currently in WIP, uses the [AssemblyRunner](xunit.v3.runner.utility) found in xunit.v3.runner.utility to run the tests so that the external dlls are loaded in the same AppDomain.
This is necessary when running unit tests of code hosted by external applications (Revit etc).
Coming soon, a xunit runner for Revit.
+2 -2
View File
@@ -21,8 +21,8 @@ namespace SampleTestAssembly
//[Trait("TraitName1", "TraitValue2")]
public void Fail()
{
Thread.Sleep(TimeSpan.FromSeconds(1));
Assert.True(false);
Thread.Sleep(TimeSpan.FromSeconds(2));
Assert.True(true);
}
[Fact(Skip = "Testing")]
+15 -10
View File
@@ -1,6 +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\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="..\packages\xunit.core.2.4.1\build\xunit.core.props" Condition="Exists('..\packages\xunit.core.2.4.1\build\xunit.core.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -42,16 +42,16 @@
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
<Private>True</Private>
<HintPath>..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll</HintPath>
</Reference>
<Reference Include="xunit.assert, Version=2.1.0.3109, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.assert.2.1.0-beta4-build3109\lib\dotnet\xunit.assert.dll</HintPath>
<Private>True</Private>
<Reference Include="xunit.assert, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll</HintPath>
</Reference>
<Reference Include="xunit.core, Version=2.1.0.3109, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.core.2.1.0-beta4-build3109\lib\dotnet\xunit.core.dll</HintPath>
<Private>True</Private>
<Reference Include="xunit.core, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.core.2.4.1\lib\net452\xunit.core.dll</HintPath>
</Reference>
<Reference Include="xunit.execution.desktop, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
@@ -61,13 +61,18 @@
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\packages\xunit.analyzers.0.10.0\analyzers\dotnet\cs\xunit.analyzers.dll" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\xunit.core.2.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'))" />
<Error Condition="!Exists('..\packages\xunit.core.2.4.1\build\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.4.1\build\xunit.core.props'))" />
<Error Condition="!Exists('..\packages\xunit.core.2.4.1\build\xunit.core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.4.1\build\xunit.core.targets'))" />
</Target>
<Import Project="..\packages\xunit.core.2.4.1\build\xunit.core.targets" Condition="Exists('..\packages\xunit.core.2.4.1\build\xunit.core.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
+7 -5
View File
@@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="xunit" version="2.1.0-beta4-build3109" targetFramework="net452" />
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
<package id="xunit.assert" version="2.1.0-beta4-build3109" targetFramework="net452" />
<package id="xunit.core" version="2.1.0-beta4-build3109" targetFramework="net452" />
<package id="xunit.extensibility.core" version="2.1.0-beta4-build3109" targetFramework="net452" />
<package id="xunit" version="2.4.1" targetFramework="net452" />
<package id="xunit.abstractions" version="2.0.3" targetFramework="net452" />
<package id="xunit.analyzers" version="0.10.0" targetFramework="net452" />
<package id="xunit.assert" version="2.4.1" targetFramework="net452" />
<package id="xunit.core" version="2.4.1" targetFramework="net452" />
<package id="xunit.extensibility.core" version="2.4.1" targetFramework="net452" />
<package id="xunit.extensibility.execution" version="2.4.1" targetFramework="net452" />
</packages>
+52
View File
@@ -0,0 +1,52 @@
# .NET Desktop
# Build and run tests for .NET Desktop or Windows classic desktop solutions.
# Add steps that publish symbols, save build artifacts, and more:
# https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net
trigger:
- master
pool:
vmImage: 'windows-latest'
variables:
solution: '**/*.sln'
buildPlatform: 'Any CPU'
buildConfiguration: 'Release'
steps:
- task: NuGetToolInstaller@1
- task: NuGetCommand@2
inputs:
restoreSolution: '$(solution)'
- task: VSBuild@1
inputs:
solution: '$(solution)'
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
# - task: VSTest@2
# inputs:
# platform: '$(buildPlatform)'
# configuration: '$(buildConfiguration)'
- task: NuGetCommand@2
inputs:
command: 'pack'
packagesToPack: '**/speckle.xunit.runner.wpf.csproj'
versioningScheme: 'off'
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'artifact'
publishLocation: 'Container'
- powershell: |
nuget push -ApiKey $env:APIKEY -Source https://api.nuget.org/v3/index.json $(Build.ArtifactStagingDirectory)/**/*.nupkg
env:
APIKEY: $(nuget-apikey)
+37
View File
@@ -0,0 +1,37 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30011.22
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleTestAssembly", "SampleTestAssembly\SampleTestAssembly.csproj", "{BDAFB5DD-FFB3-4A94-A312-DFB080010846}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "speckle.xunit.runner.wpf", "speckle.xunit.runner.wpf\speckle.xunit.runner.wpf.csproj", "{E43230D2-EEB7-45D3-B02C-0C1EF37B7A07}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Launcher", "Launcher\Launcher.csproj", "{F7536830-352E-4081-BBBE-F3CF1A2F38FA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BDAFB5DD-FFB3-4A94-A312-DFB080010846}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BDAFB5DD-FFB3-4A94-A312-DFB080010846}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BDAFB5DD-FFB3-4A94-A312-DFB080010846}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BDAFB5DD-FFB3-4A94-A312-DFB080010846}.Release|Any CPU.Build.0 = Release|Any CPU
{E43230D2-EEB7-45D3-B02C-0C1EF37B7A07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E43230D2-EEB7-45D3-B02C-0C1EF37B7A07}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E43230D2-EEB7-45D3-B02C-0C1EF37B7A07}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E43230D2-EEB7-45D3-B02C-0C1EF37B7A07}.Release|Any CPU.Build.0 = Release|Any CPU
{F7536830-352E-4081-BBBE-F3CF1A2F38FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F7536830-352E-4081-BBBE-F3CF1A2F38FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7536830-352E-4081-BBBE-F3CF1A2F38FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7536830-352E-4081-BBBE-F3CF1A2F38FA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F8EBD009-FF10-49B8-91DB-F90A51C529BA}
EndGlobalSection
EndGlobal
@@ -1,6 +1,6 @@
<?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.8" />
</startup>
</configuration>
Binary file not shown.

After

Width:  |  Height:  |  Size: 266 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.

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.

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

+384
View File
@@ -0,0 +1,384 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Xunit.Abstractions;
using Xunit.Runner.Wpf;
using Xunit.Sdk;
namespace Xunit.Runners
{
/// <summary>
/// A class which makes it simpler for casual runner authors to find and run tests and get results.
/// Adapted from: https://github.com/xunit/xunit/blob/main/src/xunit.v3.runner.utility/Runners/AssemblyRunner.cs
/// </summary>
public class AssemblyRunner2 : LongLivedMarshalByRefObject, IDisposable, IMessageSinkWithTypes
{
static readonly Dictionary<Type, string> MessageTypeNames;
private TaskCompletionSource<string> tcs { get; set; }
private CancellationToken cancellationToken { get; set; }
volatile bool cancelled;
bool disposed;
readonly TestAssemblyConfiguration configuration;
readonly IFrontController controller;
string assemblyFileName { get; set; }
int testCasesDiscovered;
readonly List<ITestCase> testCasesToRun = new List<ITestCase>();
static AssemblyRunner2()
{
MessageTypeNames = new Dictionary<Type, string>();
AddMessageTypeName<IDiagnosticMessage>();
AddMessageTypeName<IDiscoveryCompleteMessage>();
AddMessageTypeName<IErrorMessage>();
AddMessageTypeName<ITestAssemblyCleanupFailure>();
AddMessageTypeName<ITestAssemblyFinished>();
AddMessageTypeName<ITestCaseCleanupFailure>();
AddMessageTypeName<ITestCaseDiscoveryMessage>();
AddMessageTypeName<ITestClassCleanupFailure>();
AddMessageTypeName<ITestCleanupFailure>();
AddMessageTypeName<ITestCollectionCleanupFailure>();
AddMessageTypeName<ITestFailed>();
AddMessageTypeName<ITestFinished>();
AddMessageTypeName<ITestMethodCleanupFailure>();
AddMessageTypeName<ITestOutput>();
AddMessageTypeName<ITestPassed>();
AddMessageTypeName<ITestSkipped>();
AddMessageTypeName<ITestStarting>();
}
AssemblyRunner2(AppDomainSupport appDomainSupport,
string assemblyFileName,
TaskCompletionSource<string> tcs,
CancellationToken cancellationToken,
string configFileName = null,
bool shadowCopy = true,
string shadowCopyFolder = null)
{
this.tcs = tcs;
this.cancellationToken = cancellationToken;
this.assemblyFileName = assemblyFileName;
controller = new XunitFrontController(appDomainSupport, assemblyFileName, configFileName, shadowCopy, shadowCopyFolder, diagnosticMessageSink: MessageSinkAdapter.Wrap(this));
configuration = ConfigReader.Load(assemblyFileName, configFileName);
}
/// <summary>
/// Set to get notification of diagnostic messages.
/// </summary>
public Action<DiagnosticMessageInfo> OnDiagnosticMessage { get; set; }
/// <summary>
/// Set to get notification of when test discovery is complete.
/// </summary>
public Action<TestDiscoveryInfo> OnDiscoveryComplete { get; set; }
/// <summary>
/// Set to get notification of error messages (unhandled exceptions outside of tests).
/// </summary>
public Action<ErrorMessageInfo> OnErrorMessage { get; set; }
/// <summary>
/// Set to get notification of when test execution is complete.
/// </summary>
public Action<ExecutionCompleteInfo> OnExecutionComplete { get; set; }
/// <summary>
/// Set to get notification of failed tests.
/// </summary>
public Action<TestFailedInfo> OnTestFailed { get; set; }
/// <summary>
/// Set to get notification of finished tests (regardless of outcome).
/// </summary>
public Action<TestFinishedInfo> OnTestFinished { get; set; }
/// <summary>
/// Set to get real-time notification of test output (for xUnit.net v2 tests only).
/// Note that output is captured and reported back to all the test completion Info>s
/// in addition to being sent to this Info>.
/// </summary>
public Action<TestOutputInfo> OnTestOutput { get; set; }
/// <summary>
/// Set to get notification of passing tests.
/// </summary>
public Action<TestPassedInfo> OnTestPassed { get; set; }
/// <summary>
/// Set to get notification of skipped tests.
/// </summary>
public Action<TestSkippedInfo> OnTestSkipped { get; set; }
/// <summary>
/// Set to get notification of when tests start running.
/// </summary>
public Action<TestStartingInfo> OnTestStarting { get; set; }
/// <summary>
/// Set to be able to filter the test cases to decide which ones to run. If this is not set,
/// then all test cases will be run.
/// </summary>
public Func<ITestCase, bool> TestCaseFilter { get; set; }
static void AddMessageTypeName<T>() => MessageTypeNames.Add(typeof(T), typeof(T).FullName);
/// <summary>
/// Call to request that the current run be cancelled. Note that cancellation may not be
/// instantaneous, and even after cancellation has been acknowledged, you can expect to
/// receive all the cleanup-related messages.
/// </summary>
public void Cancel()
{
cancelled = true;
}
/// <inheritdoc/>
public void Dispose()
{
if (disposed)
return;
disposed = true;
controller?.Dispose();
}
ITestFrameworkDiscoveryOptions GetDiscoveryOptions(bool? diagnosticMessages, TestMethodDisplay? methodDisplay, TestMethodDisplayOptions? methodDisplayOptions, bool? preEnumerateTheories, bool? internalDiagnosticMessages)
{
var discoveryOptions = TestFrameworkOptions.ForDiscovery(configuration);
discoveryOptions.SetSynchronousMessageReporting(true);
if (diagnosticMessages.HasValue)
discoveryOptions.SetDiagnosticMessages(diagnosticMessages);
if (internalDiagnosticMessages.HasValue)
discoveryOptions.SetDiagnosticMessages(internalDiagnosticMessages);
if (methodDisplay.HasValue)
discoveryOptions.SetMethodDisplay(methodDisplay);
if (methodDisplayOptions.HasValue)
discoveryOptions.SetMethodDisplayOptions(methodDisplayOptions);
if (preEnumerateTheories.HasValue)
discoveryOptions.SetPreEnumerateTheories(preEnumerateTheories);
return discoveryOptions;
}
ITestFrameworkExecutionOptions GetExecutionOptions(bool? diagnosticMessages, bool? parallel, int? maxParallelThreads, bool? internalDiagnosticMessages)
{
var executionOptions = TestFrameworkOptions.ForExecution(configuration);
executionOptions.SetSynchronousMessageReporting(true);
if (diagnosticMessages.HasValue)
executionOptions.SetDiagnosticMessages(diagnosticMessages);
if (internalDiagnosticMessages.HasValue)
executionOptions.SetDiagnosticMessages(internalDiagnosticMessages);
if (parallel.HasValue)
executionOptions.SetDisableParallelization(!parallel.GetValueOrDefault());
if (maxParallelThreads.HasValue)
executionOptions.SetMaxParallelThreads(maxParallelThreads);
return executionOptions;
}
/// <summary>
/// Starts running tests from a single type (if provided) or the whole assembly (if not). This call returns
/// immediately, and status results are dispatched to the Info>s on this class. Callers can check <see cref="Status"/>
/// to find out the current status.
/// </summary>
/// <param name="typeName">The (optional) type name of the single test class to run</param>
/// <param name="diagnosticMessages">Set to <c>true</c> to enable diagnostic messages; set to <c>false</c> to disable them.
/// By default, uses the value from the assembly configuration file.</param>
/// <param name="methodDisplay">Set to choose the default display name style for test methods.
/// By default, uses the value from the assembly configuration file. (This parameter is ignored for xUnit.net v1 tests.)</param>
/// <param name="methodDisplayOptions">Set to choose the default display name style options for test methods.
/// By default, uses the value from the assembly configuration file. (This parameter is ignored for xUnit.net v1 tests.)</param>
/// <param name="preEnumerateTheories">Set to <c>true</c> to pre-enumerate individual theory tests; set to <c>false</c> to use
/// a single test case for the theory. By default, uses the value from the assembly configuration file. (This parameter is ignored
/// for xUnit.net v1 tests.)</param>
/// <param name="parallel">Set to <c>true</c> to run test collections in parallel; set to <c>false</c> to run them sequentially.
/// By default, uses the value from the assembly configuration file. (This parameter is ignored for xUnit.net v1 tests.)</param>
/// <param name="maxParallelThreads">Set to 0 to use unlimited threads; set to any other positive integer to limit to an exact number
/// of threads. By default, uses the value from the assembly configuration file. (This parameter is ignored for xUnit.net v1 tests.)</param>
/// <param name="internalDiagnosticMessages">Set to <c>true</c> to enable internal diagnostic messages; set to <c>false</c> to disable them.
/// By default, uses the value from the assembly configuration file.</param>
public void Discover(
string typeName = null,
bool? diagnosticMessages = null,
TestMethodDisplay? methodDisplay = null,
TestMethodDisplayOptions? methodDisplayOptions = null,
bool? preEnumerateTheories = null,
bool? internalDiagnosticMessages = null)
{
cancelled = false;
testCasesDiscovered = 0;
testCasesToRun.Clear();
XunitWorkerThread.QueueUserWorkItem(() =>
{
var discoveryOptions = GetDiscoveryOptions(diagnosticMessages, methodDisplay, methodDisplayOptions, preEnumerateTheories, internalDiagnosticMessages);
if (typeName != null)
controller.Find(typeName, false, this, discoveryOptions);
else
controller.Find(false, this, discoveryOptions);
if (cancelled)
{
// Synthesize the execution complete message, since we're not going to run at all
if (OnExecutionComplete != null)
OnExecutionComplete(ExecutionCompleteInfo.Empty);
return;
}
});
}
public void Run(List<ITestCase> cases,
bool? diagnosticMessages = null,
bool? parallel = null,
int? maxParallelThreads = null,
bool? internalDiagnosticMessages = null)
{
cancelled = false;
testCasesDiscovered = cases.Count();
testCasesToRun.Clear();
testCasesToRun.AddRange(cases);
XunitWorkerThread.QueueUserWorkItem(() =>
{
if (cancelled)
{
// Synthesize the execution complete message, since we're not going to run at all
if (OnExecutionComplete != null)
OnExecutionComplete(ExecutionCompleteInfo.Empty);
return;
}
var executionOptions = GetExecutionOptions(diagnosticMessages, parallel, maxParallelThreads, internalDiagnosticMessages);
controller.RunTests(testCasesToRun, this, executionOptions);
});
}
/// <summary>
/// Creates an assembly runner that discovers and run tests in a separate app domain.
/// </summary>
/// <param name="assemblyFileName">The test assembly.</param>
/// <param name="configFileName">The test assembly configuration file.</param>
/// <param name="shadowCopy">If set to <c>true</c>, runs tests in a shadow copied app domain, which allows
/// tests to be discovered and run without locking assembly files on disk.</param>
/// <param name="shadowCopyFolder">The path on disk to use for shadow copying; if <c>null</c>, a folder
/// will be automatically (randomly) generated</param>
//public static AssemblyRunner2 WithAppDomain(string assemblyFileName,
// string configFileName = null,
// bool shadowCopy = true,
// string shadowCopyFolder = null)
//{
// //Guard.ArgumentValid(nameof(shadowCopyFolder), "Cannot set shadowCopyFolder if shadowCopy is false", shadowCopy == true || shadowCopyFolder == null);
// return new AssemblyRunner2(AppDomainSupport.Required, assemblyFileName, configFileName, shadowCopy, shadowCopyFolder);
//}
/// <summary>
/// Creates an assembly runner that discovers and runs tests without a separate app domain.
/// </summary>
/// <param name="assemblyFileName">The test assembly.</param>
public static AssemblyRunner2 WithoutAppDomain(string assemblyFileName, TaskCompletionSource<string> tcs, CancellationToken cancellationToken)
{
return new AssemblyRunner2(AppDomainSupport.Denied, assemblyFileName, tcs, cancellationToken);
}
bool DispatchMessage<TMessage>(IMessageSinkMessage message, HashSet<string> messageTypes, Action<TMessage> handler)
where TMessage : class
{
if (messageTypes == null || !MessageTypeNames.TryGetValue(typeof(TMessage), out var typeName) || !messageTypes.Contains(typeName))
return false;
handler((TMessage)message);
return true;
}
bool IMessageSinkWithTypes.OnMessageWithTypes(IMessageSinkMessage message, HashSet<string> messageTypes)
{
if (cancellationToken.IsCancellationRequested)
{
cancelled = true;
}
if (DispatchMessage<ITestCaseDiscoveryMessage>(message, messageTypes, testDiscovered =>
{
++testCasesDiscovered;
if (TestCaseFilter == null || TestCaseFilter(testDiscovered.TestCase))
testCasesToRun.Add(testDiscovered.TestCase);
}))
return !cancelled;
if (DispatchMessage<IDiscoveryCompleteMessage>(message, messageTypes, discoveryComplete =>
{
OnDiscoveryComplete?.Invoke(new TestDiscoveryInfo(testCasesToRun, assemblyFileName));
tcs.TrySetResult("");
}))
return !cancelled;
if (DispatchMessage<ITestAssemblyFinished>(message, messageTypes, assemblyFinished =>
{
OnExecutionComplete?.Invoke(new ExecutionCompleteInfo(assemblyFinished.TestsRun, assemblyFinished.TestsFailed, assemblyFinished.TestsSkipped, assemblyFinished.ExecutionTime));
tcs.TrySetResult("");
}))
return !cancelled;
if (OnDiagnosticMessage != null)
if (DispatchMessage<IDiagnosticMessage>(message, messageTypes, m => OnDiagnosticMessage(new DiagnosticMessageInfo(m.Message))))
return !cancelled;
if (OnTestFailed != null)
if (DispatchMessage<ITestFailed>(message, messageTypes, m => OnTestFailed(new TestFailedInfo(m.TestClass.Class.Name, m.TestMethod.Method.Name, m.TestCase.Traits, m.Test.DisplayName, m.TestCollection.DisplayName, m.ExecutionTime, m.Output, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
return !cancelled;
if (OnTestFinished != null)
if (DispatchMessage<ITestFinished>(message, messageTypes, m => OnTestFinished(new TestFinishedInfo(m.TestClass.Class.Name, m.TestMethod.Method.Name, m.TestCase.Traits, m.Test.DisplayName, m.TestCollection.DisplayName, m.ExecutionTime, m.Output))))
return !cancelled;
if (OnTestOutput != null)
if (DispatchMessage<ITestOutput>(message, messageTypes, m => OnTestOutput(new TestOutputInfo(m.TestClass.Class.Name, m.TestMethod.Method.Name, m.TestCase.Traits, m.Test.DisplayName, m.TestCollection.DisplayName, m.Output))))
return !cancelled;
if (OnTestPassed != null)
if (DispatchMessage<ITestPassed>(message, messageTypes, m => OnTestPassed(new TestPassedInfo(m.TestClass.Class.Name, m.TestMethod.Method.Name, m.TestCase.Traits, m.Test.DisplayName, m.TestCollection.DisplayName, m.ExecutionTime, m.Output))))
return !cancelled;
if (OnTestSkipped != null)
if (DispatchMessage<ITestSkipped>(message, messageTypes, m => OnTestSkipped(new TestSkippedInfo(m.TestClass.Class.Name, m.TestMethod.Method.Name, m.TestCase.Traits, m.Test.DisplayName, m.TestCollection.DisplayName, m.Reason))))
return !cancelled;
if (OnTestStarting != null)
if (DispatchMessage<ITestStarting>(message, messageTypes, m => OnTestStarting(new TestStartingInfo(m.TestClass.Class.Name, m.TestMethod.Method.Name, m.TestCase.Traits, m.Test.DisplayName, m.TestCollection.DisplayName))))
return !cancelled;
if (OnErrorMessage != null)
{
if (DispatchMessage<IErrorMessage>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.CatastrophicError, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
return !cancelled;
if (DispatchMessage<ITestAssemblyCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestAssemblyCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
return !cancelled;
if (DispatchMessage<ITestCaseCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestCaseCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
return !cancelled;
if (DispatchMessage<ITestClassCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestClassCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
return !cancelled;
if (DispatchMessage<ITestCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
return !cancelled;
if (DispatchMessage<ITestCollectionCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestCollectionCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
return !cancelled;
if (DispatchMessage<ITestMethodCleanupFailure>(message, messageTypes, m => OnErrorMessage(new ErrorMessageInfo(ErrorMessageType.TestMethodCleanupFailure, m.ExceptionTypes.FirstOrDefault(), m.Messages.FirstOrDefault(), m.StackTraces.FirstOrDefault()))))
return !cancelled;
}
return !cancelled;
}
}
}
@@ -1,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
{
@@ -0,0 +1,82 @@
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace Xunit.Runner.Wpf.Converters
{
public class TestStateConverter : IValueConverter
{
private static ImageSource runningSource;
private static ImageSource failedSource;
private static ImageSource passedSource;
private static ImageSource skippedSource;
private static SolidColorBrush skippedBrush = new SolidColorBrush(Color.FromRgb(0xEB, 0xCA, 0x00));
static TestStateConverter()
{
runningSource = LoadResourceImage("Running_small.png");
failedSource = LoadResourceImage("Failed_small.png");
passedSource = LoadResourceImage("Passed_small.png");
skippedSource = LoadResourceImage("Skipped_small.png");
}
private static BitmapImage LoadResourceImage(string resourceName)
{
var image = new BitmapImage();
image.BeginInit();
image.UriSource = new Uri("pack://application:,,,/Speckle.Xunit.Runner.Wpf;component/Artwork/" + resourceName);
image.EndInit();
return image;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var state = (TestState)value;
if (targetType == typeof(Brush))
{
switch (state)
{
case TestState.Running:
return Brushes.Blue;
case TestState.Failed:
return Brushes.Red;
case TestState.Passed:
return Brushes.Green;
case TestState.Skipped:
return skippedBrush;
default:
return Brushes.Gray;
}
}
else if (targetType == typeof(ImageSource))
{
switch (state)
{
case TestState.Running:
return runningSource;
case TestState.Failed:
return failedSource;
case TestState.Passed:
return passedSource;
case TestState.Skipped:
return skippedSource;
default:
return null;
}
}
else
{
throw new NotSupportedException();
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
@@ -0,0 +1,50 @@
using mscoree;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Xunit.Runner.Wpf
{
public static class AppDomains
{
public static IEnumerable<AppDomain> EnumAppDomains()
{
IntPtr enumHandle = IntPtr.Zero;
ICorRuntimeHost host = null;
try
{
host = GetCorRuntimeHost();
host.EnumDomains(out enumHandle);
object domain = null;
host.NextDomain(enumHandle, out domain);
while (domain != null)
{
yield return (AppDomain)domain;
host.NextDomain(enumHandle, out domain);
}
}
finally
{
if (host != null)
{
if (enumHandle != IntPtr.Zero)
{
host.CloseEnum(enumHandle);
}
Marshal.ReleaseComObject(host);
}
}
}
private static ICorRuntimeHost GetCorRuntimeHost()
{
return (ICorRuntimeHost)Activator.CreateInstance(Marshal.GetTypeFromCLSID(new Guid("CB2F6723-AB3A-11D2-9C40-00C04FA30A3E")));
}
}
}
+114
View File
@@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Xunit.Runner.Wpf
{
public static partial class Extensions
{
public static void AddRange<TList, TEnumerable>(this ICollection<TList> list, IEnumerable<TEnumerable> items) where TEnumerable : TList
{
foreach (var i in items)
{
list.Add(i);
}
}
public static void AddRange<TList, TEnumerable>(this ObservableCollection<TList> list, IEnumerable<TEnumerable> items) where TEnumerable : TList
{
foreach (var i in items)
{
list.Add(i);
}
}
public static int BinarySearch<T, TValue>(this ObservableCollection<T> collection, int index, int length, TValue value, IComparer<TValue> comparer, Func<T, TValue> selector)
{
comparer = comparer ?? Comparer<TValue>.Default;
var low = index;
var high = (index + length) - 1;
while (low <= high)
{
var mid = low + ((high - low) / 2);
var comp = comparer.Compare(selector(collection[mid]), value);
if (comp == 0)
{
return mid;
}
if (comp < 0)
{
low = mid + 1;
}
else
{
high = mid - 1;
}
}
return ~low;
}
public static int BinarySearch<T, TValue>(this ObservableCollection<T> collection, TValue value, IComparer<TValue> comparer, Func<T, TValue> selector)
{
return collection.BinarySearch(0, collection.Count, value, comparer, selector);
}
public static int BinarySearch<T, TValue>(this ObservableCollection<T> collection, int index, int length, TValue value, Func<TValue, TValue, int> comparison, Func<T, TValue> selector)
{
return collection.BinarySearch(index, length, value, new FuncComparer<TValue>(comparison), selector);
}
public static int BinarySearch<T, TValue>(this ObservableCollection<T> collection, TValue value, Func<TValue, TValue, int> comparison, Func<T, TValue> selector)
{
return collection.BinarySearch(0, collection.Count, value, new FuncComparer<TValue>(comparison), selector);
}
public static int BinarySearch<T, TValue>(this ObservableCollection<T> collection, TValue value, Func<T, TValue> selector)
{
return collection.BinarySearch(0, collection.Count, value, comparer: null, selector: selector);
}
public static int BinarySearch<T>(this ObservableCollection<T> collection, int index, int length, T value, IComparer<T> comparer)
{
return collection.BinarySearch(index, length, value, comparer, x => x);
}
public static int BinarySearch<T>(this ObservableCollection<T> collection, T value, IComparer<T> comparer)
{
return collection.BinarySearch(0, collection.Count, value, comparer, x => x);
}
public static int BinarySearch<T>(this ObservableCollection<T> collection, T value)
{
return collection.BinarySearch(0, collection.Count, value, Comparer<T>.Default, x => x);
}
public static int BinarySearch<T>(this ObservableCollection<T> collection, int index, int length, T value, Func<T, T, int> comparison)
{
return collection.BinarySearch(index, length, value, new FuncComparer<T>(comparison), x => x);
}
public static int BinarySearch<T>(this ObservableCollection<T> collection, T value, Func<T, T, int> comparison)
{
return collection.BinarySearch(0, collection.Count, value, new FuncComparer<T>(comparison), x => x);
}
private class FuncComparer<T> : IComparer<T>
{
private readonly Func<T, T, int> _comparison;
public FuncComparer(Func<T, T, int> comparison)
{
_comparison = comparison;
}
public int Compare(T x, T y) => _comparison(x, y);
}
}
}
@@ -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)
{
@@ -0,0 +1,23 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace mscoree
{
[CompilerGenerated]
[Guid("CB2F6722-AB3A-11D2-9C40-00C04FA30A3E")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[TypeIdentifier]
[ComImport]
[CLSCompliant(false)]
public interface ICorRuntimeHost
{
void _VtblGap1_11();
void EnumDomains(out IntPtr enumHandle);
void NextDomain([In] IntPtr enumHandle, [MarshalAs(UnmanagedType.IUnknown)] out object appDomain);
void CloseEnum([In] IntPtr enumHandle);
}
}
@@ -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();
}
}
@@ -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);
}
}
}
}
+421
View File
@@ -0,0 +1,421 @@
<Window x:Class="Xunit.Runner.Wpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform"
xmlns:converters="clr-namespace:Xunit.Runner.Wpf.Converters"
xmlns:local="clr-namespace:Xunit.Runner.Wpf"
xmlns:vm="clr-namespace:Xunit.Runner.Wpf.ViewModel"
mc:Ignorable="d"
Title="MainWindow" SizeToContent="Height" Width="800">
<!--<Grid>
-->
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<cmd:EventToCommand Command="{Binding WindowLoadedCommand}" />
</i:EventTrigger>
<i:EventTrigger EventName="Closing">
<cmd:EventToCommand Command="{Binding WindowClosingCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Window.Resources>
<converters:TestStateConverter x:Key="TestStateConverter" />
</Window.Resources>
<Grid local:CommandBindings.Registration="{Binding CommandBindings}">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="_File">
<MenuItem Command="{Binding ExitCommand}" Header="E_xit" />
</MenuItem>
<MenuItem Header="_Assembly">
<MenuItem Command="ApplicationCommands.Open" Header="_Open" />
<MenuItem Header="R_ecent" ItemsSource="{Binding RecentAssemblies}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding FilePath}" />
<Setter Property="Command" Value="{Binding Command}" />
<Setter Property="CommandParameter" Value="{Binding}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
<Separator />
<MenuItem Command="{Binding AssemblyRemoveAllCommand}" Header="_Unload" />
<MenuItem Command="{Binding AssemblyReloadAllCommand}" Header="_Reload" />
<Separator />
<MenuItem
Command="{Binding AutoReloadAssembliesCommand}"
Header="_Auto Reload Test Assemblies"
IsCheckable="True"
IsChecked="{Binding AutoReloadAssemblies}" />
</MenuItem>
</Menu>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="200px" />
<ColumnDefinition Width="2*" MinWidth="200px" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<GroupBox
Grid.Row="0"
Grid.Column="0"
Margin="3"
Header="Refinements">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="Search:" />
<TextBox Grid.Row="1" Text="{Binding FilterString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Label Grid.Row="2" Content="Assemblies:" />
<ListBox
Grid.Row="3"
Height="175"
ItemsSource="{Binding Assemblies}"
SelectionMode="Extended">
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:TestAssemblyViewModel">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DisplayName}" />
<TextBlock
FontStyle="Italic"
Foreground="Gray"
Text=" Discovering tests...">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding State}" Value="{x:Static vm:AssemblyState.Loading}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Command="{Binding AssemblyReloadCommand}" Header="Reload" />
<MenuItem Command="{Binding AssemblyReloadAllCommand}" Header="Reload All" />
<Separator />
<MenuItem Command="{Binding AssemblyRemoveCommand}" Header="Remove" />
<MenuItem Command="{Binding AssemblyRemoveAllCommand}" Header="Remove All" />
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
<Label Grid.Row="4" Content="Traits:" />
<TreeView Grid.Row="5" ItemsSource="{Binding Traits}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type vm:TraitViewModel}" ItemsSource="{Binding Children}">
<CheckBox Content="{Binding Text}" IsChecked="{Binding IsChecked}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<cmd:EventToCommand Command="{Binding DataContext.TraitCheckedChangedCommand, RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter="{Binding}" />
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<cmd:EventToCommand Command="{Binding DataContext.TraitCheckedChangedCommand, RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
</HierarchicalDataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ContextMenu>
<ContextMenu>
<MenuItem Command="{Binding TraitsClearCommand}" Header="Clear" />
</ContextMenu>
</TreeView.ContextMenu>
</TreeView>
</Grid>
</GroupBox>
<Grid Grid.Row="1" Grid.Column="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button
Grid.Column="0"
Margin="10,0,0,0"
Command="{Binding RunAllCommand}"
Content="_Run All" />
<Button
Grid.Column="1"
Command="{Binding RunSelectedCommand}"
Content="Run _Selected" />
<Button
Grid.Column="2"
Margin="0,0,10,0"
Command="{Binding CancelCommand}"
Content="_Cancel" />
</Grid>
<Grid Grid.Row="0" Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="*" MinHeight="200px" />
<RowDefinition Height="auto" />
<RowDefinition Height="*" MinHeight="200px" />
</Grid.RowDefinitions>
<GroupBox
Grid.Row="0"
Margin="3"
Header="{Binding TestCasesCaption}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="55*"/>
<ColumnDefinition Width="244*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.ColumnSpan="2">
<ToggleButton
Grid.Column="0"
Margin="0,4,2,4"
Background="Transparent"
BorderThickness="0"
Command="{Binding TestFilterChanged}"
IsChecked="{Binding FilterPassedTests}">
<StackPanel Orientation="Horizontal">
<Image Source="Artwork\Passed_large.png" />
<TextBlock
Margin="4,0"
VerticalAlignment="Center"
FontSize="16"
Text="{Binding TestsPassed, StringFormat={}{0:#\,0}}" />
</StackPanel>
</ToggleButton>
<ToggleButton
Grid.Column="1"
Margin="2,4"
Background="Transparent"
BorderThickness="0"
Command="{Binding TestFilterChanged}"
IsChecked="{Binding FilterFailedTests}">
<StackPanel Orientation="Horizontal">
<Image Source="Artwork\Failed_large.png" />
<TextBlock
Margin="4,0"
VerticalAlignment="Center"
FontSize="16"
Text="{Binding TestsFailed, StringFormat={}{0:#\,0}}" />
</StackPanel>
</ToggleButton>
<ToggleButton
Grid.Column="2"
Margin="2,4,0,4"
Background="Transparent"
BorderThickness="0"
Command="{Binding TestFilterChanged}"
IsChecked="{Binding FilterSkippedTests}">
<StackPanel Orientation="Horizontal">
<Image Source="Artwork\Skipped_large.png" />
<TextBlock
Margin="4,0"
VerticalAlignment="Center"
FontSize="16"
Text="{Binding TestsSkipped, StringFormat={}{0:#\,0}}" />
</StackPanel>
</ToggleButton>
<ToggleButton
Grid.Column="2"
Margin="2,4,0,4"
Background="Transparent"
BorderThickness="0"
Command="{Binding TestFilterChanged}"
IsChecked="{Binding FilterRunningTests}">
<StackPanel Orientation="Horizontal">
<Image Source="Artwork\Running_large.png" />
<TextBlock
Margin="4,0"
VerticalAlignment="Center"
FontSize="16"
Text="{Binding TestsRunning, StringFormat={}{0:#\,0}}" />
</StackPanel>
</ToggleButton>
</StackPanel>
<ListBox
x:Name="TestCases"
Grid.Row="1"
ItemsSource="{Binding FilteredTestCases}"
SelectedItem="{Binding SelectedTestCase, Mode=TwoWay}"
SelectionChanged="TestCases_SelectionChanged"
SelectionMode="Extended" Grid.ColumnSpan="2">
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:TestCaseViewModel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
Width="16"
Margin="0,0,2,0"
Source="{Binding Path=State, Mode=OneWay, Converter={StaticResource TestStateConverter}}" />
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Text="{Binding DisplayName}" />
<TextBlock
Grid.Column="2"
VerticalAlignment="Center"
Margin="10,0,2,0"
Text="{Binding ExecutionTime}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding RunSelectedCommand}" />
</ListBox.InputBindings>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<cmd:EventToCommand Command="{Binding Path=RunSelectedCommand}" PassEventArgsToCommand="False" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
</Grid>
</GroupBox>
<GridSplitter
Grid.Row="1"
Height="3"
HorizontalAlignment="Stretch"
Background="White" />
<GroupBox
Grid.Row="2"
Margin="3"
Header="Output">
<TextBox
FontFamily="Consolas"
HorizontalScrollBarVisibility="Auto"
IsReadOnly="True"
Text="{Binding SelectedTestCase.Output}"
VerticalScrollBarVisibility="Visible" />
</GroupBox>
</Grid>
<GridSplitter
Grid.Column="0"
Width="3"
VerticalAlignment="Stretch"
Background="White" />
<ProgressBar
Grid.Row="1"
Grid.Column="1"
Margin="3"
Foreground="{Binding Path=CurrentRunState, Converter={StaticResource TestStateConverter}, Mode=OneWay}"
Maximum="{Binding MaximumProgress}"
Minimum="0"
Value="{Binding Path=TestsCompleted}" />
</Grid>
<StackPanel Grid.Row="2">
<Border
Margin="3"
BorderBrush="LightGray"
BorderThickness="1" />
<StatusBar>
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="16" />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem Grid.Column="1">
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="DarkGreen" Text="Tests passed: " />
<TextBlock Text="{Binding TestsPassed, StringFormat={}{0:#\,0}}" />
</StackPanel>
</StatusBarItem>
<StatusBarItem Grid.Column="2">
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="DarkRed" Text="Tests failed: " />
<TextBlock Text="{Binding TestsFailed, StringFormat={}{0:#\,0}}" />
</StackPanel>
</StatusBarItem>
<StatusBarItem Grid.Column="3">
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="DarkGoldenrod" Text="Tests skipped: " />
<TextBlock Text="{Binding TestsSkipped, StringFormat={}{0:#\,0}}" />
</StackPanel>
</StatusBarItem>
</StatusBar>
</StackPanel>
</Grid>
</Window>
@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Xunit.Runner.Wpf.ViewModel;
namespace Xunit.Runner.Wpf
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
//in case it's loaded by an external application
this.DataContext = new ViewModelLocator().Main;
InitializeComponent();
}
private void TestCases_SelectionChanged(Object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
foreach (var item in e.AddedItems)
{
var model = item as TestCaseViewModel;
if (model != null)
{
model.IsSelected = true;
}
}
foreach (var item in e.RemovedItems)
{
var model = item as TestCaseViewModel;
if (model != null)
{
model.IsSelected = false;
}
}
}
}
}
@@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Xml;
using System.Xml.Linq;
namespace Xunit.Runner.Wpf.Persistence
{
internal sealed class Settings
{
private const string SettingsFileName = "settings.xml";
private const string RecentAssembliesElementName = "recent_assemblies";
private const string RecentAssemblyElementName = "recent_assembly";
private const string SettingsElementName = "settings";
private const string VersionAttributeName = "version";
private const string AutoReloadAssembliesElementName = "auto_reload_assemblies";
private const int MaxRecentAssemblies = 10;
private static readonly Version s_latestVersion = new Version(1, 0, 0, 0);
private List<string> recentAssemblies;
private bool autoReloadAssemblies;
private Settings()
{
recentAssemblies = new List<string>();
autoReloadAssemblies = true;
}
public void AddRecentAssembly(string filePath)
{
for (int i = recentAssemblies.Count - 1; i >= 0; i--)
{
if (StringComparer.OrdinalIgnoreCase.Equals(recentAssemblies[i], filePath))
{
recentAssemblies.RemoveAt(i);
}
}
recentAssemblies.Insert(0, filePath);
if (recentAssemblies.Count > MaxRecentAssemblies)
{
recentAssemblies.RemoveRange(MaxRecentAssemblies - 1, recentAssemblies.Count - MaxRecentAssemblies);
}
}
public void ToggleAutoReloadAssemblies()
{
autoReloadAssemblies = !autoReloadAssemblies;
}
public bool GetAutoReloadAssemblies() => autoReloadAssemblies;
public ImmutableArray<string> GetRecentAssemblies()
{
return this.recentAssemblies.ToImmutableArray();
}
public void Save()
{
using (var xmlFile = Storage.CreateXmlFile(SettingsFileName))
{
var xml = new XElement(SettingsElementName, new XAttribute(VersionAttributeName, s_latestVersion));
if (this.recentAssemblies.Count > 0)
{
var recentAssembliesElement = new XElement(RecentAssembliesElementName);
foreach (var recentAssembly in this.recentAssemblies)
{
recentAssembliesElement.Add(new XElement(RecentAssemblyElementName, recentAssembly));
}
xml.Add(recentAssembliesElement);
}
xml.Add(new XElement(AutoReloadAssembliesElementName, autoReloadAssemblies));
xml.Save(xmlFile);
}
}
public static Settings Load()
{
using (var xmlFile = Storage.OpenXmlFile(SettingsFileName))
{
var settings = new Settings();
if (xmlFile == null || xmlFile.EOF)
{
return settings;
}
try
{
xmlFile.MoveToContent();
}
catch (XmlException)
{
return settings;
}
var xml = XElement.Load(xmlFile);
var recentAssembliesElement = xml.Element(RecentAssembliesElementName);
if (recentAssembliesElement != null)
{
var recentAssemblyElements = recentAssembliesElement.Elements(RecentAssemblyElementName);
foreach (var recentAssemblyElement in recentAssemblyElements)
{
var filePath = (string)recentAssemblyElement;
settings.AddRecentAssembly(filePath);
}
}
var autoReloadAssembliesElement = xml.Element(AutoReloadAssembliesElementName);
if (autoReloadAssembliesElement != null)
{
if (!bool.TryParse(autoReloadAssembliesElement.Value, out var autoReloadAssemblies))
{
autoReloadAssemblies = true;
}
settings.autoReloadAssemblies = autoReloadAssemblies;
}
else
{
settings.autoReloadAssemblies = true;
}
return settings;
}
}
}
}
@@ -0,0 +1,105 @@
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Xml.Linq;
namespace Xunit.Runner.Wpf.Persistence
{
internal static partial class Storage
{
private static class WindowPlacement
{
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
private struct WINDOWPLACEMENT
{
public int length;
public int flags;
public int showCmd;
public POINT ptMinPosition;
public POINT ptMaxPosition;
public RECT rcNormalPosition;
}
private const int SW_SHOWNORMAL = 1;
private const int SW_SHOWMINIMIZED = 2;
[DllImport("user32.dll")]
private static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);
[DllImport("user32.dll")]
private static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl);
private const string WindowPlacementElementName = "window_placement";
private const string ShowCommandElementName = "show_command";
private const string MinPositionElementName = "min_position";
private const string MaxPositionElementName = "max_position";
private const string NormalPositionElementName = "normal_position";
private const string XAttributeName = "x";
private const string YAttributeName = "y";
private const string LeftAttributeName = "left";
private const string TopAttributeName = "top";
private const string RightAttributeName = "right";
private const string BottomAttributeName = "bottom";
public static void Restore(Window window, XElement xml)
{
var placement = new WINDOWPLACEMENT();
placement.length = Marshal.SizeOf(typeof(WINDOWPLACEMENT));
placement.flags = 0;
placement.showCmd = (int)xml.Element(ShowCommandElementName);
placement.ptMinPosition.x = (int)xml.Element(MinPositionElementName).Attribute(XAttributeName);
placement.ptMinPosition.y = (int)xml.Element(MinPositionElementName).Attribute(YAttributeName);
placement.ptMaxPosition.x = (int)xml.Element(MaxPositionElementName).Attribute(XAttributeName);
placement.ptMaxPosition.y = (int)xml.Element(MaxPositionElementName).Attribute(YAttributeName);
placement.rcNormalPosition.left = (int)xml.Element(NormalPositionElementName).Attribute(LeftAttributeName);
placement.rcNormalPosition.top = (int)xml.Element(NormalPositionElementName).Attribute(TopAttributeName);
placement.rcNormalPosition.right = (int)xml.Element(NormalPositionElementName).Attribute(RightAttributeName);
placement.rcNormalPosition.bottom = (int)xml.Element(NormalPositionElementName).Attribute(BottomAttributeName);
var windowInteropHelper = new WindowInteropHelper(window);
SetWindowPlacement(windowInteropHelper.Handle, ref placement);
}
public static XElement Save(Window window)
{
var windowInteropHelper = new WindowInteropHelper(window);
var placement = new WINDOWPLACEMENT();
GetWindowPlacement(windowInteropHelper.Handle, out placement);
return
new XElement(WindowPlacementElementName,
new XElement(ShowCommandElementName, (placement.showCmd == SW_SHOWMINIMIZED ? SW_SHOWNORMAL : placement.showCmd)),
new XElement(MinPositionElementName,
new XAttribute(XAttributeName, placement.ptMinPosition.x),
new XAttribute(YAttributeName, placement.ptMinPosition.y)),
new XElement(MaxPositionElementName,
new XAttribute(XAttributeName, placement.ptMaxPosition.x),
new XAttribute(YAttributeName, placement.ptMaxPosition.y)),
new XElement(NormalPositionElementName,
new XAttribute(LeftAttributeName, placement.rcNormalPosition.left),
new XAttribute(TopAttributeName, placement.rcNormalPosition.top),
new XAttribute(RightAttributeName, placement.rcNormalPosition.right),
new XAttribute(BottomAttributeName, placement.rcNormalPosition.bottom)));
}
}
}
}
@@ -0,0 +1,86 @@
using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Text;
using System.Windows;
using System.Xml;
using System.Xml.Linq;
namespace Xunit.Runner.Wpf.Persistence
{
internal static partial class Storage
{
private const string WindowLayoutFileName = "window_layout.xml";
private static string GetWindowLayoutFileName(Window window) => $"{window.Name}_{WindowLayoutFileName}";
private static IsolatedStorageFile GetStorageFile() => IsolatedStorageFile.GetUserStoreForAssembly();
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);
}
}
}
}
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit.Abstractions;
namespace Xunit.Runner.Wpf
{
public class TestDiscoveryInfo
{
public IEnumerable<ITestCase> Cases { get; set; }
public string AssemblyFileName { get; set; }
public TestDiscoveryInfo(IEnumerable<ITestCase> cases, string assemblyFileName)
{
Cases = cases;
AssemblyFileName = assemblyFileName;
}
}
}
+18
View File
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Xunit.Runner.Wpf
{
public enum TestState
{
All = 0,
NotRun = 1,
Running = 2,
Passed = 3,
Skipped = 4,
Failed = 5
}
}
+45
View File
@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace Xunit.Runner.Wpf
{
public class Tests
{
[Fact]
//[Trait("TraitName1", "TraitValue1")]
public void Pass()
{
Thread.Sleep(TimeSpan.FromSeconds(1));
}
[Fact]
public void Pass2()
{
Thread.Sleep(TimeSpan.FromSeconds(3));
}
[Fact]
public void Pass3()
{
Thread.Sleep(TimeSpan.FromSeconds(3));
}
[Fact]
//[Trait("TraitName1", "TraitValue2")]
public void Fail()
{
Thread.Sleep(TimeSpan.FromSeconds(1));
Assert.True(false);
}
[Fact(Skip = "Testing")]
//[Trait("TraitName2", "TraitValue2")]
public void Skip()
{
Thread.Sleep(TimeSpan.FromSeconds(1));
}
}
}
@@ -1,6 +1,6 @@
using System.IO;
namespace xunit.runner.wpf.ViewModel
namespace Xunit.Runner.Wpf.ViewModel
{
public class AssemblyAndConfigFile
{
@@ -0,0 +1,992 @@
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
using Microsoft.Win32;
using Microsoft.WindowsAPICodePack.Taskbar;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using Xunit.Runner.Wpf.Persistence;
using Xunit.Runners;
namespace Xunit.Runner.Wpf.ViewModel
{
public class MainViewModel : ViewModelBase
{
//public IAssemblyInjector Injector { get; set; }
public List<string> StartupAssemblies { get; set; }
#region commands
public ICommand ExitCommand { get; }
public ICommand WindowLoadedCommand { get; }
public RelayCommand<CancelEventArgs> WindowClosingCommand { get; }
public RelayCommand RunAllCommand { get; }
public RelayCommand RunSelectedCommand { get; }
public RelayCommand CancelCommand { get; }
public ICommand TraitCheckedChangedCommand { get; }
public ICommand TraitSelectionChangedCommand { get; }
public ICommand TraitsClearCommand { get; }
public ICommand AssemblyReloadCommand { get; }
public ICommand AssemblyReloadAllCommand { get; }
public ICommand AssemblyRemoveCommand { get; }
public ICommand AssemblyRemoveAllCommand { get; }
public ICommand AutoReloadAssembliesCommand { get; }
public CommandBindingCollection CommandBindings { get; }
#endregion
private readonly Settings settings;
private readonly ITestAssemblyWatcher assemblyWatcher;
private readonly HashSet<string> allTestCaseUniqueIDs = new HashSet<string>();
private readonly ObservableCollection<TestCaseViewModel> allTestCases = new ObservableCollection<TestCaseViewModel>();
private readonly TraitCollectionView traitCollectionView = new TraitCollectionView();
private readonly HashSet<string> runningTestSet = new HashSet<string>();
private CancellationTokenSource filterCancellationTokenSource = new CancellationTokenSource();
private CancellationTokenSource cancellationTokenSource;
private bool isBusy;
private SearchQuery searchQuery = new SearchQuery();
private bool autoReloadAssemblies;
public ObservableCollection<TestAssemblyViewModel> Assemblies { get; } = new ObservableCollection<TestAssemblyViewModel>();
public FilteredCollectionView<TestCaseViewModel, SearchQuery> FilteredTestCases { get; }
public ObservableCollection<TraitViewModel> Traits => this.traitCollectionView.Collection;
public bool AutoReloadAssemblies
{
get => autoReloadAssemblies;
set
{
var oldVal = autoReloadAssemblies;
autoReloadAssemblies = value;
RaisePropertyChanged(nameof(AutoReloadAssemblies), oldVal, autoReloadAssemblies);
}
}
public ObservableCollection<RecentAssemblyViewModel> RecentAssemblies { get; } = new ObservableCollection<RecentAssemblyViewModel>();
SynchronizationContext uiContext;
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
this.settings = Settings.Load();
if (IsInDesignMode)
{
this.Assemblies.Add(new TestAssemblyViewModel(new AssemblyAndConfigFile(@"C:\Code\Xunit.Runner.Wpf\SampleTestAssembly\bin\Debug\SampleTestAssembly.dll", null)));
}
//Commands
CommandBindings = CreateCommandBindings();
this.ExitCommand = new RelayCommand(OnExecuteExit);
this.WindowLoadedCommand = new RelayCommand(OnExecuteWindowLoaded);
this.WindowClosingCommand = new RelayCommand<CancelEventArgs>(OnExecuteWindowClosing);
this.RunAllCommand = new RelayCommand(OnExecuteRunAll, CanExecuteRunAll);
this.RunSelectedCommand = new RelayCommand(OnExecuteRunSelected, CanExecuteRunSelected);
this.CancelCommand = new RelayCommand(OnExecuteCancel, CanExecuteCancel);
this.TraitCheckedChangedCommand = new RelayCommand<TraitViewModel>(OnExecuteTraitCheckedChanged);
this.TraitsClearCommand = new RelayCommand(OnExecuteTraitsClear);
this.AssemblyReloadCommand = new RelayCommand(OnExecuteAssemblyReload, CanExecuteAssemblyReload);
this.AssemblyReloadAllCommand = new RelayCommand(OnExecuteAssemblyReloadAll);
this.AssemblyRemoveCommand = new RelayCommand(OnExecuteAssemblyRemove, CanExecuteAssemblyRemove);
this.AssemblyRemoveAllCommand = new RelayCommand(OnExecuteAssemblyRemoveAll);
this.AutoReloadAssembliesCommand = new RelayCommand(OnToggleAutoReloadAssemblies);
this.FilteredTestCases = new FilteredCollectionView<TestCaseViewModel, SearchQuery>(
allTestCases, TestCaseMatches, searchQuery, TestComparer.Instance);
this.FilteredTestCases.CollectionChanged += TestCases_CollectionChanged;
this.assemblyWatcher = new Impl.TestAssemblyWatcher(Dispatcher.CurrentDispatcher);
this.TestCasesCaption = "Test Cases (0)";
RebuildRecentAssembliesMenu();
AutoReloadAssemblies = this.settings.GetAutoReloadAssemblies();
UpdateAutoReloadStatus();
uiContext = SynchronizationContext.Current;
}
private Task Discover(string assemblyPath)
{
var tcs = new TaskCompletionSource<string>();
try
{
using (var runner = AssemblyRunner2.WithoutAppDomain(assemblyPath, tcs, cancellationTokenSource.Token))
{
runner.OnDiscoveryComplete = OnTestsDiscovered;
runner.Discover();
}
}
catch (Exception e)
{
}
return tcs.Task;
}
private List<Task> RunTests(IEnumerable<TestCaseViewModel> testsToRun)
{
Debug.Assert(this.isBusy);
Debug.Assert(this.cancellationTokenSource != null);
TestsCompleted = 0;
TestsRunning = 0;
TestsPassed = 0;
TestsFailed = 0;
TestsSkipped = 0;
CurrentRunState = TestState.NotRun;
foreach (var test in testsToRun)
{
test.State = TestState.NotRun;
test.ExecutionTime = "";
}
var taskList = new List<Task>();
var assemblies = testsToRun.GroupBy(x => x.AssemblyFileName).ToDictionary(x => x.Key, y => y.ToList());
foreach (var key in assemblies.Keys)
{
var path = assemblies[key].FirstOrDefault().AssemblyPath;
taskList.Add(Run(path, key, assemblies[key]));
}
return taskList;
}
private Task Run(string assemblyPath, string assemblyName, IEnumerable<TestCaseViewModel> testsToRun)
{
var tcs = new TaskCompletionSource<string>();
try
{
////inject stuff into assembly
//if (Injector != null)
//{
// Assembly ass = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => x.FullName == assemblyName);
// if (ass != null)
// Injector.Inject(ass);
//}
using (var runner = AssemblyRunner2.WithoutAppDomain(assemblyPath, tcs, cancellationTokenSource.Token))
{
runner.OnExecutionComplete = OnExecutionComplete;
runner.OnTestStarting = OnTestStarting;
runner.OnTestFailed = OnTestFailed;
runner.OnTestSkipped = OnTestSkipped;
runner.OnTestPassed = OnTestPassed;
runner.OnTestFinished = OnTestFinished;
runner.Run(testsToRun.Select(x => x.TestCase).ToList(), maxParallelThreads:1);
}
}
catch (Exception e)
{
}
return tcs.Task;
}
#region commands
private bool CanExecuteCancel()
{
return this.cancellationTokenSource != null && !this.cancellationTokenSource.IsCancellationRequested;
}
private void OnExecuteCancel()
{
Debug.Assert(CanExecuteCancel());
this.cancellationTokenSource.Cancel();
}
private async void OnExecuteOpen(object sender, ExecutedRoutedEventArgs e)
{
var fileDialog = new OpenFileDialog
{
Filter = "Unit Test Assemblies|*.dll",
Multiselect = true
};
if (fileDialog.ShowDialog() != true)
{
return;
}
var assemblies = fileDialog.FileNames.Select(x => new AssemblyAndConfigFile(x, configFileName: null));
await AddAssemblies(assemblies);
}
private static void OnExecuteExit()
{
//Application.Current.Shutdown();
}
private async void OnExecuteWindowLoaded()
{
if (StartupAssemblies == null)
return;
var assemblies = StartupAssemblies.Select(x => new AssemblyAndConfigFile(x, configFileName: null));
await AddAssemblies(assemblies);
//await AddAssemblies(ParseCommandLine(Environment.GetCommandLineArgs().Skip(1)));
}
private void OnExecuteWindowClosing(CancelEventArgs e)
{
this.settings.Save();
}
private bool CanExecuteRunAll()
=> !IsBusy && FilteredTestCases.Any();
private bool CanExecuteRunSelected()
=> !IsBusy && SelectedTestCase != null;
private async void OnExecuteRunAll()
{
UpdateTestCaseInfo(useSelected: false);
await ExecuteTestSessionOperation(RunFilteredTests);
}
private async void OnExecuteRunSelected()
{
Debug.Assert(this.SelectedTestCase != null);
UpdateTestCaseInfo(useSelected: true);
await ExecuteTestSessionOperation(RunSelectedTests);
}
private List<Task> RunFilteredTests()
{
return RunTests(FilteredTestCases.ToImmutableList());
}
private List<Task> RunSelectedTests()
{
return RunTests(ImmutableList.CreateRange(FilteredTestCases.Where(x => x.IsSelected).ToList()));
}
private void OnTestsDiscovered(TestDiscoveryInfo discoveryInfo)
{
uiContext.Send(x =>
{
var traitWorkerList = new List<TraitViewModel>();
foreach (var testCase in discoveryInfo.Cases)
{
traitWorkerList.Clear();
// Get or create traits.
if (testCase.Traits.Count > 0)
{
foreach (var kvp in testCase.Traits)
{
var name = kvp.Key;
var values = kvp.Value;
var parentTraitViewModel = traitCollectionView.GetOrAdd(name);
foreach (var value in values)
{
var traitViewModel = parentTraitViewModel.GetOrAdd(value);
traitWorkerList.Add(traitViewModel);
}
}
}
var vm = new TestCaseViewModel(
testCase.DisplayName,
testCase.UniqueID,
testCase.SkipReason,
testCase.TestMethod.TestClass.TestCollection.TestAssembly.Assembly.Name,
testCase.TestMethod.TestClass.TestCollection.TestAssembly.Assembly.AssemblyPath,
testCase,
traitWorkerList);
allTestCases.Add(vm);
}
}, null);
}
private void OnExecutionComplete(ExecutionCompleteInfo info)
{
//Console.WriteLine($"Finished: {info.TotalTests} tests in {Math.Round(info.ExecutionTime, 3)}s ({info.TestsFailed} failed, {info.TestsSkipped} skipped)");
//uiContext.Send(x =>
//{
// IsBusy = false;
//}, null);
}
private void OnTestFinished(TestFinishedInfo info)
{
TestsCompleted++;
var test = FilteredTestCases.FirstOrDefault(x => x.DisplayName == info.TestDisplayName);
if (test != null)
{
test.ExecutionTime = Math.Round(info.ExecutionTime, 2) + "s";
}
}
private void OnTestPassed(TestPassedInfo info)
{
TestsPassed++;
var test = FilteredTestCases.FirstOrDefault(x => x.DisplayName == info.TestDisplayName);
if (test != null)
{
test.Output = info.Output;
test.State = TestState.Passed;
CurrentRunState = test.State;
}
}
private void OnTestFailed(TestFailedInfo info)
{
TestsFailed++;
var test = FilteredTestCases.FirstOrDefault(x => x.DisplayName == info.TestDisplayName);
if (test != null)
{
test.State = TestState.Failed;
CurrentRunState = test.State;
test.Output = info.ExceptionMessage;
if (info.ExceptionStackTrace != null)
{
test.Output += "\n\n";
test.Output += info.ExceptionStackTrace;
}
}
}
private void OnTestSkipped(TestSkippedInfo info)
{
TestsSkipped++;
var test = FilteredTestCases.FirstOrDefault(x => x.DisplayName == info.TestDisplayName);
if (test != null)
{
test.State = TestState.Skipped;
CurrentRunState = test.State;
test.Output = info.SkipReason;
}
}
private void OnTestStarting(TestStartingInfo info)
{
var test = FilteredTestCases.FirstOrDefault(x => x.DisplayName == info.TestDisplayName);
if (test != null)
{
test.State = TestState.Running;
}
}
private void OnExecuteTraitCheckedChanged(TraitViewModel trait)
{
this.searchQuery.TraitSet = this.traitCollectionView.GetCheckedTraits();
FilterAfterDelay();
}
private void OnExecuteTraitsClear()
{
foreach (var cur in this.traitCollectionView.Collection)
{
cur.IsChecked = false;
}
}
private async void OnExecuteRecentAssembly(RecentAssemblyViewModel recentAssembly)
{
var assemblyAndConfig = new AssemblyAndConfigFile(recentAssembly.FilePath, configFileName: null);
await this.AddAssemblies(new[] { assemblyAndConfig });
}
private bool CanExecuteAssemblyReload()
{
return SelectedAssemblies.Count > 0;
}
private async void OnExecuteAssemblyReload()
{
await ReloadAssemblies(SelectedAssemblies);
}
private async void OnExecuteAssemblyReloadAll()
{
await ReloadAssemblies(Assemblies);
}
private bool CanExecuteAssemblyRemove()
{
return SelectedAssemblies.Count > 0;
}
private void OnExecuteAssemblyRemove()
{
RemoveAssemblies(SelectedAssemblies);
}
private void OnExecuteAssemblyRemoveAll()
{
RemoveAssemblies(Assemblies.ToArray());
}
private void OnToggleAutoReloadAssemblies()
{
ToggleReloadAssemblies();
}
#endregion
public List<TestAssemblyViewModel> SelectedAssemblies
{
get { return Assemblies.Where(x => x.IsSelected).ToList(); }
}
private void ToggleReloadAssemblies()
{
this.settings.ToggleAutoReloadAssemblies();
AutoReloadAssemblies = this.settings.GetAutoReloadAssemblies();
UpdateAutoReloadStatus();
}
private void UpdateAutoReloadStatus()
{
if (AutoReloadAssemblies)
{
assemblyWatcher.EnableWatch(ReloadAssemblies);
}
else
{
assemblyWatcher.DisableWatch();
}
}
private void RebuildRecentAssembliesMenu()
{
this.RecentAssemblies.Clear();
foreach (var recentAssembly in this.settings.GetRecentAssemblies())
{
var viewModel = new RecentAssemblyViewModel(recentAssembly, new RelayCommand<RecentAssemblyViewModel>(this.OnExecuteRecentAssembly));
this.RecentAssemblies.Add(viewModel);
}
}
private void RemoveAssemblyTestCases(string assemblyPath)
{
var i = 0;
while (i < this.allTestCases.Count)
{
if (string.Compare(this.allTestCases[i].AssemblyFileName, assemblyPath, StringComparison.OrdinalIgnoreCase) == 0)
{
this.allTestCaseUniqueIDs.Remove(this.allTestCases[i].UniqueID);
this.allTestCases.RemoveAt(i);
}
else
{
i++;
}
}
}
private CommandBindingCollection CreateCommandBindings()
{
var openBinding = new CommandBinding(ApplicationCommands.Open, OnExecuteOpen);
CommandManager.RegisterClassCommandBinding(typeof(MainViewModel), openBinding);
return new CommandBindingCollection
{
openBinding,
};
}
private IEnumerable<AssemblyAndConfigFile> ParseCommandLine(IEnumerable<string> enumerable)
{
while (enumerable.Any())
{
var assemblyFileName = enumerable.First();
enumerable = enumerable.Skip(1);
var configFileName = (string)null;
if (IsConfigFile(enumerable.FirstOrDefault()))
{
configFileName = enumerable.First();
enumerable = enumerable.Skip(1);
}
yield return new AssemblyAndConfigFile(assemblyFileName, configFileName);
}
}
private bool IsConfigFile(string fileName)
=> (fileName?.EndsWith(".config", StringComparison.OrdinalIgnoreCase) ?? false) ||
(fileName?.EndsWith(".json", StringComparison.OrdinalIgnoreCase) ?? false);
public async Task AddAssemblies(IEnumerable<AssemblyAndConfigFile> assemblies)
{
if (!assemblies.Any())
{
return;
}
var newAssemblyViewModels = new List<TestAssemblyViewModel>();
try
{
await this.ExecuteTestSessionOperation(() =>
{
var taskList = new List<Task>();
foreach (var assembly in assemblies)
{
taskList.Add(Discover(assembly.AssemblyFileName));
var assemblyViewModel = new TestAssemblyViewModel(assembly);
newAssemblyViewModels.Add(assemblyViewModel);
this.Assemblies.Add(assemblyViewModel);
this.settings.AddRecentAssembly(assembly.AssemblyFileName);
assemblyViewModel.State = AssemblyState.Loading;
}
return taskList;
});
}
finally
{
foreach (var assemblyViewModel in newAssemblyViewModels)
{
assemblyViewModel.State = AssemblyState.Ready;
assemblyWatcher.AddAssembly(assemblyViewModel.FileName);
}
RebuildRecentAssembliesMenu();
}
}
private async Task ExecuteTestSessionOperation(Func<List<Task>> operation)
{
Debug.Assert(!this.IsBusy);
Debug.Assert(this.cancellationTokenSource == null);
try
{
this.IsBusy = true;
this.cancellationTokenSource = new CancellationTokenSource();
var taskList = operation();
await Task.WhenAll(taskList);
}
catch (Exception ex)
{
this.cancellationTokenSource?.Cancel();
//MessageBox.Show(ex.ToString());
}
finally
{
this.cancellationTokenSource = null;
this.IsBusy = false;
}
}
public bool ReloadAssemblies(IEnumerable<string> assemblies)
{
if (IsBusy)
{
return false;
}
var testAssemblies = Assemblies.Where(assembly => assemblies.Contains(assembly.FileName));
uiContext.Send(x => { ReloadAssemblies(testAssemblies); }, null);
return true;
}
private async Task ReloadAssemblies(IEnumerable<TestAssemblyViewModel> assemblies)
{
try
{
await ExecuteTestSessionOperation(() =>
{
var taskList = new List<Task>();
foreach (var assemblyViewModel in assemblies)
{
assemblyViewModel.State = AssemblyState.Loading;
var assemblyFileName = assemblyViewModel.FileName;
RemoveAssemblyTestCases(assemblyFileName);
taskList.Add(Discover(assemblyFileName));
}
return taskList;
});
RebuildTraits();
}
finally
{
foreach (var assemblyViewModel in assemblies)
{
assemblyViewModel.State = AssemblyState.Ready;
}
}
}
private void RemoveAssemblies(IEnumerable<TestAssemblyViewModel> assemblies)
{
foreach (var assembly in assemblies.ToList())
{
assemblyWatcher.RemoveAssembly(assembly.FileName);
RemoveAssemblyTestCases(assembly.FileName);
Assemblies.Remove(assembly);
}
RebuildTraits();
}
/// <summary>
/// Reloading an assembly could have changed the traits. There is no easy way
/// to selectively edit this list (traits can cross assembly boundaries). Just
/// do a full reload instead.
/// way to
/// </summary>
private void RebuildTraits()
{
this.traitCollectionView.Collection.Clear();
foreach (var testCase in this.allTestCases)
{
this.traitCollectionView.AddRange(testCase.Traits);
}
}
private void TestCases_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdateTestCaseInfo(useSelected: false);
ClearSelectionFlags();
}
private void ClearSelectionFlags()
{
foreach (var test in this.allTestCases)
{
test.IsSelected = false;
}
}
void UpdateTestCaseInfo(bool useSelected)
{
var count = FilteredTestCases.Count;
if (useSelected)
{
var selected = FilteredTestCases.Count(tc => tc.IsSelected);
if (selected > 0)
{
count = selected;
}
}
//TestCasesCaption = $"Test Cases ({count:#,0})";
MaximumProgress = count;
}
private void UpdateProgress()
{
if (TaskbarManager.IsPlatformSupported)
{
var tb = TaskbarManager.Instance;
tb.SetProgressState(GetTaskBarState());
tb.SetProgressValue(this.TestsCompleted, this.MaximumProgress);
}
}
private TaskbarProgressBarState GetTaskBarState()
{
switch (this.CurrentRunState)
{
case TestState.Failed:
return TaskbarProgressBarState.Error;
case TestState.Skipped:
return TaskbarProgressBarState.Paused;
default:
return TaskbarProgressBarState.Normal;
}
}
private void FilterAfterDelay()
{
filterCancellationTokenSource.Cancel();
filterCancellationTokenSource = new CancellationTokenSource();
var token = filterCancellationTokenSource.Token;
Task
.Delay(TimeSpan.FromMilliseconds(200), token)
.ContinueWith(
x =>
{
FilteredTestCases.FilterArgument = searchQuery;
},
token,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
}
#region bindings
public string FilterString
{
get { return searchQuery.SearchString; }
set
{
if (Set(ref searchQuery.SearchString, value))
{
FilterAfterDelay();
}
}
}
private int testsCompleted = 0;
public int TestsCompleted
{
get { return testsCompleted; }
set
{
Set(ref testsCompleted, value);
UpdateProgress();
}
}
private TestCaseViewModel selectedTest;
public TestCaseViewModel SelectedTestCase
{
get { return selectedTest; }
set
{
Set(ref selectedTest, value);
}
}
private string testCasesCaption;
public string TestCasesCaption
{
get { return testCasesCaption; }
private set { Set(ref testCasesCaption, value); }
}
private bool IsBusy
{
get { return isBusy; }
set
{
isBusy = value;
RunAllCommand.RaiseCanExecuteChanged();
RunSelectedCommand.RaiseCanExecuteChanged();
CancelCommand.RaiseCanExecuteChanged();
}
}
private int testsRunning = 0;
public int TestsRunning
{
get { return testsRunning; }
set { Set(ref testsRunning, value); }
}
private int testsPassed = 0;
public int TestsPassed
{
get { return testsPassed; }
set { Set(ref testsPassed, value); }
}
private int testsFailed = 0;
public int TestsFailed
{
get { return testsFailed; }
set { Set(ref testsFailed, value); }
}
private int testsSkipped = 0;
public int TestsSkipped
{
get { return testsSkipped; }
set { Set(ref testsSkipped, value); }
}
private int maximumProgress = int.MaxValue;
public int MaximumProgress
{
get { return maximumProgress; }
set
{
Set(ref maximumProgress, value);
UpdateProgress();
}
}
private TestState currentRunState;
public TestState CurrentRunState
{
get { return currentRunState; }
set
{
Set(ref currentRunState, value);
UpdateProgress();
}
}
public bool FilterRunningTests
{
get { return searchQuery.FilterRunningTests; }
set
{
if (Set(ref searchQuery.FilterRunningTests, value))
{
FilterAfterDelay();
}
}
}
public bool FilterPassedTests
{
get { return searchQuery.FilterPassedTests; }
set
{
if (Set(ref searchQuery.FilterPassedTests, value))
{
FilterAfterDelay();
}
}
}
public bool FilterFailedTests
{
get { return searchQuery.FilterFailedTests; }
set
{
if (Set(ref searchQuery.FilterFailedTests, value))
{
FilterAfterDelay();
}
}
}
public bool FilterSkippedTests
{
get { return searchQuery.FilterSkippedTests; }
set
{
if (Set(ref searchQuery.FilterSkippedTests, value))
{
FilterAfterDelay();
}
}
}
#endregion
private static bool TestCaseMatches(TestCaseViewModel testCase, SearchQuery searchQuery)
{
if (testCase.DisplayName.IndexOf(searchQuery.SearchString, StringComparison.CurrentCultureIgnoreCase) < 0)
{
return false;
}
if (searchQuery.TraitSet.Count > 0)
{
var anyMatch = false;
foreach (var cur in testCase.Traits)
{
if (searchQuery.TraitSet.Contains(cur))
{
anyMatch = true;
break;
}
}
if (!anyMatch)
{
return false;
}
}
var noFilter = !(searchQuery.FilterRunningTests | searchQuery.FilterFailedTests | searchQuery.FilterPassedTests | searchQuery.FilterSkippedTests);
switch (testCase.State)
{
case TestState.Running:
return noFilter || searchQuery.FilterRunningTests;
case TestState.Passed:
return noFilter || searchQuery.FilterPassedTests;
case TestState.Skipped:
return noFilter || searchQuery.FilterSkippedTests;
case TestState.Failed:
return noFilter || searchQuery.FilterFailedTests;
case TestState.NotRun:
return noFilter;
default:
Debug.Assert(false, "What state is this test case in?");
return true;
}
}
}
public class TestComparer : IComparer<TestCaseViewModel>
{
public static TestComparer Instance { get; } = new TestComparer();
public int Compare(TestCaseViewModel x, TestCaseViewModel y)
{
int result = StringComparer.OrdinalIgnoreCase.Compare(x.DisplayName, y.DisplayName);
if (result != 0)
return result;
result = StringComparer.Ordinal.Compare(x.DisplayName, y.DisplayName);
if (result != 0)
return result;
return StringComparer.Ordinal.Compare(x.UniqueID, y.UniqueID);
}
private TestComparer() { }
}
}
@@ -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;
}
}
}
@@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace Xunit.Runner.Wpf.ViewModel
{
public class SearchQuery
{
public bool FilterRunningTests = false;
public bool FilterFailedTests = false;
public bool FilterPassedTests = false;
public bool FilterSkippedTests = false;
public string SearchString = string.Empty;
public ISet<TraitViewModel> TraitSet = new HashSet<TraitViewModel>(TraitViewModel.EqualityComparer);
}
}
@@ -1,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
}
}
@@ -0,0 +1,67 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using GalaSoft.MvvmLight;
using Xunit.Abstractions;
using Xunit.Runner.Wpf;
namespace Xunit.Runner.Wpf.ViewModel
{
public class TestCaseViewModel : ViewModelBase
{
private TestState _state = TestState.NotRun;
private string _output = "";
private string _execTime = "";
public string DisplayName { get; }
public string UniqueID { get; }
public string SkipReason { get; }
public string AssemblyFileName { get; }
public string AssemblyPath { get; }
public ImmutableArray<TraitViewModel> Traits { get; }
public bool IsSelected { get; set; }
public bool HasSkipReason => !string.IsNullOrEmpty(this.SkipReason);
public ITestCase TestCase { get; }
public TestState State
{
get { return _state; }
set { Set(ref _state, value); }
}
public string ExecutionTime
{
get { return _execTime; }
set
{
Set(ref _execTime, value);
}
}
public string Output
{
get { return _output; }
set
{
Set(ref _output, value);
}
}
public TestCaseViewModel(string displayName, string uniqueID, string skipReason, string assemblyFileName, string assemblyPath, ITestCase testCase, IEnumerable<TraitViewModel> traits)
{
this.DisplayName = displayName;
this.UniqueID = uniqueID;
this.SkipReason = skipReason;
this.AssemblyFileName = assemblyFileName;
this.AssemblyPath = assemblyPath;
this.TestCase = testCase;
this.Traits = traits.ToImmutableArray();
if (!string.IsNullOrEmpty(skipReason))
{
_state = TestState.Skipped;
}
}
}
}
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace Xunit.Runner.Wpf.ViewModel
{
public sealed partial class TraitCollectionView
{
public ObservableCollection<TraitViewModel> Collection { get; } = new ObservableCollection<TraitViewModel>();
public void AddRange(IEnumerable<TraitViewModel> traits)
{
foreach (var trait in traits)
{
var index = Collection.BinarySearch(trait, TraitViewModel.Comparer);
if (index < 0)
{
Collection.Insert(~index, trait);
}
else
{
// This trait already exists, add more values.
var originalTrait = Collection[index];
originalTrait.AddValues(trait.Children.Select(x => x.Text));
}
}
}
public TraitViewModel GetOrAdd(string text)
{
var index = this.Collection.BinarySearch(text, StringComparer.Ordinal, vm => vm.Text);
if (index < 0)
{
var viewModel = new TraitViewModel(text);
this.Collection.Insert(~index, viewModel);
return viewModel;
}
return this.Collection[index];
}
public ISet<TraitViewModel> GetCheckedTraits()
{
return new HashSet<TraitViewModel>(
Collection.SelectMany(x => x.Children).Where(x => x.IsChecked == true),
comparer: TraitViewModel.EqualityComparer);
}
}
}
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
namespace Xunit.Runner.Wpf.ViewModel
{
public partial class TraitViewModel
{
private static readonly TraitViewModelComparer _comparer = new TraitViewModelComparer();
internal static IComparer<TraitViewModel> Comparer => _comparer;
internal static IEqualityComparer<TraitViewModel> EqualityComparer => _comparer;
private class TraitViewModelComparer : IEqualityComparer<TraitViewModel>, IComparer<TraitViewModel>
{
public int Compare(TraitViewModel x, TraitViewModel y) => StringComparer.Ordinal.Compare(x.Text, y.Text);
public bool Equals(TraitViewModel x, TraitViewModel y) => StringComparer.Ordinal.Equals(x.Text, y.Text);
public int GetHashCode(TraitViewModel obj) => obj.Text.GetHashCode();
}
}
}
@@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using GalaSoft.MvvmLight;
using Xunit.Runner.Wpf;
namespace Xunit.Runner.Wpf.ViewModel
{
public partial class TraitViewModel : ViewModelBase
{
private readonly TraitViewModel _parent;
private bool? _isChecked;
private bool _isExpanded;
private string _text;
public ObservableCollection<TraitViewModel> Children { get; }
public TraitViewModel(string text)
: this(null, text)
{
}
private TraitViewModel(TraitViewModel parent, string text)
{
this._parent = parent;
this._isChecked = false;
this._isExpanded = true;
this._text = text;
this.Children = new ObservableCollection<TraitViewModel>();
}
private void SetIsChecked(bool? value, bool updateChildren, bool updateParent)
{
if (value == this._isChecked)
{
return;
}
this._isChecked = value;
if (updateChildren && value != null)
{
foreach (var child in this.Children)
{
child.SetIsChecked(value, updateChildren: true, updateParent: false);
}
}
if (updateParent && _parent != null)
{
_parent.VerifyCheckState();
}
this.RaisePropertyChanged(nameof(IsChecked));
}
private void VerifyCheckState()
{
bool? state = null;
var isFirst = true;
foreach (var child in this.Children)
{
if (isFirst)
{
state = child.IsChecked;
isFirst = false;
}
else if (state != child.IsChecked)
{
state = null;
break;
}
}
this.SetIsChecked(state, updateChildren: false, updateParent: true);
}
public void AddValues(IEnumerable<string> values)
{
foreach (var value in values)
{
var index = this.Children.BinarySearch(value, StringComparer.Ordinal.Compare, v => v.Text);
if (index < 0)
{
this.Children.Insert(~index, new TraitViewModel(this, value));
}
}
}
public TraitViewModel GetOrAdd(string text)
{
var index = this.Children.BinarySearch(text, StringComparer.Ordinal, vm => vm.Text);
if (index < 0)
{
var viewModel = new TraitViewModel(this, text);
this.Children.Insert(~index, viewModel);
return viewModel;
}
return this.Children[index];
}
public bool? IsChecked
{
get { return _isChecked; }
set { SetIsChecked(value, updateChildren: true, updateParent: true); }
}
public bool IsExpanded
{
get { return _isExpanded; }
set { Set(ref _isExpanded, value); }
}
public string Text
{
get { return _text; }
set { Set(ref _text, value); }
}
}
}
@@ -1,7 +1,7 @@
/*
In App.xaml:
<Application.Resources>
<vm:ViewModelLocator xmlns:vm="clr-namespace:xunit.runner.wpf"
<vm:ViewModelLocator xmlns:vm="clr-namespace:Xunit.Runner.Wpf"
x:Key="Locator" />
</Application.Resources>
@@ -12,11 +12,11 @@
See http://www.galasoft.ch/mvvm
*/
using CommonServiceLocator;
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
@@ -46,8 +46,13 @@ namespace xunit.runner.wpf.ViewModel
}
public MainViewModel Main
=> ServiceLocator.Current.GetInstance<MainViewModel>();
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public static void Cleanup()
{
// TODO Clear the ViewModels
@@ -0,0 +1,111 @@
#if NETSTANDARD
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Xunit.Sdk
{
class XunitWorkerThread : IDisposable
{
readonly ManualResetEvent finished = new ManualResetEvent(false);
static readonly TaskFactory taskFactory = new TaskFactory();
public XunitWorkerThread(Action threadProc)
{
QueueUserWorkItem(threadProc, finished);
}
public void Dispose()
{
finished.Dispose();
}
public void Join()
{
finished.WaitOne();
}
public static void QueueUserWorkItem(Action backgroundTask, EventWaitHandle finished = null)
{
taskFactory.StartNew(_ =>
{
var state = (State)_;
try
{
state.BackgroundTask();
}
finally
{
if (state.Finished != null)
state.Finished.Set();
}
},
new State { BackgroundTask = backgroundTask, Finished = finished },
CancellationToken.None,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
}
class State
{
public Action BackgroundTask;
public EventWaitHandle Finished;
}
}
}
#else
using System;
using System.Threading;
namespace Xunit.Sdk
{
class XunitWorkerThread : IDisposable
{
readonly Thread thread;
public XunitWorkerThread(Action threadProc)
{
thread = new Thread(s => ((Action)s)()) { IsBackground = true };
thread.Start(threadProc);
}
public void Dispose() { }
public void Join()
{
if (thread != Thread.CurrentThread)
thread.Join();
}
public static void QueueUserWorkItem(Action backgroundTask, EventWaitHandle finished = null)
{
ThreadPool.QueueUserWorkItem(_ =>
{
var state = (State)_;
try
{
state.BackgroundTask();
}
finally
{
if (state.Finished != null)
state.Finished.Set();
}
},
new State { BackgroundTask = backgroundTask, Finished = finished });
}
class State
{
public Action BackgroundTask;
public EventWaitHandle Finished;
}
}
}
#endif
@@ -0,0 +1,78 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<PackageId>speckle.xunit.runner.wpf</PackageId>
<Authors>Speckle</Authors>
<Company>Speckle</Company>
<Product>speckle.xunit.runner.wpf</Product>
<Description>WPF xUnit runner</Description>
<PackageProjectUrl>https://github.com/Speckle-Next/speckle.xunit.runner.wpf</PackageProjectUrl>
<RepositoryUrl>https://github.com/Speckle-Next/speckle.xunit.runner.wpf</RepositoryUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageTags>xunit wpf runner speckle</PackageTags>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<OutputType>Library</OutputType>
<PackageIcon></PackageIcon>
<PackageIconUrl>https://avatars2.githubusercontent.com/u/2092016</PackageIconUrl>
<PackageIconUrl>https://avatars2.githubusercontent.com/u/2092016</PackageIconUrl>
<Version>1.0.3</Version>
</PropertyGroup>
<ItemGroup>
<None Remove="Artwork\Application.ico" />
<None Remove="Artwork\Failed_large.png" />
<None Remove="Artwork\Failed_small.png" />
<None Remove="Artwork\Passed_large.png" />
<None Remove="Artwork\Passed_small.png" />
<None Remove="Artwork\Running_large.png" />
<None Remove="Artwork\Running_small.png" />
<None Remove="Artwork\Skipped_large.png" />
<None Remove="Artwork\Skipped_small.png" />
<None Remove="MainWindow.xaml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MvvmLightLibs" Version="5.4.1.1" />
<PackageReference Include="System.Collections.Immutable" Version="1.7.1" />
<PackageReference Include="WindowsAPICodePack-Shell" Version="1.1.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.utility" Version="2.4.1" />
</ItemGroup>
<ItemGroup>
<Page Include="MainWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System.Xaml" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Resource Include="Artwork\Application.ico" />
<Resource Include="Artwork\Failed_large.png" />
<Resource Include="Artwork\Failed_small.png" />
<Resource Include="Artwork\Passed_large.png" />
<Resource Include="Artwork\Passed_small.png" />
<Resource Include="Artwork\Running_large.png" />
<Resource Include="Artwork\Running_small.png" />
<Resource Include="Artwork\Skipped_large.png" />
<Resource Include="Artwork\Skipped_small.png" />
</ItemGroup>
<ItemGroup>
<Compile Update="MainWindow.xaml.cs">
<SubType>Code</SubType>
<DependentUpon>MainWindow.xaml</DependentUpon>
</Compile>
</ItemGroup>
</Project>
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<package >
<metadata>
<id>$id$</id>
<version>$version$</version>
<title>$title$</title>
<authors>Speckle</authors>
<owners>$author$</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">Apache-2.0</license>
<projectUrl>https://github.com/Speckle-Next/speckle.xunit.runner.wpf</projectUrl>
<iconUrl>https://avatars2.githubusercontent.com/u/2092016</iconUrl>
<description>WPF xUnit runner</description>
<releaseNotes></releaseNotes>
<copyright>Copyright 2020</copyright>
<tags>xunit wpf speckle runner</tags>
<dependencies>
<dependency id="MvvmLightLibs" version="5.4.1.1" />
<dependency id="System.Collections.Immutable" version="1.7.1" />
<dependency id="WindowsAPICodePack-Shell" version="1.1.1" />
<dependency id="xunit" version="2.4.1" />
<dependency id="xunit.runner.utility" version="2.4.1" />
</dependencies>
</metadata>
</package>
-90
View File
@@ -1,90 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace xunit.runner.data
{
public sealed class ClientReader : IDisposable
{
private readonly BinaryReader _reader;
private bool _closed;
private Exception _exception;
public bool IsConnected => !_closed;
public ClientReader(Stream stream)
{
_reader = new BinaryReader(stream, Constants.Encoding, leaveOpen: true);
}
public void Close()
{
if (_closed)
{
return;
}
_closed = true;
_reader.Dispose();
}
public TestDataKind ReadKind()
{
return (TestDataKind)ReadCore(() => _reader.ReadInt32());
}
public TestCaseData ReadTestCaseData()
{
return ReadCore(() => TestCaseData.ReadFrom(_reader));
}
public TestResultData ReadTestResultData()
{
return ReadCore(() => TestResultData.ReadFrom(_reader));
}
public string ReadString()
{
return ReadCore(() => _reader.ReadString());
}
private T ReadCore<T>(Func<T> func)
{
if (_closed)
{
if (_exception == null)
{
throw new Exception("Connection is closed");
}
throw new Exception("Connection is closed", _exception);
}
try
{
return func();
}
catch (Exception ex)
{
// Happens during rude shut down of the client. Log to the screen and close
// the connection.
Console.WriteLine(ex.Message);
_exception = ex;
Close();
throw;
}
}
#region IDisposable
void IDisposable.Dispose()
{
Close();
}
#endregion
}
}
-82
View File
@@ -1,82 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace xunit.runner.data
{
public sealed class ClientWriter : IDisposable
{
private readonly BinaryWriter _writer;
private bool _closed;
public bool IsConnected => !_closed;
public ClientWriter(Stream stream)
{
_writer = new BinaryWriter(stream, Constants.Encoding, leaveOpen: true);
}
public void Close()
{
if (_closed)
{
return;
}
_closed = true;
_writer.Dispose();
}
public void Write(TestDataKind kind)
{
WriteCore(() => _writer.Write((int)kind));
}
public void Write(TestCaseData testCaseData)
{
WriteCore(() => testCaseData.WriteTo(_writer));
}
public void Write(TestResultData testCaseResultData)
{
WriteCore(() => testCaseResultData.WriteTo(_writer));
}
public void Write(string str)
{
WriteCore(() => _writer.Write(str));
}
private void WriteCore(Action action)
{
if (_closed)
{
return;
}
try
{
action();
}
catch (Exception ex)
{
// Happens during rude shut down of the client. Log to the screen and close
// the connection.
Console.WriteLine(ex.Message);
Close();
}
}
#region IDisposable
void IDisposable.Dispose()
{
Close();
}
#endregion
}
}
-16
View File
@@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace xunit.runner.data
{
public static class Constants
{
public const string ActionDiscover = "discover";
public const string ActionRunAll = "runall";
public const string ActionRunSpecific = "runspecific";
public static readonly Encoding Encoding = Encoding.UTF8;
}
}
@@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("xunit.runner.data")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("xunit.runner.data")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("a1f579f4-443e-4f64-bc55-998ab86ff293")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
-45
View File
@@ -1,45 +0,0 @@
using System;
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
{
public sealed class TestCaseData
{
public string SerializedForm { get; set; }
public string DisplayName { 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)
{
SerializedForm = serializedForm;
DisplayName = displayName;
AssemblyPath = assemblyPath;
TraitMap = traitMap;
}
public static TestCaseData ReadFrom(BinaryReader reader)
{
var formatter = new BinaryFormatter();
var serializedForm = reader.ReadString();
var displayName = reader.ReadString();
var assemblyPath = reader.ReadString();
var traitMap = (Dictionary<string, List<string>>)formatter.Deserialize(reader.BaseStream);
return new TestCaseData(serializedForm, displayName, assemblyPath, traitMap);
}
public void WriteTo(BinaryWriter writer)
{
var formatter = new BinaryFormatter();
writer.Write(SerializedForm);
writer.Write(DisplayName);
writer.Write(AssemblyPath);
formatter.Serialize(writer.BaseStream, TraitMap);
}
}
}
-14
View File
@@ -1,14 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace xunit.runner.data
{
public enum TestDataKind
{
Value = 1,
EndOfData = 2
}
}
-51
View File
@@ -1,51 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace xunit.runner.data
{
/// <summary>
/// Note: More severe states are higher numbers.
/// <see cref="MainViewModel.TestRunVisitor_TestFinished(object, TestStateEventArgs)"/>
/// </summary>
public enum TestState
{
All = 0,
NotRun,
Passed,
Skipped,
Failed,
}
public sealed class TestResultData
{
public string TestCaseDisplayName { get; set; }
public TestState TestState { get; set; }
public string Output { get; set; }
public TestResultData(string displayName, TestState state, string output = "")
{
TestCaseDisplayName = displayName;
TestState = state;
Output = output;
}
public static TestResultData ReadFrom(BinaryReader reader)
{
var displayName = reader.ReadString();
var state = (TestState)reader.ReadInt32();
var output = reader.ReadString();
return new TestResultData(displayName, state, output);
}
public void WriteTo(BinaryWriter writer)
{
writer.Write(TestCaseDisplayName);
writer.Write((int)TestState);
writer.Write(Output);
}
}
}
@@ -1,59 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A1F579F4-443E-4F64-BC55-998AB86FF293}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>xunit.runner.data</RootNamespace>
<AssemblyName>xunit.runner.data</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ClientReader.cs" />
<Compile Include="ClientWriter.cs" />
<Compile Include="Constants.cs" />
<Compile Include="TestCaseData.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TestDataKind.cs" />
<Compile Include="TestResultData.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
-97
View File
@@ -1,97 +0,0 @@
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
{
internal abstract class Connection : IDisposable
{
private bool _closed;
internal abstract Stream Stream { get; }
internal abstract void WaitForClientConnect();
internal abstract void WaitForClientDone();
protected virtual void DisposeCore()
{
}
internal void Dispose()
{
if (_closed)
{
return;
}
_closed = true;
DisposeCore();
}
#region IDisposable
void IDisposable.Dispose()
{
Dispose();
}
#endregion
}
internal sealed class NamedPipeConnection : Connection
{
private readonly NamedPipeServerStream _stream;
internal override Stream Stream => _stream;
internal NamedPipeConnection(string pipeName)
{
_stream = new NamedPipeServerStream(pipeName, PipeDirection.InOut);
}
protected override void DisposeCore()
{
_stream.Close();
}
internal override void WaitForClientConnect()
{
_stream.WaitForConnection();
}
internal override void WaitForClientDone()
{
try
{
_stream.ReadByte();
}
catch (Exception ex)
{
// If there is an error reading from the client then clearly they are done
Console.WriteLine($"Error reading client done byte {ex.Message}");
}
}
}
internal sealed class TestConnection : Connection
{
private readonly MemoryStream _stream = new MemoryStream();
internal override Stream Stream => _stream;
internal override void WaitForClientConnect()
{
}
internal override void WaitForClientDone()
{
}
}
}
-62
View File
@@ -1,62 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
using xunit.runner.data;
namespace xunit.runner.worker
{
internal sealed class DiscoverUtil
{
private sealed class Impl : TestMessageVisitor<IDiscoveryCompleteMessage>
{
private readonly ITestFrameworkDiscoverer _discoverer;
private readonly ClientWriter _writer;
private readonly Dictionary<string, List<string>> _traitMap;
internal Impl(ITestFrameworkDiscoverer discoverer, ClientWriter writer)
{
_discoverer = discoverer;
_writer = writer;
_traitMap = new Dictionary<string, List<string>>(StringComparer.Ordinal);
}
protected override bool Visit(ITestCaseDiscoveryMessage testCaseDiscovered)
{
var testCase = testCaseDiscovered.TestCase;
var testCaseData = new TestCaseData(
_discoverer.Serialize(testCase),
testCase.DisplayName,
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)
{
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);
}
}
}
}
-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);
}
}
}
-106
View File
@@ -1,106 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using xunit.runner.data;
namespace xunit.runner.worker
{
public static class Program
{
private const int ExitSuccess = 0;
private const int ExitError = 1;
public static int Main(string[] args)
{
if (args.Length < 3)
{
Usage();
return ExitError;
}
string pipeName = args[0];
string action = args[1];
string argument = args[2];
try
{
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);
return ExitError;
}
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");
}
}
}
@@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("xunit.runner.worker")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("xunit.runner.worker")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("9df97a2b-0eb5-4b12-9f81-69dfac979814")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
-156
View File
@@ -1,156 +0,0 @@
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.Abstractions;
namespace xunit.runner.worker
{
internal sealed class RunUtil
{
private sealed class TestRunVisitor : TestMessageVisitor<ITestAssemblyFinished>
{
private readonly ClientWriter _writer;
public TestRunVisitor(ClientWriter writer)
{
_writer = writer;
}
private void Process(string displayName, TestState state, string output = "")
{
Console.WriteLine($"{state} - {displayName}");
var result = new TestResultData(displayName, state, output);
_writer.Write(TestDataKind.Value);
_writer.Write(result);
}
protected override bool Visit(ITestFailed testFailed)
{
var displayName = testFailed.TestCase.DisplayName;
var builder = new StringBuilder();
builder.AppendLine($"{displayName} FAILED:");
for (int i = 0; i < testFailed.ExceptionTypes.Length; i++)
{
builder.AppendLine($"\tException type: '{testFailed.ExceptionTypes[i]}', number: '{i}', parent: '{testFailed.ExceptionParentIndices[i]}'");
builder.AppendLine($"\tException message:");
builder.AppendLine(testFailed.Messages[i]);
builder.AppendLine($"\tException stacktrace");
builder.AppendLine(testFailed.StackTraces[i]);
}
builder.AppendLine();
Process(testFailed.TestCase.DisplayName, TestState.Failed, builder.ToString());
return _writer.IsConnected;
}
protected override bool Visit(ITestPassed testPassed)
{
Process(testPassed.TestCase.DisplayName, TestState.Passed);
return _writer.IsConnected;
}
protected override bool Visit(ITestSkipped testSkipped)
{
Process(testSkipped.TestCase.DisplayName, TestState.Skipped);
return _writer.IsConnected;
}
}
private sealed class TestCaseDiscoverer : TestMessageVisitor<IDiscoveryCompleteMessage>
{
private readonly HashSet<string> _testCaseDisplayNameSet;
private readonly List<ITestCase> _testCaseList;
internal TestCaseDiscoverer(HashSet<string> testCaseDisplayNameSet, List<ITestCase> testCaseList)
{
_testCaseDisplayNameSet = testCaseDisplayNameSet;
_testCaseList = testCaseList;
}
protected override bool Visit(ITestCaseDiscoveryMessage testCaseDiscovered)
{
var testCase = testCaseDiscovered.TestCase;
if (_testCaseDisplayNameSet.Contains(testCase.DisplayName))
{
_testCaseList.Add(testCaseDiscovered.TestCase);
}
return true;
}
}
/// <summary>
/// Read out the set of test case display names to run.
/// </summary>
private static List<string> ReadTestCaseDisplayNames(Stream stream)
{
using (var reader = new ClientReader(stream))
{
var list = new List<string>();
while (reader.ReadKind() == TestDataKind.Value)
{
list.Add(reader.ReadString());
}
return list;
}
}
private static List<ITestCase> GetTestCaseList(XunitFrontController xunit, Stream stream)
{
var testCaseDisplayNames = ReadTestCaseDisplayNames(stream);
var testCaseDisplayNameSet = new HashSet<string>(testCaseDisplayNames, StringComparer.Ordinal);
var testCaseList = new List<ITestCase>();
using (var discoverer = new TestCaseDiscoverer(testCaseDisplayNameSet, testCaseList))
{
xunit.Find(includeSourceInformation: false, messageSink: discoverer, discoveryOptions: TestFrameworkOptions.ForDiscovery());
discoverer.Finished.WaitOne();
}
return testCaseList;
}
internal static void RunAll(string assemblyPath, 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);
}
}
internal static void RunSpecific(string assemblyPath, 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);
}
}
}
}
-5
View File
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
<package id="xunit.runner.utility" version="2.1.0-beta4-build3109" targetFramework="net452" />
</packages>
@@ -1,77 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{9DF97A2B-0EB5-4B12-9F81-69DFAC979814}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>xunit.runner.worker</RootNamespace>
<AssemblyName>xunit.runner.worker</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="xunit.abstractions">
<HintPath>..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
</Reference>
<Reference Include="xunit.runner.utility.desktop">
<HintPath>..\packages\xunit.runner.utility.2.1.0-beta4-build3109\lib\net35\xunit.runner.utility.desktop.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Connection.cs" />
<Compile Include="DiscoverUtil.cs" />
<Compile Include="MessageVisitor.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RunUtil.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\xunit.runner.data\xunit.runner.data.csproj">
<Project>{a1f579f4-443e-4f64-bc55-998ab86ff293}</Project>
<Name>xunit.runner.data</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
-40
View File
@@ -1,40 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.23107.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xunit.runner.wpf", "xunit.runner.wpf\xunit.runner.wpf.csproj", "{34FB519C-FB49-4B31-ACA2-7F7879311BCF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleTestAssembly", "SampleTestAssembly\SampleTestAssembly.csproj", "{BDAFB5DD-FFB3-4A94-A312-DFB080010846}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xunit.runner.worker", "xunit.runner.worker\xunit.runner.worker.csproj", "{9DF97A2B-0EB5-4B12-9F81-69DFAC979814}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xunit.runner.data", "xunit.runner.data\xunit.runner.data.csproj", "{A1F579F4-443E-4F64-BC55-998AB86FF293}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{34FB519C-FB49-4B31-ACA2-7F7879311BCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{34FB519C-FB49-4B31-ACA2-7F7879311BCF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{34FB519C-FB49-4B31-ACA2-7F7879311BCF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{34FB519C-FB49-4B31-ACA2-7F7879311BCF}.Release|Any CPU.Build.0 = Release|Any CPU
{BDAFB5DD-FFB3-4A94-A312-DFB080010846}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BDAFB5DD-FFB3-4A94-A312-DFB080010846}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BDAFB5DD-FFB3-4A94-A312-DFB080010846}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BDAFB5DD-FFB3-4A94-A312-DFB080010846}.Release|Any CPU.Build.0 = Release|Any CPU
{9DF97A2B-0EB5-4B12-9F81-69DFAC979814}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9DF97A2B-0EB5-4B12-9F81-69DFAC979814}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9DF97A2B-0EB5-4B12-9F81-69DFAC979814}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9DF97A2B-0EB5-4B12-9F81-69DFAC979814}.Release|Any CPU.Build.0 = Release|Any CPU
{A1F579F4-443E-4F64-BC55-998AB86FF293}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1F579F4-443E-4F64-BC55-998AB86FF293}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1F579F4-443E-4F64-BC55-998AB86FF293}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1F579F4-443E-4F64-BC55-998AB86FF293}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
-14
View File
@@ -1,14 +0,0 @@
<Application x:Class="xunit.runner.wpf.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:xunit.runner.wpf"
StartupUri="MainWindow.xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d1p1:Ignorable="d"
xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006">
<Application.Resources>
<vm:ViewModelLocator x:Key="Locator"
d:IsDataSource="True"
xmlns:vm="clr-namespace:xunit.runner.wpf.ViewModel" />
</Application.Resources>
</Application>
-17
View File
@@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace xunit.runner.wpf
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

@@ -1,78 +0,0 @@
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;
namespace xunit.runner.wpf.Converters
{
public class TestStateConverter : IValueConverter
{
private static ImageSource passedSource;
private static ImageSource failedSource;
private static ImageSource skippedSource;
static TestStateConverter()
{
passedSource = LoadResourceImage("Passed.ico");
failedSource = LoadResourceImage("Failed.ico");
skippedSource = LoadResourceImage("Skipped.ico");
}
private static BitmapImage LoadResourceImage(string resourceName)
{
var image = new BitmapImage();
image.BeginInit();
image.UriSource = new Uri("pack://application:,,,/xunit.runner.wpf;component/Artwork/" + resourceName);
image.EndInit();
return image;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var state = (TestState)value;
if (targetType == typeof(Brush))
{
switch (state)
{
case TestState.Failed:
return Brushes.Red;
case TestState.Skipped:
return Brushes.Yellow;
case TestState.Passed:
return Brushes.Green;
default:
return Brushes.Gray;
}
}
else if (targetType == typeof(ImageSource))
{
switch (state)
{
case TestState.Failed:
return failedSource;
case TestState.Skipped:
return skippedSource;
case TestState.Passed:
return passedSource;
default:
return null;
}
}
else
{
throw new NotSupportedException();
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
-28
View File
@@ -1,28 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace xunit.runner.wpf
{
public static class Extensions
{
public static void AddRange<TList, TEnumerable>(this IList<TList> list, IEnumerable<TEnumerable> items) where TEnumerable : TList
{
foreach (var i in items)
{
list.Add(i);
}
}
public static void AddRange<TList, TEnumerable>(this ObservableCollection<TList> list, IEnumerable<TEnumerable> items) where TEnumerable : TList
{
foreach (var i in items)
{
list.Add(i);
}
}
}
}
-32
View File
@@ -1,32 +0,0 @@
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;
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));
/// <summary>
/// Begin a run of all unit tests for the given assembly.
/// </summary>
Task RunAll(string assemblyPath, Action<TestResultData> callback, 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));
}
}
@@ -1,182 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using xunit.runner.data;
using xunit.runner.wpf.ViewModel;
namespace xunit.runner.wpf.Impl
{
internal partial class RemoteTestUtil
{
private sealed class BackgroundWriter<T>
{
private readonly ClientWriter _writer;
private readonly ImmutableArray<T> _data;
private readonly Action<ClientWriter, T> _writeValue;
private readonly CancellationToken _cancellationToken;
internal BackgroundWriter(ClientWriter writer, ImmutableArray<T> data, Action<ClientWriter, T> writeValue, CancellationToken cancellationToken)
{
_writer = writer;
_writeValue = writeValue;
_data = data;
_cancellationToken = cancellationToken;
}
internal Task WriteAsync()
{
return Task.Run(() => GoOnBackground(), _cancellationToken);
}
private void GoOnBackground()
{
foreach (var item in _data)
{
if (_cancellationToken.IsCancellationRequested)
{
break;
}
_writer.Write(TestDataKind.Value);
_writeValue(_writer, item);
}
_writer.Write(TestDataKind.EndOfData);
}
}
private sealed class BackgroundReader<T> where T : class
{
private readonly ConcurrentQueue<T> _queue;
private readonly ClientReader _reader;
private readonly Func<ClientReader, T> _readValue;
private readonly CancellationToken _cancellationToken;
internal ClientReader Reader => _reader;
internal BackgroundReader(ConcurrentQueue<T> queue, ClientReader reader, Func<ClientReader, T> readValue, CancellationToken cancellationToken)
{
_queue = queue;
_reader = reader;
_readValue = readValue;
_cancellationToken = cancellationToken;
}
internal Task ReadAsync()
{
return Task.Run(() => GoOnBackground(), _cancellationToken);
}
/// <summary>
/// This will be called on a background thread to read the results of the test from the
/// named pipe client stream.
/// </summary>
/// <returns></returns>
private void GoOnBackground()
{
while (!_cancellationToken.IsCancellationRequested)
{
try
{
var kind = _reader.ReadKind();
if (kind != TestDataKind.Value)
{
break;
}
var value = _readValue(_reader);
_queue.Enqueue(value);
}
catch
{
// TODO: Happens when the connection unexpectedly closes on us. Need to surface this
// to the user.
break;
}
}
// Signal we are done
_queue.Enqueue(null);
}
}
private sealed class BackgroundProducer<T> where T : class
{
private const int MaxResultPerTick = 1000;
private readonly Connection _connection;
private readonly ConcurrentQueue<T> _queue;
private readonly DispatcherTimer _timer;
private readonly Action<T> _callback;
private readonly int _maxPerTick;
private readonly TaskCompletionSource<bool> _taskCompletionSource;
internal Task Task => _taskCompletionSource.Task;
internal BackgroundProducer(
Connection connection,
Dispatcher dispatcher,
ConcurrentQueue<T> queue,
Action<T> callback,
int maxResultPerTick = MaxResultPerTick,
TimeSpan? interval = null)
{
_connection = connection;
_queue = queue;
_maxPerTick = maxResultPerTick;
_callback = callback;
_timer = new DispatcherTimer(
interval ?? TimeSpan.FromMilliseconds(100),
DispatcherPriority.Normal,
OnTimerTick,
dispatcher);
_taskCompletionSource = new TaskCompletionSource<bool>();
}
private void OnTimerTick(object sender, EventArgs e)
{
var i = 0;
var list = new List<T>();
var isDone = false;
T value;
while (i < _maxPerTick && _queue.TryDequeue(out value))
{
if (value == null)
{
isDone = true;
break;
}
list.Add(value);
}
foreach (var cur in list)
{
_callback(cur);
}
if (isDone)
{
try
{
_timer.Stop();
_connection.Dispose();
}
finally
{
_taskCompletionSource.SetResult(true);
}
}
}
}
}
}
@@ -1,71 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using xunit.runner.data;
using xunit.runner.wpf.ViewModel;
namespace xunit.runner.wpf.Impl
{
internal sealed partial class RemoteTestUtil : ITestUtil
{
private sealed class Connection : IDisposable
{
private NamedPipeClientStream _stream;
private Process _process;
private ClientReader _reader;
internal NamedPipeClientStream Stream => _stream;
internal ClientReader Reader => _reader;
internal Connection(NamedPipeClientStream stream, Process process)
{
_stream = stream;
_process = process;
_reader = new ClientReader(stream);
}
internal void Dispose()
{
if (_process != 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.
}
}
}
void IDisposable.Dispose()
{
Dispose();
}
}
}
}
-120
View File
@@ -1,120 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using xunit.runner.data;
using xunit.runner.wpf.ViewModel;
namespace xunit.runner.wpf.Impl
{
internal sealed partial class RemoteTestUtil : ITestUtil
{
private readonly Dispatcher _dispatcher;
internal RemoteTestUtil(Dispatcher dispatcher)
{
_dispatcher = dispatcher;
}
private static Connection StartWorkerProcess(string action, string argument)
{
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 process = Process.Start(processStartInfo);
try
{
var stream = new NamedPipeClientStream(pipeName);
stream.Connect();
return new Connection(stream, process);
}
catch
{
process.Kill();
throw;
}
}
private Task Discover(string assemblyPath, Action<TestCaseData> callback, CancellationToken cancellationToken)
{
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
{
if (!testCaseDisplayNames.IsDefaultOrEmpty)
{
var backgroundWriter = new BackgroundWriter<string>(new ClientWriter(connection.Stream), testCaseDisplayNames, (w, s) => w.Write(s), cancellationToken);
await backgroundWriter.WriteAsync();
}
var backgroundReader = new BackgroundReader<TestResultData>(queue, new ClientReader(connection.Stream), r => r.ReadTestResultData(), cancellationToken);
await backgroundReader.ReadAsync();
}
catch (Exception ex)
{
Debug.Fail(ex.Message);
// Signal data completed
queue.Enqueue(null);
}
}
#region ITestUtil
Task ITestUtil.Discover(string assemblyPath, Action<TestCaseData> callback, CancellationToken cancellationToken)
{
return Discover(assemblyPath, callback, cancellationToken);
}
Task ITestUtil.RunAll(string assemblyPath, Action<TestResultData> callback, CancellationToken cancellationToken)
{
return RunCore(Constants.ActionRunAll, assemblyPath, ImmutableArray<string>.Empty, callback, cancellationToken);
}
Task ITestUtil.RunSpecific(string assemblyPath, ImmutableArray<string> testCaseDisplayNames, Action<TestResultData> callback, CancellationToken cancellationToken)
{
return RunCore(Constants.ActionRunSpecific, assemblyPath, testCaseDisplayNames, callback, cancellationToken);
}
#endregion
}
}
-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));
}
}
-265
View File
@@ -1,265 +0,0 @@
<Window x:Class="xunit.runner.wpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns: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">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<cmd:EventToCommand Command="{Binding WindowLoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Window.Resources>
<converters:TestStateConverter x:Key="TestStateConverter" />
</Window.Resources>
<Grid local:CommandBindings.Registration="{Binding CommandBindings}">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="_File">
<MenuItem Header="E_xit"
Command="{Binding ExitCommand}" />
</MenuItem>
<MenuItem Header="_Assembly">
<MenuItem Header="_Open"
Command="ApplicationCommands.Open" />
<MenuItem Header="R_ecent" />
<Separator />
<MenuItem Header="_Unload" />
<MenuItem Header="_Reload" />
</MenuItem>
<MenuItem Header="_Project">
<MenuItem Header="_Open" />
<MenuItem Header="_Recent" />
<Separator />
<MenuItem Header="_Close" />
<Separator />
<MenuItem Header="_Save" />
<MenuItem Header="Save _As..." />
</MenuItem>
</Menu>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<GroupBox Header="Refinements"
Margin="3"
Grid.Column="0"
Grid.Row="0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<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">
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:TestAssemblyViewModel">
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Reload" Command="{Binding AssemblyReloadCommand}" />
<MenuItem Header="Reload All" Command="{Binding AssemblyReloadAllCommand}" />
<Separator />
<MenuItem Header="Remove" Command="{Binding AssemblyRemoveCommand}" />
<MenuItem Header="Remove All" Command="{Binding AssemblyRemoveAllCommand}" />
</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}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Clear" Command="{Binding TraitsClearCommand}" />
</ContextMenu>
</ListBox.ContextMenu>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding TraitSelectionChangedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
</Grid>
</GroupBox>
<Grid Grid.Column="0"
Grid.Row="1">
<Grid.ColumnDefinitions>
<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" />
</Grid>
<Grid Grid.Column="1"
Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<GroupBox Header="{Binding MethodsCaption}"
Margin="3"
Grid.Row="0">
<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">
<Image Source="Artwork\Passed.ico"
Height="16" />
<TextBlock Margin="4,0,0,0"
Text="{Binding TestsPassed}" />
</StackPanel>
</ToggleButton>
<ToggleButton IsChecked="{Binding IncludeFailedTests}"
Margin="3"
Grid.Column="1">
<StackPanel Orientation="Horizontal">
<Image Source="Artwork\Failed.ico"
Height="16" />
<TextBlock Margin="4,0,0,0"
Text="{Binding TestsFailed}" />
</StackPanel>
</ToggleButton>
<ToggleButton IsChecked="{Binding IncludeSkippedTests}"
Margin="3"
Grid.Column="2">
<StackPanel Orientation="Horizontal">
<Image Source="Artwork\Skipped.ico"
Height="16" />
<TextBlock Margin="4,0,0,0"
Text="{Binding TestsSkipped}" />
</StackPanel>
</ToggleButton>
</Grid>
<ListBox ItemsSource="{Binding TestCases}"
SelectionMode="Extended"
Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:TestCaseViewModel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<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" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</GroupBox>
<GridSplitter Grid.Row="1" />
<GroupBox Header="Output"
Margin="3"
Grid.Row="2">
<TextBox IsReadOnly="True"
Text="{Binding Output}"/>
</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" />
</Grid>
<StackPanel Grid.Row="2">
<Border BorderBrush="LightGray"
BorderThickness="1"
Margin="3" />
<StatusBar>
<StatusBarItem>
<Label />
</StatusBarItem>
</StatusBar>
</StackPanel>
</Grid>
</Window>
-32
View File
@@ -1,32 +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.Navigation;
using System.Windows.Shapes;
namespace xunit.runner.wpf
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
Instance = this;
InitializeComponent();
}
public static Window Instance { get; private set; }
}
}
-71
View File
@@ -1,71 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace xunit.runner.wpf.Properties
{
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ((resourceMan == null))
{
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("xunit.runner.wpf.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
return resourceCulture;
}
set
{
resourceCulture = value;
}
}
}
}
-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;
}
}
}
}
-600
View File
@@ -1,600 +0,0 @@
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.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.Threading;
namespace xunit.runner.wpf.ViewModel
{
public class MainViewModel : ViewModelBase
{
private readonly ITestUtil testUtil;
private readonly ObservableCollection<TestCaseViewModel> allTestCases = new ObservableCollection<TestCaseViewModel>();
private readonly TraitCollectionView traitCollectionView = new TraitCollectionView();
private CancellationTokenSource filterCancellationTokenSource = new CancellationTokenSource();
private CancellationTokenSource cancellationTokenSource;
private bool isBusy;
private SearchQuery searchQuery = new SearchQuery();
public MainViewModel()
{
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>(
allTestCases, TestCaseMatches, searchQuery, TestComparer.Instance);
this.TestCases.CollectionChanged += TestCases_CollectionChanged;
this.WindowLoadedCommand = new RelayCommand(OnExecuteWindowLoaded);
this.RunCommand = new RelayCommand(OnExecuteRun, CanExecuteRun);
this.CancelCommand = new RelayCommand(OnExecuteCancel, CanExecuteCancel);
this.TraitSelectionChangedCommand = new RelayCommand(OnExecuteTraitSelectionChanged);
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);
}
private static bool TestCaseMatches(TestCaseViewModel testCase, SearchQuery searchQuery)
{
if (!testCase.DisplayName.Contains(searchQuery.SearchString))
{
return false;
}
if (searchQuery.TraitSet.Count > 0)
{
var anyMatch = false;
foreach (var cur in testCase.Traits)
{
if (searchQuery.TraitSet.Contains(cur))
{
anyMatch = true;
break;
}
}
if (!anyMatch)
{
return false;
}
}
switch (testCase.State)
{
case TestState.Passed:
return searchQuery.IncludePassedTests;
case TestState.Skipped:
return searchQuery.IncludeSkippedTests;
case TestState.Failed:
return searchQuery.IncludeFailedTests;
case TestState.NotRun:
return true;
default:
Debug.Assert(false, "What state is this test case in?");
return true;
}
}
private void TestCases_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
MethodsCaption = $"Methods ({TestCases.Count})";
MaximumProgress = TestCases.Count;
}
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; }
public CommandBindingCollection CommandBindings { get; }
public List<TestAssemblyViewModel> SelectedAssemblies
{
get { return Assemblies.Where(x => x.IsSelected).ToList(); }
}
private string methodsCaption;
public string MethodsCaption
{
get { return methodsCaption; }
private set { Set(ref methodsCaption, value); }
}
private int testsCompleted = 0;
public int TestsCompleted
{
get { return testsCompleted; }
set { Set(ref testsCompleted, value); }
}
private int testsPassed = 0;
public int TestsPassed
{
get { return testsPassed; }
set { Set(ref testsPassed, value); }
}
private int testsFailed = 0;
public int TestsFailed
{
get { return testsFailed; }
set { Set(ref testsFailed, value); }
}
private int testsSkipped = 0;
public int TestsSkipped
{
get { return testsSkipped; }
set { Set(ref testsSkipped, value); }
}
private int maximumProgress = int.MaxValue;
public int MaximumProgress
{
get { return maximumProgress; }
set { Set(ref maximumProgress, value); }
}
private TestState currentRunState;
public TestState CurrentRunState
{
get { return currentRunState; }
set { Set(ref currentRunState, value); }
}
private string output = string.Empty;
public string Output
{
get { return output; }
set { Set(ref output, value); }
}
public string FilterString
{
get { return searchQuery.SearchString; }
set
{
if (Set(ref searchQuery.SearchString, value))
{
FilterAfterDelay();
}
}
}
private void FilterAfterDelay()
{
filterCancellationTokenSource.Cancel();
filterCancellationTokenSource = new CancellationTokenSource();
var token = filterCancellationTokenSource.Token;
Task
.Delay(TimeSpan.FromMilliseconds(500), token)
.ContinueWith(
x =>
{
TestCases.FilterArgument = searchQuery;
},
token,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
}
private CommandBindingCollection CreateCommandBindings()
{
var openBinding = new CommandBinding(ApplicationCommands.Open, OnExecuteOpen);
CommandManager.RegisterClassCommandBinding(typeof(MainViewModel), openBinding);
return new CommandBindingCollection
{
openBinding,
};
}
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",
};
if (fileDialog.ShowDialog(Application.Current.MainWindow) != true)
{
return;
}
var fileName = fileDialog.FileName;
await AddAssemblies(new[] { new AssemblyAndConfigFile(fileName, configFileName: null) });
}
private async Task AddAssemblies(IEnumerable<AssemblyAndConfigFile> assemblies)
{
if (!assemblies.Any())
{
return;
}
var loadingDialog = new LoadingDialog { Owner = MainWindow.Instance };
try
{
await 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));
}
return taskList;
});
}
finally
{
loadingDialog.Close();
}
}
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)
{
var assemblyPath = assembly.FileName;
RemoveAssemblyTestCases(assemblyPath);
taskList.Add(this.testUtil.Discover(assemblyPath, OnTestDiscovered, cancellationTokenSource.Token));
}
return taskList;
});
RebuildTraits();
}
finally
{
loadingDialog.Close();
}
}
private void RemoveAssemblies(IEnumerable<TestAssemblyViewModel> assemblies)
{
foreach (var assembly in assemblies.ToList())
{
RemoveAssemblyTestCases(assembly.FileName);
Assemblies.Remove(assembly);
}
RebuildTraits();
}
private void RemoveAssemblyTestCases(string assemblyPath)
{
var i = 0;
while (i < this.allTestCases.Count)
{
if (this.allTestCases[i].AssemblyFileName == assemblyPath)
{
this.allTestCases.RemoveAt(i);
}
else
{
i++;
}
}
}
/// <summary>
/// Reloading an assembly could have changed the traits. There is no easy way
/// to selectively edit this list (traits can cross assembly boundaries). Just
/// do a full reload instead.
/// way to
/// </summary>
private void RebuildTraits()
{
this.traitCollectionView.Collection.Clear();
foreach (var testCase in this.allTestCases)
{
this.traitCollectionView.Add(testCase.Traits);
}
}
private bool IsBusy
{
get { return isBusy; }
set
{
isBusy = value;
RunCommand.RaiseCanExecuteChanged();
CancelCommand.RaiseCanExecuteChanged();
}
}
private static void OnExecuteExit()
{
Application.Current.Shutdown();
}
private async void OnExecuteWindowLoaded()
{
await AddAssemblies(ParseCommandLine(Environment.GetCommandLineArgs().Skip(1)));
}
private IEnumerable<AssemblyAndConfigFile> ParseCommandLine(IEnumerable<string> enumerable)
{
while (enumerable.Any())
{
var assemblyFileName = enumerable.First();
enumerable = enumerable.Skip(1);
var configFileName = (string)null;
if (IsConfigFile(enumerable.FirstOrDefault()))
{
configFileName = enumerable.First();
enumerable = enumerable.Skip(1);
}
yield return new AssemblyAndConfigFile(assemblyFileName, configFileName);
}
}
private bool IsConfigFile(string fileName)
=> (fileName?.EndsWith(".config", StringComparison.OrdinalIgnoreCase) ?? false) ||
(fileName?.EndsWith(".json", StringComparison.OrdinalIgnoreCase) ?? false);
private bool CanExecuteRun()
=> !IsBusy && TestCases.Any();
private async void OnExecuteRun()
{
await ExecuteTestSessionOperation(RunTests);
}
private List<Task> RunTests()
{
Debug.Assert(this.isBusy);
Debug.Assert(this.cancellationTokenSource != null);
TestsCompleted = 0;
TestsPassed = 0;
TestsFailed = 0;
TestsSkipped = 0;
CurrentRunState = TestState.NotRun;
Output = string.Empty;
foreach (var tc in TestCases)
{
tc.State = TestState.NotRun;
}
var runAll = TestCases.Count == this.allTestCases.Count;
var testSessionList = new List<Task>();
foreach (var assemblyPath in TestCases.Select(x => x.AssemblyFileName).Distinct())
{
Task task;
if (runAll)
{
task = this.testUtil.RunAll(assemblyPath, OnTestFinished, this.cancellationTokenSource.Token);
}
else
{
var testCaseDisplayNames = TestCases
.Where(x => x.AssemblyFileName == assemblyPath)
.Select(x => x.DisplayName)
.ToImmutableArray();
task = this.testUtil.RunSpecific(assemblyPath, testCaseDisplayNames, OnTestFinished, this.cancellationTokenSource.Token);
}
testSessionList.Add(task);
}
return testSessionList;
}
private async Task ExecuteTestSessionOperation(Func<List<Task>> operation)
{
Debug.Assert(!this.IsBusy);
Debug.Assert(this.cancellationTokenSource == null);
try
{
this.IsBusy = true;
this.cancellationTokenSource = new CancellationTokenSource();
var taskList = operation();
await Task.WhenAll(taskList);
}
catch (Exception ex)
{
this.cancellationTokenSource?.Cancel();
MessageBox.Show(Application.Current.MainWindow, ex.ToString());
}
finally
{
this.cancellationTokenSource = null;
this.IsBusy = false;
}
}
private void OnTestDiscovered(TestCaseData testCaseData)
{
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);
}
private void OnTestFinished(TestResultData testResultData)
{
var testCase = TestCases.Single(x => x.DisplayName == testResultData.TestCaseDisplayName);
testCase.State = testResultData.TestState;
TestsCompleted++;
switch (testResultData.TestState)
{
case TestState.Passed:
TestsPassed++;
break;
case TestState.Failed:
TestsFailed++;
Output = Output + testResultData.Output;
break;
case TestState.Skipped:
TestsSkipped++;
break;
}
if (testResultData.TestState > CurrentRunState)
{
CurrentRunState = testResultData.TestState;
}
}
private bool CanExecuteCancel()
{
return this.cancellationTokenSource != null && !this.cancellationTokenSource.IsCancellationRequested;
}
private void OnExecuteCancel()
{
Debug.Assert(CanExecuteCancel());
this.cancellationTokenSource.Cancel();
}
private void OnExecuteTraitSelectionChanged()
{
this.searchQuery.TraitSet = new HashSet<TraitViewModel>(
this.traitCollectionView.Collection.Where(x => x.IsSelected),
TraitViewModelComparer.Instance);
FilterAfterDelay();
}
private void OnExecuteTraitsClear()
{
foreach (var cur in this.traitCollectionView.Collection)
{
cur.IsSelected = false;
}
}
private bool CanExecuteAssemblyReload()
{
return SelectedAssemblies.Count > 0;
}
private async void OnExecuteAssemblyReload()
{
await ReloadAssemblies(SelectedAssemblies);
}
private async void OnExecuteAssemblyReloadAll()
{
await ReloadAssemblies(Assemblies);
}
private bool CanExecuteAssemblyRemove()
{
return SelectedAssemblies.Count > 0;
}
private void OnExecuteAssemblyRemove()
{
RemoveAssemblies(SelectedAssemblies);
}
private void OnExecuteAssemblyRemoveAll()
{
RemoveAssemblies(Assemblies.ToArray());
}
public bool IncludePassedTests
{
get { return searchQuery.IncludePassedTests; }
set
{
if (Set(ref searchQuery.IncludePassedTests, value))
{
FilterAfterDelay();
}
}
}
public bool IncludeFailedTests
{
get { return searchQuery.IncludeFailedTests; }
set
{
if (Set(ref searchQuery.IncludeFailedTests, value))
{
FilterAfterDelay();
}
}
}
public bool IncludeSkippedTests
{
get { return searchQuery.IncludeSkippedTests; }
set
{
if (Set(ref searchQuery.IncludeSkippedTests, value))
{
FilterAfterDelay();
}
}
}
}
public class TestComparer : IComparer<TestCaseViewModel>
{
public static TestComparer Instance { get; } = new TestComparer();
public int Compare(TestCaseViewModel x, TestCaseViewModel y)
=> StringComparer.Ordinal.Compare(x.DisplayName, y.DisplayName);
private TestComparer() { }
}
}
-17
View File
@@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace xunit.runner.wpf.ViewModel
{
public class SearchQuery
{
public bool IncludeFailedTests = true;
public bool IncludePassedTests = true;
public bool IncludeSkippedTests = true;
public string SearchString = string.Empty;
public HashSet<TraitViewModel> TraitSet = new HashSet<TraitViewModel>(TraitViewModelComparer.Instance);
}
}
@@ -1,39 +0,0 @@
using GalaSoft.MvvmLight;
using System;
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;
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 TestState State
{
get { return _state; }
set { Set(ref _state, value); }
}
public string AssemblyFileName { get; }
public string TestCase { get; }
public ImmutableArray<TraitViewModel> Traits { get; }
}
}
@@ -1,59 +0,0 @@
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
{
public sealed partial class TraitCollectionView
{
private readonly TraitViewModelComparer _comparer = TraitViewModelComparer.Instance;
private readonly ObservableCollection<TraitViewModel> _collection = new ObservableCollection<TraitViewModel>();
public ObservableCollection<TraitViewModel> Collection => _collection;
public TraitCollectionView()
{
}
public void Add(ImmutableArray<TraitViewModel> traitList)
{
if (traitList.IsDefaultOrEmpty)
{
return;
}
foreach (var traitViewModel in traitList)
{
InsertIfNotPresent(traitViewModel);
}
}
private void InsertIfNotPresent(TraitViewModel trait)
{
// 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;
}
if (result == 0)
{
return;
}
}
_collection.Add(trait);
}
}
}
@@ -1,31 +0,0 @@
using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace xunit.runner.wpf.ViewModel
{
public sealed class TraitViewModel : ViewModelBase
{
private bool _isSelected;
public string Name { get; }
public string Value { get; }
public string DisplayName { get; }
public bool IsSelected
{
get { return _isSelected; }
set { Set(ref _isSelected, value); }
}
public TraitViewModel(string name, string value)
{
Name = name;
Value = value;
DisplayName = $"{Name}={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();
}
}
}
-9
View File
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="CommonServiceLocator" version="1.3" targetFramework="net452" />
<package id="MvvmLight" version="5.1.1.0" targetFramework="net452" />
<package id="MvvmLightLibs" version="5.1.1.0" targetFramework="net452" />
<package id="System.Collections.Immutable" version="1.1.37" targetFramework="net452" />
<package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
<package id="xunit.runner.utility" version="2.1.0-beta4-build3109" targetFramework="net452" />
</packages>
-181
View File
@@ -1,181 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{34FB519C-FB49-4B31-ACA2-7F7879311BCF}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>xunit.runner.wpf</RootNamespace>
<AssemblyName>xunit.runner.wpf</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="GalaSoft.MvvmLight, Version=5.1.1.35049, Culture=neutral, PublicKeyToken=e7570ab207bcb616, processorArchitecture=MSIL">
<HintPath>..\packages\MvvmLightLibs.5.1.1.0\lib\net45\GalaSoft.MvvmLight.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="GalaSoft.MvvmLight.Extras, Version=5.1.1.35049, Culture=neutral, PublicKeyToken=669f0b5e8f868abf, processorArchitecture=MSIL">
<HintPath>..\packages\MvvmLightLibs.5.1.1.0\lib\net45\GalaSoft.MvvmLight.Extras.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="GalaSoft.MvvmLight.Platform, Version=5.1.1.35053, Culture=neutral, PublicKeyToken=5f873c45e98af8a1, processorArchitecture=MSIL">
<HintPath>..\packages\MvvmLightLibs.5.1.1.0\lib\net45\GalaSoft.MvvmLight.Platform.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Practices.ServiceLocation, Version=1.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Collections.Immutable, Version=1.1.37.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.1.1.37\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\MvvmLightLibs.5.1.1.0\lib\net45\System.Windows.Interactivity.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="CommandBindings.cs" />
<Compile Include="Converters\TestStateConverter.cs" />
<Compile Include="Extensions.cs" />
<Compile Include="FilteredCollectionView.cs" />
<Compile Include="Impl\RemoteTestUtil.Connection.cs" />
<Compile Include="Impl\RemoteTestUtil.BackgroundRunner.cs" />
<Compile Include="Impl\RemoteTestUtil.cs" />
<Compile Include="ITestUtil.cs" />
<Compile Include="LoadingDialog.xaml.cs">
<DependentUpon>LoadingDialog.xaml</DependentUpon>
</Compile>
<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.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>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<AppDesigner Include="Properties\" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<Resource Include="Artwork\xunit-dot-net-small-logo.png" />
<Resource Include="Artwork\Passed.ico" />
<Resource Include="Artwork\Skipped.ico" />
<Resource Include="Artwork\Failed.ico" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SampleTestAssembly\SampleTestAssembly.csproj">
<Project>{bdafb5dd-ffb3-4a94-a312-dfb080010846}</Project>
<Name>SampleTestAssembly</Name>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
<ProjectReference Include="..\xunit.runner.data\xunit.runner.data.csproj">
<Project>{a1f579f4-443e-4f64-bc55-998ab86ff293}</Project>
<Name>xunit.runner.data</Name>
</ProjectReference>
<ProjectReference Include="..\xunit.runner.worker\xunit.runner.worker.csproj">
<Project>{9df97a2b-0eb5-4b12-9f81-69dfac979814}</Project>
<Name>xunit.runner.worker</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<WCFMetadata Include="Service References\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>