diff --git a/.gitignore b/.gitignore index 10640094..543d7638 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,9 @@ build obj package deploy +<<<<<<< HEAD:.gitignore test-results +======= +lib + +>>>>>>> nunit-console/master:.gitignore diff --git a/BUILDING.txt b/BUILDING.txt new file mode 100644 index 00000000..e22bd8fd --- /dev/null +++ b/BUILDING.txt @@ -0,0 +1,74 @@ +Building NUnit 3.0 + +NUnit 3.0 consists of three separate layers: the Framework, the Engine and the Console Runner. There is a Visual Studio solution and a NAnt build script for each of these. Eventually, the three layers will be independent projects. For now, they are developed in a single repository and have some dependencies. + +The solutions all place their output in a common bin directory. In order to satisfy all references you must first build the framework, then the engine and finally the console runner. + +For the time being, there is also a combined solutions and a combined script, making it easier to work on the overall initial development of NUnit 3.0. + +The following guidelines are provided for developers working on NUnit: + +1. When working on changes to the Framework alone, use the NUnitFramework solution and/or NAnt script. Examples of such changes might be new constraints and new or modified attributes. This is probably the most common sort of change that you will need to make. + +2. NUnitLite is built as a part of the framework layer and makes no use of the engine or console layers. Consequently, changes to NUnitLite must also be made at this level. + +3. When working on changes to the Engine alone, use the NUnitEngine solution and/or NAnt script. An example of such a change would be to support an additional framework or to run tests on a remote system. Such changes should be somewhat rare once the Engine is released. + +4. When working on changes to the Console Runner alone, use the NUnitConsole solution and/or NAnt script. Changes to the report output produced by the runner would be an example of such a change. + +5. When changes involve multiple levels, it is recommended that you develop +and test the individual pieces using the single-component solutions or scripts and then use the combined solution or script to ensure that it all works together. + +Further guidelines are given below for each layer and for the combined solution/scripts. + +Framework Layer + +The NUnitFramework build produces the following assemblies: + * nunit.framework.dll - the framework itself + * nunit.framework.tests.dll - tests of the framework + * nunit.testdata.dll - data used by the tests + * nunitlite.dll - the NUnitLite framework + * nunitlite.tests.dll - tests of the NUnitLite framework + * nunitlite.testdata.dll - data used by the NUnitLite tests + * direct-runner.exe - test harness used to run framework tests + * mock-assembly.dll - a test assembly used by some of the tests +Each of these is built for three targets: .NET 2.0, .NET 3.5 and .NET 4.0. Features requiring either .NET 3.5 or .NET 4.0 are excluded from lesser builds by the use of conditional tests. + +The NUnitFramework solution builds each of the assemblies for each target for a total of 24 assemblies. The output assemblies for each target are stored in a separate subdirectory under the shared bin directory used by all of the solutions. + +The NAnt build for each target stores its output in separate subdirectories of the NUnitFramework/build directory. The 'deploy' target copies the output to a higher level shared directory for use in combined tests. The 'deploy-test' target runs tests in the deploy directory. + +Whenever changes are made, it's important to run all tests and to ensure that both the solutions and the nant scripts are updated as needed. The NAnt test +target runs tests for both full NUnit and NUnitLite. When using the solutions, it is necessary to run them separately to ensure that everything is working. + +Engine Layer + +The Engine build produces the following assemblies: + * nunit.engine.dll - the engine itself + * nunit.engine.api.dll - the api assembly referenced by runners + * nunit-agent.exe - the agent used for running tests in a separate process + * nunit.engine.tests.dll - tests of the engine +Engine components are all built with a target of .NET 2.0. + +The NUnitEngine solution builds all the engine assemblies. It produces output in the shared bin directory and references the .NET 2.0 builds of nunit.framework.dll and mock-assembly.dll. In addition, the 2.0 build of direct-runner.exe is copied to the bin directory in order to run the tests. + +The NAnt build script puts the engine assemblies into the NUnitEngine/build directory. The 'deploy' target copies the output to the higher level deploy directory. The 'deploy-test' target runs tests in the deploy directory. + +Console Layer + +The Console Runner build produces the following assemblies: + * nunit-console.exe + * nunit-console.tests.dll +All assemblies are built with a target of .NET 2.0. + +The NUnitConsole solution produces output in the shared bin directory. The tests reference nunit.framework.dll. The console runner itself is used to execute its own tests. + +The NAnt build script produces output in the NUnitConsole/build directory. The 'deploy' target copies the output to the higner level deploy directory. The 'deploy-test' target runs tests in the deploy directory. + +Combined Solution and Scripts + +The nunit.sln file builds includes all assemblies from the other three solutions with the exception of those related to NUnitLite. The Console runner is used to execute tests in the NUnitTests.nunit project file, which contains separate configs for testing under .NET 2.0, 3.5 and 4.0. + +The master NAnt script, nunit.build, uses the three lower-level scripts to build and deploy the NUnit assemblies and run tests under the console runner. +Separate console executions are used to run tests for the Console, the Engine and the Framework. Framework tests are run under each of the supported platforms that is available on the build machine. + diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 00000000..8ba489fb --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,176 @@ +NUnit 2.9.6 - October 4, 2013 + +Main Features + * Separate projects for nunit-console and nunit.engine + * New builds for .NET 4.5 and Silverlight + * TestContext is now supported + * External API is now stable; internal interfaces are separate from API + * Tests may be run in parallel on separate threads + * Solutions and projects now use VS2012 (except for Compact framework) + +Bug Fixes + + * 463470 We should encapsulate references to pre-2.0 collections + * 498690 Assert.That() doesn't like properties with scoped setters + * 501784 Theory tests do not work correctly when using null parameters + * 531873 Feature: Extraction of unit tests from NUnit test assembly and calling appropriate one + * 611325 Allow Teardown to detect if last test failed + * 611938 Generic Test Instances disappear + * 655882 Make CategoryAttribute inherited + * 664081 Add Server2008 R2 and Windows 7 to PlatformAttribute + * 671432 Upgrade NAnt to Latest Release + * 676560 Assert.AreEqual does not support IEquatable + * 691129 Add Category parameter to TestFixture + * 697069 Feature request: dynamic location for TestResult.xml + * 708173 NUnit's logic for comparing arrays - use Comparer if it is provided + * 709062 "System.ArgumentException : Cannot compare" when the element is a list + * 712156 Tests cannot use AppDomain.SetPrincipalPolicy + * 719184 Platformdependency in src/ClientUtilities/util/Services/DomainManager.cs:40 + * 719187 Using Path.GetTempPath() causes conflicts in shared temporary folders + * 735851 Add detection of 3.0, 3.5 and 4.0 frameworks to PlatformAttribute + * 736062 Deadlock when EventListener performs a Trace call + EventPump synchronisation + * 756843 Failing assertion does not show non-linear tolerance mode + * 766749 net-2.0\nunit-console-x86.exe.config should have a element and also enable loadFromRemoteSources + * 770471 Assert.IsEmpty does not support IEnumerable + * 785460 Add Category parameter to TestCaseSourceAttribute + * 787106 EqualConstraint provides inadequate failure information for IEnumerables + * 792466 TestContext MethodName + * 794115 HashSet incorrectly reported + * 800089 Assert.Throws() hides details of inner AssertionException + * 848713 Feature request: Add switch for console to break on any test case error + * 878376 Add 'Exactly(n)' to the NUnit constraint syntax + * 882137 When no tests are run, higher level suites display as Inconclusive + * 882517 NUnit 2.5.10 doesn't recognize TestFixture if there are only TestCaseSource inside + * 885173 Tests are still executed after cancellation by user + * 885277 Exception when project calls for a runtime using only 2 digits + * 885604 Feature request: Explicit named parameter to TestCaseAttribute + * 890129 DelayedConstraint doesn't appear to poll properties of objects + * 892844 Not using Mono 4.0 profile under Windows + * 893919 DelayedConstraint fails polling properties on references which are initially null + * 896973 Console output lines are run together under Linux + * 897289 Is.Empty constraint has unclear failure message + * 898192 Feature Request: Is.Negative, Is.Positive + * 898256 IEnumerable for Datapoints doesn't work + * 899178 Wrong failure message for parameterized tests that expect exceptions + * 904841 After exiting for timeout the teardown method is not executed + * 908829 TestCase attribute does not play well with variadic test functions + * 910218 NUnit should add a trailing separator to the ApplicationBase + * 920472 CollectionAssert.IsNotEmpty must dispose Enumerator + * 922455 Add Support for Windows 8 and Windows 2012 Server to PlatformAttribute + * 928246 Use assembly.Location instead of assembly.CodeBase + * 958766 For development work under TeamCity, we need to support nunit2 formatted output under direct-runner + * 1000181 Parameterized TestFixture with System.Type as constructor arguments fails + * 1000213 Inconclusive message Not in report output + * 1023084 Add Enum support to RandomAttribute + * 1028188 Add Support for Silverlight + * 1029785 Test loaded from remote folder failed to run with exception System.IODirectory + * 1037144 Add MonoTouch support to PlatformAttribute + * 1041365 Add MaxOsX and Xbox support to platform attribute + * 1057981 C#5 async tests are not supported + * 1060631 Add .NET 4.5 build + * 1064014 Simple async tests should not return Task + * 1071164 Support async methods in usage scenarios of Throws constraints + * 1071343 Runner.Load fails on CF if the test assembly contains a generic method + * 1071861 Error in Path Constraints + * 1072379 Report test execution time at a higher resolution + * 1074568 Assert/Assume should support an async method for the ActualValueDelegate + * 1082330 Better Exception if SetCulture attribute is applied multiple times + * 1111834 Expose Random Object as part of the test context + * 1111838 Include Random Seed in Test Report + * 1172979 Add Category Support to nunitlite Runner + * 1203361 Randomizer uniqueness tests sometimes fail + * 1221712 When non-existing test method is specified in -test, result is still "Tests run: 1, Passed: 1" + * 1223294 System.NullReferenceException thrown when ExpectedExceptionAttribute is used in a static class + * 1225542 Standardize commandline options for test harness + +Bug Fixes in 2.9.6 But Not Listed Here in the Release + * 541699 Silverlight Support + * 1222148 /framework switch does not recognize net-4.5 + * 1228979 Theories with all test cases inconclusive are not reported as failures + + +NUnit 2.9.5 - July 30, 2010 + +Bug Fixes + + * 483836 Allow non-public test fixtures consistently + * 487878 Tests in generic class without proper TestFixture attribute should be invalid + * 498656 TestCase should show array values in GUI + * 513989 Is.Empty should work for directories + * 519912 Thread.CurrentPrincipal Set In TestFixtureSetUp Not Maintained Between Tests + * 532488 constraints from ConstraintExpression/ConstraintBuilder are not reusable + * 590717 categorie contains dash or trail spaces is not selectable + * 590970 static TestFixtureSetUp/TestFixtureTearDown methods in base classes are not run + * 595683 NUnit console runner fails to load assemblies + * 600627 Assertion message formatted poorly by PropertyConstraint + * 601108 Duplicate test using abstract test fixtures + * 601645 Parametered test should try to convert data type from source to parameter + * 605432 ToString not working properly for some properties + * 606548 Deprecate Directory Assert in 2.5 and remove it in 3.0 + * 608875 NUnit Equality Comparer incorrectly defines equality for Dictionary objects + +NUnit 2.9.4 - May 4, 2010 + +Bug Fixes + + * 419411 Fixture With No Tests Shows as Non-Runnable + * 459219 Changes to thread princpal cause failures under .NET 4.0 + * 459224 Culture test failure under .NET 4.0 + * 462019 Line endings needs to be better controlled in source + * 462418 Assume.That() fails if I specify a message + * 483845 TestCase expected return value cannot be null + * 488002 Should not report tests in abstract class as invalid + * 490679 Category in TestCaseData clashes with Category on ParameterizedMethodSuite + * 501352 VS2010 projects have not been updated for new directory structure + * 504018 Automatic Values For Theory Test Parameters Not Provided For bool And enum + * 505899 'Description' parameter in both TestAttribute and TestCaseAttribute is not allowed + * 523335 TestFixtureTearDown in static class not executed + * 556971 Datapoint(s)Attribute should work on IEnumerable as well as on Arrays + * 561436 SetCulture broken with 2.5.4 + * 563532 DatapointsAttribute should be allowed on properties and methods + +NUnit 2.9.3 - October 26, 2009 + +Main Features + + * Created new API for controlling framework + * New builds for .Net 3.5 and 4.0, compact framework 3.5 + * Support for old style tests has been removed + * New adhoc runner for testing the framework + +Bug Fixes + + * 432805 Some Framework Tests don't run on Linux + * 440109 Full Framework does not support "Contains" + +NUnit 2.9.2 - September 19, 2009 + +Main Features + + * NUnitLite code is now merged with NUnit + * Added NUnitLite runner to the framework code + * Added Compact framework builds + +Bug Fixes + + * 430100 Assert.Catch should return T + * 432566 NUnitLite shows empty string as argument + * 432573 Mono test should be at runtime + +NUnit 2.9.1 - August 27, 2009 +General + + * Created a separate project for the framework and framework tests + * Changed license to MIT / X11 + * Created Windows installer for the framework + +Bug Fixes + + * 400502 NUnitEqualityComparer.StreamsE­qual fails for same stream + * 400508 TestCaseSource attirbute is not working when Type is given + * 400510 TestCaseData variable length ctor drops values + * 417557 Add SetUICultureAttribute from NUnit 2.5.2 + * 417559 Add Ignore to TestFixture, TestCase and TestCaseData + * 417560 Merge Assert.Throws and Assert.Catch changes from NUnit 2.5.2 + * 417564 TimeoutAttribute on Assembly + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..88aca2bb --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2011-2013 Charlie Poole + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/NUnitConsole.build b/NUnitConsole.build new file mode 100644 index 00000000..092a338e --- /dev/null +++ b/NUnitConsole.build @@ -0,0 +1,481 @@ + + + + +************************************************************************** +* This script is used to build the NUnit Console runner and tests. * +* * +* Dependencies: * +* * +* Building the runner requires at least .NET 3.5 but it runs under * +* .NET 2.0 or greater. * +* * +* The script requires the NAnt 0.92 release with the config file * +* modified in order to run run tests under .NET 4.5. * +* * +* PartCover is required to run the 'test-coverage' target. * +* * +************************************************************************** + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NUnitConsole.sln b/NUnitConsole.sln new file mode 100644 index 00000000..c204b2af --- /dev/null +++ b/NUnitConsole.sln @@ -0,0 +1,65 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "nunit-console", "src\nunit-console\nunit-console.csproj", "{0DE218CA-AFB8-423A-9CD2-E22DEAC55C46}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "nunit-console.tests", "src\nunit-console.tests\nunit-console.tests.csproj", "{B310A760-8AE1-41CA-81F8-03B12E2FCE30}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "nunit.engine.api", "src\nunit.engine.api\nunit.engine.api.csproj", "{775FAD50-3623-4922-97C4-DFB29A8BE4C7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "nunit.engine", "src\nunit.engine\nunit.engine.csproj", "{372A3447-D657-40FF-A089-77C19FEC30C8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "nunit.engine.tests", "src\nunit.engine.tests\nunit.engine.tests.csproj", "{D694CB69-6CFB-4762-86C2-EB27B808B282}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "nunit-agent", "src\nunit-agent\nunit-agent.csproj", "{C2A8FC7A-FA64-46EA-AF6D-73D6B371DBF8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{147554FB-464E-4E4D-ADD6-B84121AAE2A5}" + ProjectSection(SolutionItems) = preProject + local.settings.include.sample = local.settings.include.sample + NUnitConsole.build = NUnitConsole.build + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mock-assembly", "src\mock-assembly\mock-assembly.csproj", "{2E368281-3BA8-4050-B05E-0E0E43F8F446}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0DE218CA-AFB8-423A-9CD2-E22DEAC55C46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DE218CA-AFB8-423A-9CD2-E22DEAC55C46}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DE218CA-AFB8-423A-9CD2-E22DEAC55C46}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0DE218CA-AFB8-423A-9CD2-E22DEAC55C46}.Release|Any CPU.Build.0 = Release|Any CPU + {B310A760-8AE1-41CA-81F8-03B12E2FCE30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B310A760-8AE1-41CA-81F8-03B12E2FCE30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B310A760-8AE1-41CA-81F8-03B12E2FCE30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B310A760-8AE1-41CA-81F8-03B12E2FCE30}.Release|Any CPU.Build.0 = Release|Any CPU + {775FAD50-3623-4922-97C4-DFB29A8BE4C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {775FAD50-3623-4922-97C4-DFB29A8BE4C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {775FAD50-3623-4922-97C4-DFB29A8BE4C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {775FAD50-3623-4922-97C4-DFB29A8BE4C7}.Release|Any CPU.Build.0 = Release|Any CPU + {372A3447-D657-40FF-A089-77C19FEC30C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {372A3447-D657-40FF-A089-77C19FEC30C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {372A3447-D657-40FF-A089-77C19FEC30C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {372A3447-D657-40FF-A089-77C19FEC30C8}.Release|Any CPU.Build.0 = Release|Any CPU + {D694CB69-6CFB-4762-86C2-EB27B808B282}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D694CB69-6CFB-4762-86C2-EB27B808B282}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D694CB69-6CFB-4762-86C2-EB27B808B282}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D694CB69-6CFB-4762-86C2-EB27B808B282}.Release|Any CPU.Build.0 = Release|Any CPU + {C2A8FC7A-FA64-46EA-AF6D-73D6B371DBF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C2A8FC7A-FA64-46EA-AF6D-73D6B371DBF8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C2A8FC7A-FA64-46EA-AF6D-73D6B371DBF8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C2A8FC7A-FA64-46EA-AF6D-73D6B371DBF8}.Release|Any CPU.Build.0 = Release|Any CPU + {2E368281-3BA8-4050-B05E-0E0E43F8F446}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E368281-3BA8-4050-B05E-0E0E43F8F446}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E368281-3BA8-4050-B05E-0E0E43F8F446}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E368281-3BA8-4050-B05E-0E0E43F8F446}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + StartupItem = NUnitConsole\src\nunit-console\nunit-console-vs2010.csproj + EndGlobalSection +EndGlobal diff --git a/NUnitConsole.sln.DotSettings b/NUnitConsole.sln.DotSettings new file mode 100644 index 00000000..3c56afce --- /dev/null +++ b/NUnitConsole.sln.DotSettings @@ -0,0 +1,165 @@ + + True + SUGGESTION + True + True + True + True + True + False + True + False + *********************************************************************** +Copyright (c) $CURRENT_YEAR$ Charlie Poole + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*********************************************************************** + + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb"><ExtraRule Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb"><ExtraRule Prefix="" Suffix="" Style="aaBb" /></Policy> + True + True + getUserName() + -1 + getCurrentDate("yyyy") + -1 + copyright + NUnit Copyright + // *********************************************************************** +// Copyright (c) $YEAR$ $USER$ +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + True + True + True + True + InCSharpTypeAndNamespace + 2.0 + True + 0 + True + 1 + True + True + Test Method + True + constant("TestMethod") + 0 + True + True + 2.0 + InCSharpTypeMember + test + True + [Test] +public void $TEST_METHOD$() +{ + $END$ +} + True + True + Test Fixture + True + constant("TestClass") + 0 + True + constant("TestMethod") + 1 + True + True + 2.0 + InCSharpTypeAndNamespace + True + 2.0 + InCSharpTypeMember + tf + True + [TestFixture] +public class $TEST_CLASS$ +{ + [Test] + public void $TEST_METHOD$() + { + $END$ + } +} + True + True + cs + TestClass + True + &Test Class + True + getFileNameWithoutExtension() + -1 + 2 + True + fileheader() + -1 + 0 + True + fileDefaultNamespace() + -1 + 1 + True + constant("TestMethod") + 3 + True + True + InCSharpProjectFile + True + $HEADER$ +#region Using Directives + +using System; +using NUnit.Framework; + +#endregion + +namespace $NAMESPACE$ +{ + [TestFixture] + public class $CLASS$ + { + [Test] + public void $TEST_METHOD$() + { + $END$ + } + } +} \ No newline at end of file diff --git a/NUnitConsoleTests.nunit b/NUnitConsoleTests.nunit new file mode 100644 index 00000000..facba0bb --- /dev/null +++ b/NUnitConsoleTests.nunit @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/lib/nunit.framework.dll b/lib/nunit.framework.dll new file mode 100644 index 00000000..b6d2e77c Binary files /dev/null and b/lib/nunit.framework.dll differ diff --git a/local.settings.include.sample b/local.settings.include.sample new file mode 100644 index 00000000..88d4c1fc --- /dev/null +++ b/local.settings.include.sample @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CommonAssemblyInfo.cs b/src/CommonAssemblyInfo.cs new file mode 100644 index 00000000..69971e06 --- /dev/null +++ b/src/CommonAssemblyInfo.cs @@ -0,0 +1,39 @@ +// *********************************************************************** +// Copyright (c) 2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System.Reflection; + +// +// Common Information about the NUnit Engine and Console assemblies +// +[assembly: AssemblyCompany("NUnit Software")] +[assembly: AssemblyProduct("NUnit 3.0")] +[assembly: AssemblyCopyright("Copyright (C) 2014 Charlie Poole")] +[assembly: AssemblyTrademark("NUnit is a trademark of NUnit Software")] + +#if DEBUG +[assembly: AssemblyConfiguration("Debug")] +#else +[assembly: AssemblyConfiguration("")] +#endif + diff --git a/src/CommonVersionInfo.cs b/src/CommonVersionInfo.cs new file mode 100644 index 00000000..e808af2d --- /dev/null +++ b/src/CommonVersionInfo.cs @@ -0,0 +1,29 @@ +// *********************************************************************** +// Copyright (c) 2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System.Reflection; + +// +// Common Versioning for the NUnit Engine and Console assemblies +// +[assembly: AssemblyVersion("3.0.*")] diff --git a/src/mock-assembly/AssemblyInfo.cs b/src/mock-assembly/AssemblyInfo.cs new file mode 100644 index 00000000..734be769 --- /dev/null +++ b/src/mock-assembly/AssemblyInfo.cs @@ -0,0 +1,17 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Mock Assembly")] +[assembly: AssemblyDescription("Test assembly used by NUnit tests")] +[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("37f4a88d-9f41-462e-ac05-93f1d390b700")] diff --git a/src/mock-assembly/MockAssembly.cs b/src/mock-assembly/MockAssembly.cs new file mode 100644 index 00000000..8a4a6512 --- /dev/null +++ b/src/mock-assembly/MockAssembly.cs @@ -0,0 +1,309 @@ +// *********************************************************************** +// Copyright (c) 2013 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using NUnit.Framework; +using NUnit.Framework.Internal; + +namespace NUnit.Engine.Tests +{ + namespace Assemblies + { + /// + /// Constant definitions for the mock-assembly dll. + /// + public class MockAssembly + { + public static int Classes = 9; + public static int NamespaceSuites = 6; // assembly, NUnit, Tests, Assemblies, Singletons, TestAssembly + + public static int Tests = MockTestFixture.Tests + + Singletons.OneTestCase.Tests + + TestAssembly.MockTestFixture.Tests + + IgnoredFixture.Tests + + ExplicitFixture.Tests + + BadFixture.Tests + + FixtureWithTestCases.Tests + + ParameterizedFixture.Tests + + GenericFixtureConstants.Tests; + + public static int Suites = MockTestFixture.Suites + + Singletons.OneTestCase.Suites + + TestAssembly.MockTestFixture.Suites + + IgnoredFixture.Suites + + ExplicitFixture.Suites + + BadFixture.Suites + + FixtureWithTestCases.Suites + + ParameterizedFixture.Suites + + GenericFixtureConstants.Suites + + NamespaceSuites; + + public static readonly int Nodes = Tests + Suites; + + public static int ExplicitFixtures = 1; + public static int SuitesRun = Suites - ExplicitFixtures; + + public static int Ignored = MockTestFixture.Ignored + IgnoredFixture.Tests; + public static int Explicit = MockTestFixture.Explicit + ExplicitFixture.Tests; + public static int NotRunnable = MockTestFixture.NotRunnable + BadFixture.Tests; + public static int NotRun = Ignored + Explicit + NotRunnable; + public static int TestsRun = Tests - NotRun; + public static int ResultCount = Tests - Explicit; + + public static int Errors = MockTestFixture.Errors; + public static int Failures = MockTestFixture.Failures; + public static int ErrorsAndFailures = Errors + Failures; + public static int Inconclusive = MockTestFixture.Inconclusive; + public static int Success = TestsRun - ErrorsAndFailures - Inconclusive; + + public static int Categories = MockTestFixture.Categories; + + public static string AssemblyPath = AssemblyHelper.GetAssemblyPath(typeof(MockAssembly).Assembly); + } + + public class MockSuite + { + [Suite] + public static TestSuite Suite + { + get + { + return new TestSuite( "MockSuite" ); + } + } + } + + [TestFixture(Description="Fake Test Fixture")] + [Category("FixtureCategory")] + public class MockTestFixture + { + public static readonly int Tests = 11; + public static readonly int Suites = 1; + + public static readonly int Ignored = 1; + public static readonly int Explicit = 1; + public static readonly int NotRunnable = 2; + public static readonly int NotRun = Ignored + Explicit + NotRunnable; + public static readonly int TestsRun = Tests - NotRun; + public static readonly int ResultCount = Tests - Explicit; + + public static readonly int Failures = 1; + public static readonly int Errors = 1; + public static readonly int ErrorsAndFailures = Errors + Failures; + public const int Inconclusive = 1; + + public static readonly int Categories = 5; + public static readonly int MockCategoryTests = 2; + + [Test(Description="Mock Test #1")] + public void MockTest1() + {} + + [Test] + [Category("MockCategory")] + [Property("Severity","Critical")] + [Description("This is a really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really long description")] + public void MockTest2() + {} + + [Test] + [Category("MockCategory")] + [Category("AnotherCategory")] + public void MockTest3() + { Assert.Pass("Succeeded!"); } + + [Test] + protected static void MockTest5() + {} + + [Test] + public void FailingTest() + { + Assert.Fail("Intentional failure"); + } + + [Test, Property("TargetMethod", "SomeClassName"), Property("Size", 5), /*Property("TargetType", typeof( System.Threading.Thread ))*/] + public void TestWithManyProperties() + {} + + [Test] + [Ignore("ignoring this test method for now")] + [Category("Foo")] + public void MockTest4() + {} + + [Test, Explicit] + [Category( "Special" )] + public void ExplicitlyRunTest() + {} + + [Test] + public void NotRunnableTest( int a, int b) + { + } + + [Test] + public void InconclusiveTest() + { + Assert.Inconclusive("No valid data"); + } + + [Test] + public void TestWithException() + { + MethodThrowsException(); + } + + private void MethodThrowsException() + { + throw new Exception("Intentional Exception"); + } + } + } + + namespace Singletons + { + [TestFixture] + public class OneTestCase + { + public static readonly int Tests = 1; + public static readonly int Suites = 1; + + [Test] + public virtual void TestCase() + {} + } + } + + namespace TestAssembly + { + [TestFixture] + public class MockTestFixture + { + public static readonly int Tests = 1; + public static readonly int Suites = 1; + + [Test] + public void MyTest() + { + } + } + } + + [TestFixture, Ignore] + public class IgnoredFixture + { + public static readonly int Tests = 3; + public static readonly int Suites = 1; + + [Test] + public void Test1() { } + + [Test] + public void Test2() { } + + [Test] + public void Test3() { } + } + + [TestFixture,Explicit] + public class ExplicitFixture + { + public static readonly int Tests = 2; + public static readonly int Suites = 1; + public static readonly int Nodes = Tests + Suites; + + [Test] + public void Test1() { } + + [Test] + public void Test2() { } + } + + [TestFixture] + public class BadFixture + { + public static readonly int Tests = 1; + public static readonly int Suites = 1; + + public BadFixture(int val) { } + + [Test] + public void SomeTest() { } + } + + [TestFixture] + public class FixtureWithTestCases + { + public static readonly int Tests = 4; + public static readonly int Suites = 3; + + [TestCase(2, 2, ExpectedResult=4)] + [TestCase(9, 11, ExpectedResult=20)] + public int MethodWithParameters(int x, int y) + { + return x+y; + } + + [TestCase(2, 4)] + [TestCase(9.2, 11.7)] + public void GenericMethod(T x, T y) + { + } + } + + [TestFixture(5)] + [TestFixture(42)] + public class ParameterizedFixture + { + public static readonly int Tests = 4; + public static readonly int Suites = 3; + + public ParameterizedFixture(int num) { } + + [Test] + public void Test1() { } + + [Test] + public void Test2() { } + } + + public class GenericFixtureConstants + { + public static readonly int Tests = 4; + public static readonly int Suites = 3; + } + + [TestFixture(5)] + [TestFixture(11.5)] + public class GenericFixture + { + public GenericFixture(T num){ } + + [Test] + public void Test1() { } + + [Test] + public void Test2() { } + } +} diff --git a/src/mock-assembly/mock-assembly.build b/src/mock-assembly/mock-assembly.build new file mode 100644 index 00000000..57b84d0e --- /dev/null +++ b/src/mock-assembly/mock-assembly.build @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/mock-assembly/mock-assembly.csproj b/src/mock-assembly/mock-assembly.csproj new file mode 100644 index 00000000..5ff3ef01 --- /dev/null +++ b/src/mock-assembly/mock-assembly.csproj @@ -0,0 +1,95 @@ + + + + Local + 9.0.21022 + 2.0 + {2E368281-3BA8-4050-B05E-0E0E43F8F446} + Debug + AnyCPU + + + mock-assembly + JScript + Grid + IE50 + false + Library + NUnit.Engine.Tests + OnBuildSuccess + + + + + 3.5 + v2.0 + + + ..\..\bin\Debug\ + 285212672 + + + DEBUG;TRACE + + + true + 4096 + false + false + false + 4 + full + prompt + + + ..\..\bin\Release\net-2.0\ + 285212672 + + + TRACE + + + 4096 + true + false + false + 4 + none + prompt + + + + False + ..\..\lib\nunit.framework.dll + + + System + + + System.Data + + + System.XML + + + + + CommonAssemblyInfo.cs + + + CommonVersionInfo.cs + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/nunit-agent/AssemblyInfo.cs b/src/nunit-agent/AssemblyInfo.cs new file mode 100644 index 00000000..0b3a9e8c --- /dev/null +++ b/src/nunit-agent/AssemblyInfo.cs @@ -0,0 +1,17 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NUnit Agent")] +[assembly: AssemblyDescription("Runs tests in a separate process when necessary")] +[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("59b4d53d-99d7-44f2-8c6e-b83d41718dd6")] diff --git a/src/nunit-agent/Program.cs b/src/nunit-agent/Program.cs new file mode 100644 index 00000000..0ff24776 --- /dev/null +++ b/src/nunit-agent/Program.cs @@ -0,0 +1,168 @@ +// *********************************************************************** +// Copyright (c) 2008 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Runtime.Remoting.Channels; +using System.Runtime.Remoting.Channels.Tcp; +using System.Diagnostics; +using NUnit.Engine; +using NUnit.Engine.Agents; +using NUnit.Engine.Internal; +using NUnit.Engine.Services; + +namespace NUnit.Agent +{ + /// + /// Summary description for Program. + /// + public class NUnitTestAgent + { + //static Logger log = InternalTrace.GetLogger(typeof(NUnitTestAgent)); + + static Guid AgentId; + static string AgencyUrl; + static ITestAgency Agency; + + /// + /// Channel used for communications with the agency + /// and with clients + /// + static TcpChannel Channel; + + /// + /// The main entry point for the application. + /// + [STAThread] + public static int Main(string[] args) + { + AgentId = new Guid(args[0]); + AgencyUrl = args[1]; + + bool pause = false, verbose = false; + for (int i = 2; i < args.Length; i++) + switch (args[i]) + { + case "--pause": + pause = true; + break; + case "--verbose": + verbose = true; + break; + } +#if DEBUG + if (pause) + System.Windows.Forms.MessageBox.Show("Attach debugger if desired, then press OK", "NUnit-Agent"); +#endif + + // Create SettingsService early so we know the trace level right at the start + SettingsService settingsService = new SettingsService("NUnit30Settings.xml", false); + //InternalTrace.Initialize("nunit-agent_%p.log", (InternalTraceLevel)settingsService.GetSetting("Options.InternalTraceLevel", InternalTraceLevel.Default)); + + //log.Info("Agent process {0} starting", Process.GetCurrentProcess().Id); + //log.Info("Running under version {0}, {1}", + // Environment.Version, + // RuntimeFramework.CurrentFramework.DisplayName); + + if (verbose) + { + Console.WriteLine("Agent process {0} starting", Process.GetCurrentProcess().Id); + Console.WriteLine("Running under version {0}, {1}", + Environment.Version, + RuntimeFramework.CurrentFramework.DisplayName); + } + + // Create TestEngine - this program is + // conceptually part of the engine and + // can access it's internals as needed. + TestEngine engine = new TestEngine(); + + // Custom Service Initialization + //log.Info("Adding Services"); + engine.Services.Add(settingsService); + engine.Services.Add(new ProjectService()); + engine.Services.Add(new DomainManager()); + engine.Services.Add(new InProcessTestRunnerFactory()); + engine.Services.Add(new DriverFactory()); + //engine.Services.Add( new TestLoader() ); + + // Initialize Services + //log.Info("Initializing Services"); + engine.Services.ServiceManager.InitializeServices(); + + Channel = ServerUtilities.GetTcpChannel(); + + //log.Info("Connecting to TestAgency at {0}", AgencyUrl); + try + { + Agency = Activator.GetObject(typeof(ITestAgency), AgencyUrl) as ITestAgency; + } + catch (Exception ex) + { + Console.WriteLine("Unable to connect\r\n{0}", ex); + //log.Error("Unable to connect", ex); + } + + if (Channel != null) + { + //log.Info("Starting RemoteTestAgent"); + RemoteTestAgent agent = new RemoteTestAgent(AgentId, Agency, engine.Services); + + try + { + if (agent.Start()) + { + //log.Debug("Waiting for stopSignal"); + agent.WaitForStop(); + //log.Debug("Stop signal received"); + } + else + Console.WriteLine("Failed to start RemoteTestAgent"); + //log.Error("Failed to start RemoteTestAgent"); + } + catch (Exception ex) + { + //log.Error("Exception in RemoteTestAgent", ex); + Console.WriteLine("Exception in RemoteTestAgent", ex); + } + + //log.Info("Unregistering Channel"); + try + { + ChannelServices.UnregisterChannel(Channel); + } + catch (Exception ex) + { + //log.Error("ChannelServices.UnregisterChannel threw an exception", ex); + Console.WriteLine("ChannelServices.UnregisterChannel threw an exception\r\n{0}", ex); + } + } + + if (verbose) + Console.WriteLine("Agent process {0} exiting", Process.GetCurrentProcess().Id); + //log.Info("Agent process {0} exiting", Process.GetCurrentProcess().Id); + //InternalTrace.Close(); + + return 0; + } + } +} diff --git a/src/nunit-agent/app.config b/src/nunit-agent/app.config new file mode 100644 index 00000000..db292afe --- /dev/null +++ b/src/nunit-agent/app.config @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/nunit-agent/nunit-agent.build b/src/nunit-agent/nunit-agent.build new file mode 100644 index 00000000..784c621c --- /dev/null +++ b/src/nunit-agent/nunit-agent.build @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/nunit-agent/nunit-agent.csproj b/src/nunit-agent/nunit-agent.csproj new file mode 100644 index 00000000..12e72805 --- /dev/null +++ b/src/nunit-agent/nunit-agent.csproj @@ -0,0 +1,75 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {C2A8FC7A-FA64-46EA-AF6D-73D6B371DBF8} + Exe + Properties + nunit_agent + nunit-agent + v2.0 + 512 + + + 3.5 + + + + true + full + false + ..\..\bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + ..\..\bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + CommonAssemblyInfo.cs + + + CommonVersionInfo.cs + + + + + + + + + + + {775FAD50-3623-4922-97C4-DFB29A8BE4C7} + nunit.engine.api + + + {372A3447-D657-40FF-A089-77C19FEC30C8} + nunit.engine + + + + + \ No newline at end of file diff --git a/src/nunit-console.tests/ColorConsoleTests.cs b/src/nunit-console.tests/ColorConsoleTests.cs new file mode 100644 index 00000000..890b4720 --- /dev/null +++ b/src/nunit-console.tests/ColorConsoleTests.cs @@ -0,0 +1,77 @@ +// *********************************************************************** +// Copyright (c) 2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using NUnit.Framework; + +namespace NUnit.ConsoleRunner.Tests +{ + using Utilities; + + [TestFixture] + public class ColorConsoleTests + { + [SetUp] + public void SetUp() + { + ColorConsole.Enabled = true; + + // Set to an unknown, unlikely color so that we can test for change + Console.ForegroundColor = ConsoleColor.Magenta; + + if( Console.ForegroundColor != ConsoleColor.Magenta ) + { + Assert.Inconclusive("Color tests are inconclusive because the current console does not support color"); + } + } + + [TearDown] + public void TearDown() + { + Console.ResetColor(); + } + + [Test] + public void TestConstructor() + { + ConsoleColor expected = ColorConsole.GetColor( ColorStyle.Error ); + using ( new ColorConsole( ColorStyle.Error ) ) + { + Assert.That(Console.ForegroundColor, Is.EqualTo(expected)); + } + Assert.That( Console.ForegroundColor, Is.Not.EqualTo(expected) ); + } + + [Test] + public void TestNoColorOption() + { + ColorConsole.Enabled = false; + + using (new ColorConsole(ColorStyle.Error)) + { + Assert.That(Console.ForegroundColor, Is.EqualTo(ConsoleColor.Magenta)); + } + Assert.That(Console.ForegroundColor, Is.EqualTo(ConsoleColor.Magenta)); + } + } +} diff --git a/src/nunit-console.tests/ColorStyleTests.cs b/src/nunit-console.tests/ColorStyleTests.cs new file mode 100644 index 00000000..50804f51 --- /dev/null +++ b/src/nunit-console.tests/ColorStyleTests.cs @@ -0,0 +1,41 @@ +// *********************************************************************** +// Copyright (c) 2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using NUnit.Framework; + +namespace NUnit.ConsoleRunner.Utilities.Tests +{ + [TestFixture] + public class ColorStyleTests + { + [TestCase(ColorStyle.Pass, ConsoleColor.Green)] + [TestCase(ColorStyle.Failure, ConsoleColor.Red)] + [TestCase(ColorStyle.Warning, ConsoleColor.Yellow)] + [TestCase(ColorStyle.Error, ConsoleColor.Red)] + public void TestGetColor( ColorStyle style, ConsoleColor expected ) + { + Assert.That(ColorConsole.GetColor(style), Is.EqualTo(expected)); + } + } +} diff --git a/src/nunit-console.tests/CommandLineTests.cs b/src/nunit-console.tests/CommandLineTests.cs new file mode 100644 index 00000000..3badba64 --- /dev/null +++ b/src/nunit-console.tests/CommandLineTests.cs @@ -0,0 +1,471 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using System.Reflection; +using System.Text; +using NUnit.Engine; +using NUnit.Framework; + +namespace NUnit.ConsoleRunner.Tests +{ + using Options; + + [TestFixture] + public class CommandLineTests + { + #region General Tests + + [Test] + public void NoInputFiles() + { + ConsoleOptions options = new ConsoleOptions(); + Assert.True(options.Validate()); + Assert.AreEqual(0, options.InputFiles.Count); + } + + //[Test] + //public void AllowForwardSlashDefaultsCorrectly() + //{ + // ConsoleOptions options = new ConsoleOptions(); + // Assert.AreEqual( Path.DirectorySeparatorChar != '/', options.AllowForwardSlash ); + //} + + [TestCase("ShowHelp", "help|h")] + [TestCase("StopOnError", "stoponerror")] + [TestCase("WaitBeforeExit", "wait")] + [TestCase("PauseBeforeRun", "pause")] + [TestCase("NoHeader", "noheader|noh")] + public void CanRecognizeBooleanOptions(string propertyName, string pattern) + { + Console.WriteLine("Testing " + propertyName); + string[] prototypes = pattern.Split('|'); + + PropertyInfo property = GetPropertyInfo(propertyName); + Assert.AreEqual(typeof(bool), property.PropertyType, "Property '{0}' is wrong type", propertyName); + + foreach (string option in prototypes) + { + ConsoleOptions options = new ConsoleOptions("-" + option); + Assert.AreEqual(true, (bool)property.GetValue(options, null), "Didn't recognize -" + option); + + options = new ConsoleOptions("-" + option + "+"); + Assert.AreEqual(true, (bool)property.GetValue(options, null), "Didn't recognize -" + option + "+"); + + options = new ConsoleOptions("-" + option + "-"); + Assert.AreEqual(false, (bool)property.GetValue(options, null), "Didn't recognize -" + option + "-"); + + options = new ConsoleOptions("--" + option); + Assert.AreEqual(true, (bool)property.GetValue(options, null), "Didn't recognize --" + option); + + options = new ConsoleOptions("/" + option); + Assert.AreEqual(true, (bool)property.GetValue(options, null), "Didn't recognize /" + option); + } + } + + [TestCase("Include", "include", new string[] { "Short,Fast" }, new string[0])] + [TestCase("Exclude", "exclude", new string[] { "Long" }, new string[0])] + [TestCase("ActiveConfig", "config", new string[] { "Debug" }, new string[0])] + [TestCase("ProcessModel", "process", new string[] { "Single", "Separate", "Multiple" }, new string[] { "JUNK" })] + [TestCase("DomainUsage", "domain", new string[] { "None", "Single", "Multiple" }, new string[] { "JUNK" })] + [TestCase("Framework", "framework", new string[] { "net-4.0" }, new string[0])] + [TestCase("OutFile", "output|out", new string[] { "output.txt" }, new string[0])] + [TestCase("ErrFile", "err", new string[] { "error.txt" }, new string[0])] + [TestCase("WorkDirectory", "work", new string[] { "results" }, new string[0])] + [TestCase("DisplayTestLabels", "labels", new string[] { "Off", "On", "All" }, new string[] { "JUNK" })] + [TestCase("InternalTraceLevel", "trace", new string[] { "Off", "Error", "Warning", "Info", "Debug", "Verbose" }, new string[] { "JUNK" })] + public void CanRecognizeStringOptions(string propertyName, string pattern, string[] goodValues, string[] badValues) + { + string[] prototypes = pattern.Split('|'); + + PropertyInfo property = GetPropertyInfo(propertyName); + Assert.AreEqual(typeof(string), property.PropertyType); + + foreach (string option in prototypes) + { + foreach (string value in goodValues) + { + string optionPlusValue = string.Format("--{0}:{1}", option, value); + ConsoleOptions options = new ConsoleOptions(optionPlusValue); + Assert.True(options.Validate(), "Should be valid: " + optionPlusValue); + Assert.AreEqual(value, (string)property.GetValue(options, null), "Didn't recognize " + optionPlusValue); + } + + foreach (string value in badValues) + { + string optionPlusValue = string.Format("--{0}:{1}", option, value); + ConsoleOptions options = new ConsoleOptions(optionPlusValue); + Assert.False(options.Validate(), "Should not be valid: " + optionPlusValue); + } + } + } + + [TestCase("DefaultTimeout", "timeout")] + [TestCase("RandomSeed", "seed")] + [TestCase("NumWorkers", "workers")] + public void CanRecognizeIntOptions(string propertyName, string pattern) + { + string[] prototypes = pattern.Split('|'); + + PropertyInfo property = GetPropertyInfo(propertyName); + Assert.AreEqual(typeof(int), property.PropertyType); + + foreach (string option in prototypes) + { + ConsoleOptions options = new ConsoleOptions("--" + option + ":42"); + Assert.AreEqual(42, (int)property.GetValue(options, null), "Didn't recognize --" + option + ":text"); + } + } + + //[TestCase("InternalTraceLevel", "trace", typeof(InternalTraceLevel))] + //public void CanRecognizeEnumOptions(string propertyName, string pattern, Type enumType) + //{ + // string[] prototypes = pattern.Split('|'); + + // PropertyInfo property = GetPropertyInfo(propertyName); + // Assert.IsNotNull(property, "Property {0} not found", propertyName); + // Assert.IsTrue(property.PropertyType.IsEnum, "Property {0} is not an enum", propertyName); + // Assert.AreEqual(enumType, property.PropertyType); + + // foreach (string option in prototypes) + // { + // foreach (string name in Enum.GetNames(enumType)) + // { + // { + // ConsoleOptions options = new ConsoleOptions("--" + option + ":" + name); + // Assert.AreEqual(name, property.GetValue(options, null).ToString(), "Didn't recognize -" + option + ":" + name); + // } + // } + // } + //} + + [TestCase("--include")] + [TestCase("--exclude")] + [TestCase("--config")] + [TestCase("--process")] + [TestCase("--domain")] + [TestCase("--framework")] + [TestCase("--timeout")] + //[TestCase("--xml")] + [TestCase("--output")] + [TestCase("--err")] + [TestCase("--work")] + [TestCase("--trace")] + public void MissingValuesAreReported(string option) + { + ConsoleOptions options = new ConsoleOptions(option + "="); + Assert.False(options.Validate(), "Missing value should not be valid"); + Assert.AreEqual("Missing required value for option '" + option + "'.", options.ErrorMessages[0]); + } + + [Test] + public void AssemblyName() + { + ConsoleOptions options = new ConsoleOptions("nunit.tests.dll"); + Assert.True(options.Validate()); + Assert.AreEqual(1, options.InputFiles.Count); + Assert.AreEqual("nunit.tests.dll", options.InputFiles[0]); + } + + //[Test] + //public void FixtureNamePlusAssemblyIsValid() + //{ + // ConsoleOptions options = new ConsoleOptions( "-fixture:NUnit.Tests.AllTests", "nunit.tests.dll" ); + // Assert.AreEqual("nunit.tests.dll", options.Parameters[0]); + // Assert.AreEqual("NUnit.Tests.AllTests", options.fixture); + // Assert.IsTrue(options.Validate()); + //} + + [Test] + public void AssemblyAloneIsValid() + { + ConsoleOptions options = new ConsoleOptions("nunit.tests.dll"); + Assert.True(options.Validate()); + Assert.AreEqual(0, options.ErrorMessages.Count, "command line should be valid"); + } + + [Test] + public void InvalidOption() + { + ConsoleOptions options = new ConsoleOptions("-asembly:nunit.tests.dll"); + Assert.False(options.Validate()); + Assert.AreEqual(1, options.ErrorMessages.Count); + Assert.AreEqual("Invalid argument: -asembly:nunit.tests.dll", options.ErrorMessages[0]); + } + + + //[Test] + //public void NoFixtureNameProvided() + //{ + // ConsoleOptions options = new ConsoleOptions( "-fixture:", "nunit.tests.dll" ); + // Assert.IsFalse(options.Validate()); + //} + + [Test] + public void InvalidCommandLineParms() + { + ConsoleOptions options = new ConsoleOptions("-garbage:TestFixture", "-assembly:Tests.dll"); + Assert.False(options.Validate()); + Assert.AreEqual(2, options.ErrorMessages.Count); + Assert.AreEqual("Invalid argument: -garbage:TestFixture", options.ErrorMessages[0]); + Assert.AreEqual("Invalid argument: -assembly:Tests.dll", options.ErrorMessages[1]); + } + + #endregion + + #region Timeout Option + + [Test] + public void TimeoutIsMinusOneIfNoOptionIsProvided() + { + ConsoleOptions options = new ConsoleOptions("tests.dll"); + Assert.True(options.Validate()); + Assert.AreEqual(-1, options.DefaultTimeout); + } + + [Test] + public void TimeoutThrowsExceptionIfOptionHasNoValue() + { + Assert.Throws(() => new ConsoleOptions("tests.dll", "-timeout")); + } + + [Test] + public void TimeoutParsesIntValueCorrectly() + { + ConsoleOptions options = new ConsoleOptions("tests.dll", "-timeout:5000"); + Assert.True(options.Validate()); + Assert.AreEqual(5000, options.DefaultTimeout); + } + + [Test] + public void TimeoutCausesErrorIfValueIsNotInteger() + { + ConsoleOptions options = new ConsoleOptions("tests.dll", "-timeout:abc"); + Assert.False(options.Validate()); + Assert.AreEqual(-1, options.DefaultTimeout); + } + + #endregion + + #region EngineResult Option + + [Test] + public void ResultOptionWithFilePath() + { + ConsoleOptions options = new ConsoleOptions("tests.dll", "-result:results.xml"); + Assert.True(options.Validate()); + Assert.AreEqual(1, options.InputFiles.Count, "assembly should be set"); + Assert.AreEqual("tests.dll", options.InputFiles[0]); + + OutputSpecification spec = options.ResultOutputSpecifications[0]; + Assert.AreEqual("results.xml", spec.OutputPath); + Assert.AreEqual("nunit3", spec.Format); + Assert.Null(spec.Transform); + } + + [Test] + public void ResultOptionWithFilePathAndFormat() + { + ConsoleOptions options = new ConsoleOptions("tests.dll", "-result:results.xml;format=nunit2"); + Assert.True(options.Validate()); + Assert.AreEqual(1, options.InputFiles.Count, "assembly should be set"); + Assert.AreEqual("tests.dll", options.InputFiles[0]); + + OutputSpecification spec = options.ResultOutputSpecifications[0]; + Assert.AreEqual("results.xml", spec.OutputPath); + Assert.AreEqual("nunit2", spec.Format); + Assert.Null(spec.Transform); + } + + [Test] + public void ResultOptionWithFilePathAndTransform() + { + ConsoleOptions options = new ConsoleOptions("tests.dll", "-result:results.xml;transform=transform.xslt"); + Assert.True(options.Validate()); + Assert.AreEqual(1, options.InputFiles.Count, "assembly should be set"); + Assert.AreEqual("tests.dll", options.InputFiles[0]); + + OutputSpecification spec = options.ResultOutputSpecifications[0]; + Assert.AreEqual("results.xml", spec.OutputPath); + Assert.AreEqual("user", spec.Format); + Assert.AreEqual("transform.xslt", spec.Transform); + } + + [Test] + public void FileNameWithoutResultOptionLooksLikeParameter() + { + ConsoleOptions options = new ConsoleOptions("tests.dll", "results.xml"); + Assert.True(options.Validate()); + Assert.AreEqual(0, options.ErrorMessages.Count); + Assert.AreEqual(2, options.InputFiles.Count); + } + + [Test] + public void ResultOptionWithoutFileNameIsInvalid() + { + ConsoleOptions options = new ConsoleOptions("tests.dll", "-result:"); + Assert.False(options.Validate(), "Should not be valid"); + Assert.AreEqual(1, options.ErrorMessages.Count, "An error was expected"); + } + + [Test] + public void ResultOptionMayBeRepeated() + { + ConsoleOptions options = new ConsoleOptions("tests.dll", "-result:results.xml", "-result:nunit2results.xml;format=nunit2", "-result:myresult.xml;transform=mytransform.xslt"); + Assert.True(options.Validate(), "Should be valid"); + + var specs = options.ResultOutputSpecifications; + Assert.AreEqual(3, specs.Count); + + var spec1 = specs[0]; + Assert.AreEqual("results.xml", spec1.OutputPath); + Assert.AreEqual("nunit3", spec1.Format); + Assert.Null(spec1.Transform); + + var spec2 = specs[1]; + Assert.AreEqual("nunit2results.xml", spec2.OutputPath); + Assert.AreEqual("nunit2", spec2.Format); + Assert.Null(spec2.Transform); + + var spec3 = specs[2]; + Assert.AreEqual("myresult.xml", spec3.OutputPath); + Assert.AreEqual("user", spec3.Format); + Assert.AreEqual("mytransform.xslt", spec3.Transform); + } + + [Test] + public void DefaultResultSpecification() + { + var options = new ConsoleOptions("test.dll"); + Assert.AreEqual(1, options.ResultOutputSpecifications.Count); + + var spec = options.ResultOutputSpecifications[0]; + Assert.AreEqual("TestResult.xml", spec.OutputPath); + Assert.AreEqual("nunit3", spec.Format); + Assert.Null(spec.Transform); + } + + [Test] + public void NoResultSuppressesDefaultResultSpecification() + { + var options = new ConsoleOptions("test.dll", "-noresult"); + Assert.AreEqual(0, options.ResultOutputSpecifications.Count); + } + + [Test] + public void NoResultSuppressesAllResultSpecifications() + { + var options = new ConsoleOptions("test.dll", "-result:results.xml", "-noresult", "-result:nunit2results.xml;format=nunit2"); + Assert.AreEqual(0, options.ResultOutputSpecifications.Count); + } + + #endregion + + #region Explore Option + + [Test] + public void ExploreOptionWithoutPath() + { + ConsoleOptions options = new ConsoleOptions("tests.dll", "-explore"); + Assert.True(options.Validate()); + Assert.True(options.Explore); + } + + [Test] + public void ExploreOptionWithFilePath() + { + ConsoleOptions options = new ConsoleOptions("tests.dll", "-explore:results.xml"); + Assert.True(options.Validate()); + Assert.AreEqual(1, options.InputFiles.Count, "assembly should be set"); + Assert.AreEqual("tests.dll", options.InputFiles[0]); + Assert.True(options.Explore); + + OutputSpecification spec = options.ExploreOutputSpecifications[0]; + Assert.AreEqual("results.xml", spec.OutputPath); + Assert.AreEqual("nunit3", spec.Format); + Assert.Null(spec.Transform); + } + + [Test] + public void ExploreOptionWithFilePathAndFormat() + { + ConsoleOptions options = new ConsoleOptions("tests.dll", "-explore:results.xml;format=cases"); + Assert.True(options.Validate()); + Assert.AreEqual(1, options.InputFiles.Count, "assembly should be set"); + Assert.AreEqual("tests.dll", options.InputFiles[0]); + Assert.True(options.Explore); + + OutputSpecification spec = options.ExploreOutputSpecifications[0]; + Assert.AreEqual("results.xml", spec.OutputPath); + Assert.AreEqual("cases", spec.Format); + Assert.Null(spec.Transform); + } + + [Test] + public void ExploreOptionWithFilePathAndTransform() + { + ConsoleOptions options = new ConsoleOptions("tests.dll", "-explore:results.xml;transform=myreport.xslt"); + Assert.True(options.Validate()); + Assert.AreEqual(1, options.InputFiles.Count, "assembly should be set"); + Assert.AreEqual("tests.dll", options.InputFiles[0]); + Assert.True(options.Explore); + + OutputSpecification spec = options.ExploreOutputSpecifications[0]; + Assert.AreEqual("results.xml", spec.OutputPath); + Assert.AreEqual("user", spec.Format); + Assert.AreEqual("myreport.xslt", spec.Transform); + } + + [Test] + public void ExploreOptionWithFilePathUsingEqualSign() + { + ConsoleOptions options = new ConsoleOptions("tests.dll", "-explore=C:/nunit/tests/bin/Debug/console-test.xml"); + Assert.True(options.Validate()); + Assert.True(options.Explore); + Assert.AreEqual(1, options.InputFiles.Count, "assembly should be set"); + Assert.AreEqual("tests.dll", options.InputFiles[0]); + Assert.AreEqual("C:/nunit/tests/bin/Debug/console-test.xml", options.ExploreOutputSpecifications[0].OutputPath); + } + + #endregion + + #region Helper Methods + + private static FieldInfo GetFieldInfo(string fieldName) + { + FieldInfo field = typeof(ConsoleOptions).GetField(fieldName); + Assert.IsNotNull(field, "The field '{0}' is not defined", fieldName); + return field; + } + + private static PropertyInfo GetPropertyInfo(string propertyName) + { + PropertyInfo property = typeof(ConsoleOptions).GetProperty(propertyName); + Assert.IsNotNull(property, "The property '{0}' is not defined", propertyName); + return property; + } + + #endregion + } +} diff --git a/src/nunit-console.tests/MakeTestPackageTests.cs b/src/nunit-console.tests/MakeTestPackageTests.cs new file mode 100644 index 00000000..fb43bb67 --- /dev/null +++ b/src/nunit-console.tests/MakeTestPackageTests.cs @@ -0,0 +1,174 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System.IO; +using NUnit.Framework; + +namespace NUnit.ConsoleRunner.Tests +{ + using Options; + + public class MakeTestPackageTests + { + [Test] + public void SingleAssembly() + { + var options = new ConsoleOptions("test.dll"); + var package = ConsoleRunner.MakeTestPackage(options); + + Assert.AreEqual(1, package.TestFiles.Length); + Assert.AreEqual(Path.GetFullPath("test.dll"), package.FullName); + } + + [Test] + public void MultipleAssemblies() + { + var names = new [] { "test1.dll", "test2.dll", "test3.dll" }; + var expected = new[] { + Path.GetFullPath("test1.dll"), + Path.GetFullPath("test2.dll"), + Path.GetFullPath("test3.dll") + }; + var options = new ConsoleOptions(names); + var package = ConsoleRunner.MakeTestPackage(options); + + Assert.AreEqual(expected, package.TestFiles); + } + + [Test] + public void WhenTimeoutIsSpecified_PackageIncludesIt() + { + var options = new ConsoleOptions("test.dll", "--timeout=50"); + var package = ConsoleRunner.MakeTestPackage(options); + + Assert.That(package.Settings.ContainsKey("DefaultTimeout")); + Assert.AreEqual(50, package.Settings["DefaultTimeout"]); + } + + [Test] + public void WhenProcessModelIsSpecified_PackageIncludesIt() + { + var options = new ConsoleOptions("test.dll", "--process=Separate"); + var package = ConsoleRunner.MakeTestPackage(options); + + Assert.That(package.Settings.ContainsKey("ProcessModel")); + Assert.AreEqual("Separate", package.Settings["ProcessModel"]); + } + + [Test] + public void WhenDomainUsageIsSpecified_PackageIncludesIt() + { + var options = new ConsoleOptions("test.dll", "--domain=Multiple"); + var package = ConsoleRunner.MakeTestPackage(options); + + Assert.That(package.Settings.ContainsKey("DomainUsage")); + Assert.AreEqual("Multiple", package.Settings["DomainUsage"]); + } + + [Test] + public void WhenFrameworkIsSpecified_PackageIncludesIt() + { + var options = new ConsoleOptions("test.dll", "--framework=net-4.0"); + var package = ConsoleRunner.MakeTestPackage(options); + + Assert.That(package.Settings.ContainsKey("RuntimeFramework")); + Assert.AreEqual("net-4.0", package.Settings["RuntimeFramework"]); + } + + [Test] + public void WhenConfigIsSpecified_PackageIncludesIt() + { + var options = new ConsoleOptions("test.dll", "--config=Release"); + var package = ConsoleRunner.MakeTestPackage(options); + + Assert.That(package.Settings.ContainsKey("ActiveConfig")); + Assert.AreEqual("Release", package.Settings["ActiveConfig"]); + } + + [Test] + public void WhenTraceIsSpecified_PackageIncludesIt() + { + var options = new ConsoleOptions("test.dll", "--trace=Error"); + var package = ConsoleRunner.MakeTestPackage(options); + + Assert.That(package.Settings.ContainsKey("InternalTraceLevel")); + Assert.AreEqual("Error", package.Settings["InternalTraceLevel"]); + } + + [Test] + public void WhenSeedIsSpecified_PackageIncludesIt() + { + var options = new ConsoleOptions("test.dll", "--seed=1234"); + var package = ConsoleRunner.MakeTestPackage(options); + + Assert.That(package.Settings.ContainsKey("RandomSeed")); + Assert.AreEqual(1234, package.Settings["RandomSeed"]); + } + + [Test] + public void WhenWorkersIsSpecified_PackageIncludesIt() + { + var options = new ConsoleOptions("test.dll", "--workers=3"); + var package = ConsoleRunner.MakeTestPackage(options); + + Assert.That(package.Settings.ContainsKey("NumberOfTestWorkers")); + Assert.AreEqual(3, package.Settings["NumberOfTestWorkers"]); + } + + //[Test] + //public void EnumOptions_MayBeSpecifiedAsInts() + //{ + // var options = new ConsoleOptions("test.dll", "--trace=4"); + // var package = ConsoleRunner.MakeTestPackage(options); + + // Assert.That(package.Settings.ContainsKey("InternalTraceLevel")); + // Assert.AreEqual("Info", package.Settings["InternalTraceLevel"]); + //} + + //[Test] + //public void EnumOptions_InvalidNamesCauseAnError() + //{ + // var options = new ConsoleOptions("test.dll", "--trace=All"); + // Assert.False(options.Validate()); + //} + + //[Test] + //public void EnumOptions_OutOfRangeValuesAreUsedAsIs() + //{ + // var options = new ConsoleOptions("test.dll", "--trace=7"); + // var package = ConsoleRunner.MakeTestPackage(options); + + // Assert.That(package.Settings.ContainsKey("InternalTraceLevel")); + // Assert.AreEqual(7, package.Settings["InternalTraceLevel"]); + //} + + [Test] + public void WhenNoOptionsAreSpecified_PackageContainsNoSettings() + { + var options = new ConsoleOptions("test.dll"); + var package = ConsoleRunner.MakeTestPackage(options); + + Assert.AreEqual(0, package.Settings.Count); + } + } +} diff --git a/src/nunit-console.tests/NUnit2TestResult.xsd b/src/nunit-console.tests/NUnit2TestResult.xsd new file mode 100644 index 00000000..a71bbf92 --- /dev/null +++ b/src/nunit-console.tests/NUnit2TestResult.xsd @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/nunit-console.tests/NUnit2XmlValidationTests.cs b/src/nunit-console.tests/NUnit2XmlValidationTests.cs new file mode 100644 index 00000000..1427c27d --- /dev/null +++ b/src/nunit-console.tests/NUnit2XmlValidationTests.cs @@ -0,0 +1,81 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using System.Text; +using NUnit.Framework; + +namespace NUnit.ConsoleRunner.Tests +{ + [TestFixture] + public class NUnit2XmlValidationTests : XmlOutputTest + { + private SchemaValidator validator; + + private static readonly string schemaFile = "NUnit2TestResult.xsd"; + + [OneTimeSetUp] + public void InitializeValidator() + { + this.validator = new SchemaValidator(GetLocalPath(schemaFile)); + } + + [Test,SetCulture("")] + public void TestSchemaValidatorInvariantCulture() + { + runSchemaValidatorTest(); + } + + [Test,SetCulture("en-US")] + public void TestSchemaValidatorUnitedStatesCulture() + { + runSchemaValidatorTest(); + } + + [Test,SetCulture("fr-FR")] + public void TestSchemaValidatorFrenchCulture() + { + runSchemaValidatorTest(); + } + + #region Helper Methods + + private void runSchemaValidatorTest() + { + StringBuilder output = new StringBuilder(); + + new NUnit2XmlOutputWriter().WriteResultFile(this.EngineResult.Xml, new StringWriter(output)); + + if (!validator.Validate(new StringReader(output.ToString()))) + { + StringBuilder errors = new StringBuilder("Validation Errors:" + Environment.NewLine); + foreach (string error in validator.Errors) + errors.Append(" " + error + Environment.NewLine); + Assert.Fail(errors.ToString()); + } + } + + #endregion + } +} diff --git a/src/nunit-console.tests/OutputSpecificationTests.cs b/src/nunit-console.tests/OutputSpecificationTests.cs new file mode 100644 index 00000000..d2e6d1ad --- /dev/null +++ b/src/nunit-console.tests/OutputSpecificationTests.cs @@ -0,0 +1,125 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using NUnit.Framework; + +namespace NUnit.ConsoleRunner.Tests +{ + public class OutputSpecificationTests + { + [Test] + public void SpecMayNotBeNull() + { + Assert.That( + () => new OutputSpecification(null), + Throws.TypeOf()); + } + + + [Test] + public void SpecOptionMustContainEqualSign() + { + Assert.That( + () => new OutputSpecification("MyFile.xml;transform.xslt"), + Throws.TypeOf()); + } + + [Test] + public void SpecOptionMustContainJustOneEqualSign() + { + Assert.That( + () => new OutputSpecification("MyFile.xml;transform=xslt=transform.xslt"), + Throws.TypeOf()); + } + + [Test] + public void FileNameOnly() + { + var spec = new OutputSpecification("MyFile.xml"); + Assert.That(spec.OutputPath, Is.EqualTo("MyFile.xml")); + Assert.That(spec.Format, Is.EqualTo("nunit3")); + Assert.Null(spec.Transform); + } + + [Test] + public void FileNamePlusFormat() + { + var spec = new OutputSpecification("MyFile.xml;format=nunit2"); + Assert.That(spec.OutputPath, Is.EqualTo("MyFile.xml")); + Assert.That(spec.Format, Is.EqualTo("nunit2")); + Assert.Null(spec.Transform); + } + + [Test] + public void FileNamePlusTransform() + { + var spec = new OutputSpecification("MyFile.xml;transform=transform.xslt"); + Assert.That(spec.OutputPath, Is.EqualTo("MyFile.xml")); + Assert.That(spec.Format, Is.EqualTo("user")); + Assert.That(spec.Transform, Is.EqualTo("transform.xslt")); + } + + [Test] + public void UserFormatMayBeIndicatedExplicitlyAfterTransform() + { + var spec = new OutputSpecification("MyFile.xml;transform=transform.xslt;format=user"); + Assert.That(spec.OutputPath, Is.EqualTo("MyFile.xml")); + Assert.That(spec.Format, Is.EqualTo("user")); + Assert.That(spec.Transform, Is.EqualTo("transform.xslt")); + } + + [Test] + public void UserFormatMayBeIndicatedExplicitlyBeforeTransform() + { + var spec = new OutputSpecification("MyFile.xml;format=user;transform=transform.xslt"); + Assert.That(spec.OutputPath, Is.EqualTo("MyFile.xml")); + Assert.That(spec.Format, Is.EqualTo("user")); + Assert.That(spec.Transform, Is.EqualTo("transform.xslt")); + } + + [Test] + public void MultipleFormatSpecifiersNotAllowed() + { + Assert.That( + () => new OutputSpecification("MyFile.xml;format=nunit2;format=nunit3"), + Throws.TypeOf()); + } + + [Test] + public void MultipleTransformSpecifiersNotAllowed() + { + Assert.That( + () => new OutputSpecification("MyFile.xml;transform=transform1.xslt;transform=transform2.xslt"), + Throws.TypeOf()); + } + + [Test] + public void TransformWithNonUserFormatNotAllowed() + { + Assert.That( + () => new OutputSpecification("MyFile.xml;format=nunit2;transform=transform.xslt"), + Throws.TypeOf()); + } + } +} diff --git a/src/nunit-console.tests/Properties/AssemblyInfo.cs b/src/nunit-console.tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..fc914caa --- /dev/null +++ b/src/nunit-console.tests/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +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("nunit-console.tests")] +[assembly: AssemblyDescription("Tests of the NUnit Console Runner")] +[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("f5209525-004e-44bc-8e19-9616924467ea")] diff --git a/src/nunit-console.tests/SchemaValidator.cs b/src/nunit-console.tests/SchemaValidator.cs new file mode 100644 index 00000000..42c28d93 --- /dev/null +++ b/src/nunit-console.tests/SchemaValidator.cs @@ -0,0 +1,85 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using System.Xml.Schema; +using NUnit.Framework; + +namespace NUnit.ConsoleRunner.Tests +{ + public class SchemaValidator + { + private XmlReaderSettings settings; + private List errors; + + public SchemaValidator(string schemaFile) + { + this.settings = new XmlReaderSettings(); + settings.ValidationType = ValidationType.Schema; + settings.Schemas.Add(XmlSchema.Read( + new StreamReader(schemaFile), + new ValidationEventHandler(ValidationEventHandle))); + } + + public string[] Errors + { + get { return errors.ToArray(); } + } + + public bool Validate(string xmlFile) + { + return Validate(new StreamReader(xmlFile)); + } + + public bool Validate(TextReader rdr) + { + this.errors = new List(); + + XmlReader myXmlValidatingReader = XmlReader.Create(rdr, this.settings); + + try + { + // Read XML data + while (myXmlValidatingReader.Read()) { } + } + catch (Exception e) + { + Assert.Fail(e.Message); + } + finally + { + myXmlValidatingReader.Close(); + } + + return errors.Count == 0; + } + + public void ValidationEventHandle(object sender, ValidationEventArgs args) + { + errors.Add(args.Message); + } + } +} diff --git a/src/nunit-console.tests/TestFilterBuilderTests.cs b/src/nunit-console.tests/TestFilterBuilderTests.cs new file mode 100644 index 00000000..df273c1a --- /dev/null +++ b/src/nunit-console.tests/TestFilterBuilderTests.cs @@ -0,0 +1,148 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Xml; +using NUnit.Engine; +using NUnit.Framework; + +namespace NUnit.ConsoleRunner.Utilities.Tests +{ + public class TestFilterBuilderTests + { + TestFilterBuilder builder; + + [SetUp] + public void CreateBuilder() + { + this.builder = new TestFilterBuilder(); + } + + [Test] + public void EmptyFilter() + { + TestFilter filter = builder.GetFilter(); + + Assert.That(filter.Text, Is.EqualTo("")); + Assert.That(filter.Xml.Name, Is.EqualTo("filter")); + Assert.That(filter.Xml.ChildNodes.Count, Is.EqualTo(0)); + } + + [Test] + public void OneTestSelected() + { + builder.Tests.Add("My.Test.Name"); + TestFilter filter = builder.GetFilter(); + + string expectedText = "My.Test.Name"; + Assert.That(filter.Text, Is.EqualTo(expectedText)); + Assert.That(filter.Xml.Name, Is.EqualTo("filter")); + Assert.That(filter.Xml.SelectSingleNode("tests/test").InnerText, Is.EqualTo("My.Test.Name")); + } + + [Test] + public void ThreeTestsSelected() + { + builder.Tests.Add("My.First.Test"); + builder.Tests.Add("My.Second.Test"); + builder.Tests.Add("My.Third.Test"); + TestFilter filter = builder.GetFilter(); + + string expectedText = "My.First.TestMy.Second.TestMy.Third.Test"; + Assert.That(filter.Text, Is.EqualTo(expectedText)); + Assert.That(filter.Xml.Name, Is.EqualTo("filter")); + XmlNodeList testNodes = filter.Xml.SelectNodes("tests/test"); + Assert.That(testNodes.Count, Is.EqualTo(3)); + Assert.That(testNodes[0].InnerText, Is.EqualTo("My.First.Test")); + Assert.That(testNodes[1].InnerText, Is.EqualTo("My.Second.Test")); + Assert.That(testNodes[2].InnerText, Is.EqualTo("My.Third.Test")); + } + + [Test] + public void OneCategoryIncluded() + { + builder.Include.Add("Dummy"); + TestFilter filter = builder.GetFilter(); + + string expectedText = "Dummy"; + Assert.That(filter.Text, Is.EqualTo(expectedText)); + } + + [Test] + public void ThreeCategoriesIncluded() + { + builder.Include.Add("Dummy"); + builder.Include.Add("Another"); + builder.Include.Add("StillAnother"); + TestFilter filter = builder.GetFilter(); + + string expectedText = "Dummy,Another,StillAnother"; + Assert.That(filter.Text, Is.EqualTo(expectedText)); + } + + [Test] + public void OneCategoryExcluded() + { + builder.Exclude.Add("Dummy"); + TestFilter filter = builder.GetFilter(); + + string expectedText = "Dummy"; + Assert.That(filter.Text, Is.EqualTo(expectedText)); + } + + [Test] + public void ThreeCategoriesExcluded() + { + builder.Exclude.Add("Dummy"); + builder.Exclude.Add("Another"); + builder.Exclude.Add("StillAnother"); + TestFilter filter = builder.GetFilter(); + + string expectedText = "Dummy,Another,StillAnother"; + Assert.That(filter.Text, Is.EqualTo(expectedText)); + } + + [Test] + public void OneTestAndOneCategory() + { + builder.Tests.Add("My.Test.Name"); + builder.Include.Add("Dummy"); + TestFilter filter = builder.GetFilter(); + + string expectedText = "My.Test.NameDummy"; + Assert.That(filter.Text, Is.EqualTo(expectedText)); + } + + [Test] + public void TwoCategoriesIncludedAndOneExcluded() + { + builder.Include.Add("Dummy"); + builder.Include.Add("Another"); + builder.Exclude.Add("Slow"); + TestFilter filter = builder.GetFilter(); + + string expectedText = "Dummy,AnotherSlow"; + Assert.That(filter.Text, Is.EqualTo(expectedText)); + } + } +} diff --git a/src/nunit-console.tests/TestNameParserTests.cs b/src/nunit-console.tests/TestNameParserTests.cs new file mode 100644 index 00000000..e9ec57c9 --- /dev/null +++ b/src/nunit-console.tests/TestNameParserTests.cs @@ -0,0 +1,60 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using NUnit.Framework; + +namespace NUnit.ConsoleRunner.Utilities.Tests +{ + public class TestNameParserTests + { + [TestCase("Test.Namespace.Fixture.Method")] + [TestCase("Test.Namespace.Fixture.Method,")] + [TestCase(" Test.Namespace.Fixture.Method ")] + [TestCase(" Test.Namespace.Fixture.Method ,")] + [TestCase("Test.Namespace.Fixture.Method()")] + [TestCase("Test.Namespace.Fixture.Method(\"string,argument\")")] + [TestCase("Test.Namespace.Fixture.Method(1,2,3)")] + [TestCase("Test.Namespace.Fixture.Method()")] + [TestCase("Test.Namespace.Fixture.Method(\")\")")] + public void SingleName(string name) + { + string[] names = TestNameParser.Parse(name); + Assert.AreEqual(1, names.Length); + Assert.AreEqual(name.Trim(new char[] { ' ', ',' }), names[0]); + } + + [TestCase("Test.Namespace.Fixture.Method1", "Test.Namespace.Fixture.Method2")] + [TestCase("Test.Namespace.Fixture.Method1", "Test.Namespace.Fixture.Method2,")] // <= trailing comma + [TestCase("Test.Namespace.Fixture.Method1(1,2)", "Test.Namespace.Fixture.Method2(3,4)")] + [TestCase("Test.Namespace.Fixture.Method1(\"(\")", "Test.Namespace.Fixture.Method2(\"<\")")] + public void TwoNames(string name1, string name2) + { + char[] delims = new char[] { ' ', ',' }; + string[] names = TestNameParser.Parse(name1 + "," + name2); + Assert.AreEqual(2, names.Length); + Assert.AreEqual(name1.Trim(delims), names[0]); + Assert.AreEqual(name2.Trim(delims), names[1]); + } + } +} diff --git a/src/nunit-console.tests/TextSummary.xslt b/src/nunit-console.tests/TextSummary.xslt new file mode 100644 index 00000000..0b9048c5 --- /dev/null +++ b/src/nunit-console.tests/TextSummary.xslt @@ -0,0 +1,42 @@ + + + + + + + + + + NUnit Version + + + + + + + + Runtime Environment - + OS Version: + + + CLR Version: + + + + Tests Run: + + + , Passed: + + , Failed: + + , Inconclusive: + + , Skipped: + + + , Elapsed Time: + + + + diff --git a/src/nunit-console.tests/XmlHelperTests.cs b/src/nunit-console.tests/XmlHelperTests.cs new file mode 100644 index 00000000..6e501031 --- /dev/null +++ b/src/nunit-console.tests/XmlHelperTests.cs @@ -0,0 +1,51 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Xml; +using NUnit.Framework; + +namespace NUnit.ConsoleRunner.Utilities.Tests +{ + public class XmlHelperTests + { + [Test] + public void SingleElement() + { + XmlNode node = XmlHelper.CreateTopLevelElement("myelement"); + + Assert.That(node.Name, Is.EqualTo("myelement")); + Assert.That(node.Attributes.Count, Is.EqualTo(0)); + Assert.That(node.ChildNodes.Count, Is.EqualTo(0)); + } + + [Test] + public void SafeAttributeAccess() + { + XmlNode node = XmlHelper.CreateTopLevelElement("top"); + + Assert.That(node.GetAttribute("junk"), Is.Null); + } + } +} diff --git a/src/nunit-console.tests/XmlOutputTest.cs b/src/nunit-console.tests/XmlOutputTest.cs new file mode 100644 index 00000000..a71fe9f8 --- /dev/null +++ b/src/nunit-console.tests/XmlOutputTest.cs @@ -0,0 +1,94 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; + +namespace NUnit.ConsoleRunner.Tests +{ + using Engine; + using Engine.Internal; + using Framework; + + using Runner = NUnit.Framework.Api.NUnitTestAssemblyRunner; + using Builder = NUnit.Framework.Api.DefaultTestAssemblyBuilder; + using TestListener = NUnit.Framework.Internal.TestListener; + using TestFilter = NUnit.Framework.Internal.TestFilter; + + /// + /// This is the abstract base for all XML output tests, + /// which need to work on a TestEngineResult. Creating a + /// second level engine in the test domain causes + /// problems, so this class uses internal framework + /// classes to run the test and then transforms the XML + /// result into a TestEngineResult for use by derived tests. + /// + public abstract class XmlOutputTest + { + private ITestEngine engine; + private string localDirectory; + + protected TestEngineResult EngineResult { get; private set; } + + // Method used by deribed classes to get the path to a file name + protected string GetLocalPath(string fileName) + { + return Path.Combine(localDirectory, fileName); + } + + [OneTimeSetUp] + public void InitializeTestEngineResult() + { + // Save the local directory - used by GetLocalPath + Uri uri = new Uri(Assembly.GetExecutingAssembly().CodeBase); + localDirectory = Path.GetDirectoryName(uri.LocalPath); + + // Create a fresh copy of the engine, since we can't use the + // one that is running this test. + engine = TestEngineActivator.CreateInstance(); + engine.InternalTraceLevel = InternalTraceLevel.Off; + + // Create a new DefaultAssemblyRunner, which is actually a framework class, + // because we can't use the one that's currently running this test. + var runner = new Runner(new Builder()); + var assemblyPath = GetLocalPath("mock-assembly.dll"); + var settings = new Dictionary(); + + // Make sure the runner loaded the mock assembly. + Assert.That( + runner.Load(assemblyPath, settings).RunState.ToString(), + Is.EqualTo("Runnable"), + "Unable to load mock-assembly.dll"); + + // Run the tests, saving the result as an XML string + var xmlText = runner.Run(TestListener.NULL, TestFilter.Empty).ToXml(true).OuterXml; + + // Create a TestEngineResult from the string, just as the TestEngine does, + // then add a test-run element to the result, wrapping the result so it + // looks just like what the engine would return! + this.EngineResult = new TestEngineResult(xmlText).Aggregate("test-run", "NAME", "FULLNAME"); + } + } +} diff --git a/src/nunit-console.tests/XmlTransformOutputWriterTests.cs b/src/nunit-console.tests/XmlTransformOutputWriterTests.cs new file mode 100644 index 00000000..b0017eeb --- /dev/null +++ b/src/nunit-console.tests/XmlTransformOutputWriterTests.cs @@ -0,0 +1,51 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System.IO; +using NUnit.Framework; + +namespace NUnit.ConsoleRunner.Tests +{ + public class XmlTransformOutputWriterTests : XmlOutputTest + { + [Test] + public void SummaryTransformTest() + { + var transformPath = GetLocalPath("TextSummary.xslt"); + StringWriter writer = new StringWriter(); + new XmlTransformOutputWriter(transformPath).WriteResultFile(EngineResult.Xml, writer); + + string summary = string.Format( + "Tests Run: {0}, Passed: {1}, Failed: {2}, Inconclusive: {3}, Skipped: {4}", + EngineResult.Xml.Attributes["total"].Value, + EngineResult.Xml.Attributes["passed"].Value, + EngineResult.Xml.Attributes["failed"].Value, + EngineResult.Xml.Attributes["inconclusive"].Value, + EngineResult.Xml.Attributes["skipped"].Value); + + string output = writer.GetStringBuilder().ToString(); + + Assert.That(output, Contains.Substring(summary)); + } + } +} diff --git a/src/nunit-console.tests/nunit-console.tests.build b/src/nunit-console.tests/nunit-console.tests.build new file mode 100644 index 00000000..bc5958de --- /dev/null +++ b/src/nunit-console.tests/nunit-console.tests.build @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/nunit-console.tests/nunit-console.tests.csproj b/src/nunit-console.tests/nunit-console.tests.csproj new file mode 100644 index 00000000..e4bea7c0 --- /dev/null +++ b/src/nunit-console.tests/nunit-console.tests.csproj @@ -0,0 +1,99 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {B310A760-8AE1-41CA-81F8-03B12E2FCE30} + Library + Properties + NUnit.ConsoleRunner.Tests + nunit-console.tests + v2.0 + 512 + + + 3.5 + + + + true + full + false + ..\..\bin\Debug\ + TRACE;DEBUG;CLR_4_0;CS_4_0 + prompt + 4 + + + pdbonly + true + ..\..\bin\Release\ + TRACE;CLR_4_0;CS_4_0 + prompt + 4 + + + + False + ..\..\lib\nunit.framework.dll + + + + + + + + CommonAssemblyInfo.cs + + + CommonVersionInfo.cs + + + + + + + + + + + + + + + + + + PreserveNewest + + + + Designer + PreserveNewest + + + + + {0DE218CA-AFB8-423A-9CD2-E22DEAC55C46} + nunit-console + + + {775fad50-3623-4922-97c4-dfb29a8be4c7} + nunit.engine.api + + + {372a3447-d657-40ff-a089-77c19fec30c8} + nunit.engine + + + + + \ No newline at end of file diff --git a/src/nunit-console/App.ico b/src/nunit-console/App.ico new file mode 100644 index 00000000..3a5525fd Binary files /dev/null and b/src/nunit-console/App.ico differ diff --git a/src/nunit-console/ConsoleRunner.cs b/src/nunit-console/ConsoleRunner.cs new file mode 100644 index 00000000..1587a66b --- /dev/null +++ b/src/nunit-console/ConsoleRunner.cs @@ -0,0 +1,301 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using System.Xml; +using NUnit.Engine; + +namespace NUnit.ConsoleRunner +{ + using Options; + using Utilities; + + /// + /// ConsoleRunner provides the nunit-console text-based + /// user interface, running the tests and reporting the results. + /// + public class ConsoleRunner + { + #region Console Runner Return Codes + + public static readonly int OK = 0; + public static readonly int INVALID_ARG = -1; + public static readonly int FILE_NOT_FOUND = -2; + public static readonly int FIXTURE_NOT_FOUND = -3; + public static readonly int UNEXPECTED_ERROR = -100; + + #endregion + + #region Instance Fields + + private ITestEngine _engine; + private ConsoleOptions _options; + + private TextWriter _outWriter = Console.Out; + private TextWriter _errorWriter = Console.Error; + + private string _workDirectory; + + #endregion + + #region Constructor + + public ConsoleRunner(ITestEngine engine, ConsoleOptions options) + { + _engine = engine; + _options = options; + _workDirectory = options.WorkDirectory; + if (_workDirectory == null) + _workDirectory = Environment.CurrentDirectory; + else if (!Directory.Exists(_workDirectory)) + Directory.CreateDirectory(_workDirectory); + } + + #endregion + + #region Execute Method + + /// + /// Executes tests according to the provided commandline options. + /// + /// + public int Execute() + { + TestPackage package = MakeTestPackage(_options); + + TestFilter filter = CreateTestFilter(_options); + + if (_options.Explore) + return ExploreTests(package, filter); + else + return RunTests(package, filter); + } + + #endregion + + #region Helper Methods + + private int ExploreTests(TestPackage package, TestFilter filter) + { + XmlNode result = null; + + using (var runner = _engine.GetRunner(package)) + result = runner.Explore(filter); + + if (_options.ExploreOutputSpecifications.Count == 0) + { + new TestCaseOutputWriter().WriteResultFile(result, Console.Out); + } + else + { + var outputManager = new OutputManager(result, _workDirectory); + + foreach (OutputSpecification spec in _options.ExploreOutputSpecifications) + outputManager.WriteTestFile(spec); + } + + return ConsoleRunner.OK; + } + + private int RunTests(TestPackage package, TestFilter filter) + { + // TODO: We really need options as resolved by engine for most of these + DisplayRequestedOptions(); + + // TODO: Incorporate this in EventCollector? + RedirectOutputAsRequested(); + + var labels = _options.DisplayTestLabels != null + ? _options.DisplayTestLabels.ToUpperInvariant() + : "ON"; + TestEventHandler eventHandler = new TestEventHandler(_outWriter, labels); + + XmlNode result = null; + + // Save things that might be messed up by a bad test + TextWriter savedOut = Console.Out; + TextWriter savedError = Console.Error; + + DateTime startTime = DateTime.Now; + + try + { + using ( new ColorConsole( ColorStyle.Output ) ) + using (ITestRunner runner = _engine.GetRunner(package)) + { + result = runner.Run(eventHandler, filter); + } + } + finally + { + Console.SetOut(savedOut); + Console.SetError(savedError); + + RestoreOutput(); + } + + //Console.WriteLine(); + + ResultReporter reporter = new ResultReporter(result, _options); + reporter.ReportResults(); + + // TODO: Inject this? + var outputManager = new OutputManager(result, _workDirectory); + + foreach (var outputSpec in _options.ResultOutputSpecifications) + outputManager.WriteResultFile(outputSpec, startTime); + + return reporter.Summary.ErrorsAndFailures; + } + + private void DisplayRequestedOptions() + { + ColorConsole.WriteLine(ColorStyle.SectionHeader, "Options"); + ColorConsole.WriteLabel(" ProcessModel: ", _options.ProcessModel ?? "Default", false); + ColorConsole.WriteLabel(" DomainUsage: ", _options.DomainUsage ?? "Default", true); + ColorConsole.WriteLabel(" Execution Runtime: ", _options.Framework ?? "Not Specified", true); + if (_options.DefaultTimeout >= 0) + ColorConsole.WriteLabel(" Default timeout: ", _options.DefaultTimeout.ToString(), true); + if (_options.NumWorkers > 0) + ColorConsole.WriteLabel(" Worker Threads: ", _options.NumWorkers.ToString(), true); + ColorConsole.WriteLabel(" Work Directory: ", _workDirectory, true); + ColorConsole.WriteLabel(" Internal Trace: ", _options.InternalTraceLevel ?? "Off", true); + //if (options.DisplayTeamCityServiceMessages) + // ColorConsole.WriteLine(" Display TeamCity Service Messages"); + Console.WriteLine(); + + if (_options.TestList.Count > 0) + { + ColorConsole.WriteLine(ColorStyle.Label, "Selected test(s):"); + using (new ColorConsole(ColorStyle.Default)) + foreach (string testName in _options.TestList) + Console.WriteLine(" " + testName); + } + + if (!string.IsNullOrEmpty( _options.Include )) + ColorConsole.WriteLabel("Included categories: ", _options.Include, true); + + if (!string.IsNullOrEmpty( _options.Exclude )) + ColorConsole.WriteLabel("Excluded categories: ", _options.Exclude, true); + } + + private void RedirectOutputAsRequested() + { + if (_options.OutFile != null) + { + var outStreamWriter = new StreamWriter(Path.Combine(_workDirectory, _options.OutFile)); + outStreamWriter.AutoFlush = true; + _outWriter = outStreamWriter; + } + + if (_options.ErrFile != null) + { + var errorStreamWriter = new StreamWriter(Path.Combine(_workDirectory, _options.ErrFile)); + errorStreamWriter.AutoFlush = true; + _errorWriter = errorStreamWriter; + } + } + + private void RestoreOutput() + { + _outWriter.Flush(); + if (_options.OutFile != null) + _outWriter.Close(); + + _errorWriter.Flush(); + if (_options.ErrFile != null) + _errorWriter.Close(); + } + + // This is public static for ease of testing + public static TestPackage MakeTestPackage( ConsoleOptions options ) + { + TestPackage package = options.InputFiles.Count == 1 + ? new TestPackage(options.InputFiles[0]) + : new TestPackage(options.InputFiles); + + if (options.ProcessModel != null)//ProcessModel.Default) + package.Settings[PackageSettings.ProcessModel] = options.ProcessModel; + + if (options.DomainUsage != null) + package.Settings[PackageSettings.DomainUsage] = options.DomainUsage; + + if (options.Framework != null) + package.Settings[PackageSettings.RuntimeFramework] = options.Framework; + + if (options.DefaultTimeout >= 0) + package.Settings[PackageSettings.DefaultTimeout] = options.DefaultTimeout; + + if (options.InternalTraceLevel != null) + package.Settings[PackageSettings.InternalTraceLevel] = options.InternalTraceLevel; + + if (options.ActiveConfig != null) + package.Settings[PackageSettings.ActiveConfig] = options.ActiveConfig; + + if (options.WorkDirectory != null) + package.Settings[PackageSettings.WorkDirectory] = options.WorkDirectory; + + if (options.StopOnError) + package.Settings[PackageSettings.StopOnError] = true; + + if (options.NumWorkers > 0) + package.Settings[PackageSettings.NumberOfTestWorkers] = options.NumWorkers; + + if (options.RandomSeed > 0) + package.Settings[PackageSettings.RandomSeed] = options.RandomSeed; + + if (options.Verbose) + package.Settings["Verbose"] = true; + +#if DEBUG + //foreach (KeyValuePair entry in package.Settings) + // if (!(entry.Value is string || entry.Value is int || entry.Value is bool)) + // throw new Exception(string.Format("Package setting {0} is not a valid type", entry.Key)); +#endif + + return package; + } + + // This is public static for ease of testing + public static TestFilter CreateTestFilter(ConsoleOptions options) + { + TestFilterBuilder builder = new TestFilterBuilder(); + foreach (string testName in options.TestList) + builder.Tests.Add(testName); + + // TODO: Support multiple include / exclude options + + if (options.Include != null) + builder.Include.Add(options.Include); + + if (options.Exclude != null) + builder.Exclude.Add(options.Exclude); + + return builder.GetFilter(); + } + + #endregion + } +} + diff --git a/src/nunit-console/IResultWriter.cs b/src/nunit-console/IResultWriter.cs new file mode 100644 index 00000000..4451baea --- /dev/null +++ b/src/nunit-console/IResultWriter.cs @@ -0,0 +1,34 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using System.Xml; + +namespace NUnit.ConsoleRunner +{ + public interface IResultWriter + { + void WriteResultFile(XmlNode resultNode, string outputPath); + } +} diff --git a/src/nunit-console/Options/ConsoleOptions.cs b/src/nunit-console/Options/ConsoleOptions.cs new file mode 100644 index 00000000..a4c6dd1d --- /dev/null +++ b/src/nunit-console/Options/ConsoleOptions.cs @@ -0,0 +1,315 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.IO; +using Mono.Options; + +namespace NUnit.ConsoleRunner.Options +{ + using Utilities; + + /// + /// ConsoleOptions encapsulates the option settings for + /// the nunit-console program. It inherits from the Mono + /// Options OptionSet class and provides a central location + /// for defining and parsing options. + /// + public class ConsoleOptions : OptionSet + { + private bool validated; + private bool noresult; + + #region Constructor + + public ConsoleOptions(params string[] args) + { + // NOTE: The order in which patterns are added + // determines the display order for the help. + + // Old Options no longer supported: + // fixture + // xmlConsole + // noshadow + // nothread + // nodots + + // Options to be added: + // teamcity + // workers + + // Select Tests + this.Add("test=", "Comma-separated list of {NAMES} of tests to run or explore. This option may be repeated.", + v => ((List)TestList).AddRange(TestNameParser.Parse(RequiredValue(v, "--test")))); + + this.Add("include=", "Test {CATEGORIES} to be included. May be a single category, a comma-separated list of categories or a category expression.", + v => Include = RequiredValue(v, "--include")); + + this.Add("exclude=", "Test {CATEGORIES} to be excluded. May be a single category, a comma-separated list of categories or a category expression.", + v => Exclude = RequiredValue(v, "--exclude")); + + this.Add("config=", "{NAME} of a project configuration to load (e.g.: Debug).", + v => ActiveConfig = RequiredValue(v, "--config")); + + // Where to Run Tests + this.Add("process=", "{PROCESS} isolation for test assemblies.\nValues: Single, Separate, Multiple", + v => ProcessModel = RequiredValue(v, "--process", "Single", "Separate", "Multiple")); + + this.Add("domain=", "{DOMAIN} isolation for test assemblies.\nValues: None, Single, Multiple", + v => DomainUsage = RequiredValue(v, "--domain", "None", "Single", "Multiple")); + + // How to Run Tests + this.Add("framework=", "{FRAMEWORK} type/version to use for tests.\nExamples: mono, net-3.5, v4.0, 2.0, mono-4.0", + v => Framework = RequiredValue(v, "--framework")); + + this.Add("timeout=", "Set timeout for each test case in {MILLISECONDS}.", + v => defaultTimeout = RequiredInt(v, "--timeout")); + + this.Add("seed=", "Set the random {SEED} used to generate test cases.", + v => randomSeed = RequiredInt(v, "--seed")); + + this.Add("workers=", "Specify the {NUMBER} of worker threads to be used in running tests.", + v => numWorkers = RequiredInt(v, "--workers")); + + this.Add("stoponerror", "Stop run immediately upon any test failure or error.", + v => StopOnError = v != null); + + this.Add("wait", "Wait for input before closing console window.", + v => WaitBeforeExit = v != null); + + this.Add("pause", "Pause before run to allow debugging.", + v => PauseBeforeRun = v != null); + + // Output Control + this.Add("work=", "{PATH} of the directory to use for output files.", + v => WorkDirectory = RequiredValue(v, "--work")); + + this.Add("output|out=", "File {PATH} to contain text output from the tests.", + v => OutFile = RequiredValue(v, "--output")); + + this.Add("err=", "File {PATH} to contain error output from the tests.", + v => ErrFile = RequiredValue(v, "--err")); + + this.Add("result=", "An output {SPEC} for saving the test results.\nThis option may be repeated.", + v => resultOutputSpecifications.Add(new OutputSpecification(RequiredValue(v, "--resultxml")))); + + this.Add("explore:", "Display or save test info rather than running tests. Optionally provide an output {SPEC} for saving the test info. This option may be repeated.", v => + { + Explore = true; + if (v != null) + ExploreOutputSpecifications.Add(new OutputSpecification(v)); + }); + + this.Add("noresult", "Don't save any test results.", + v => noresult = v != null); + + this.Add("labels=", "Specify whether to write test case names to the output. Values: Off, On, All", + v => DisplayTestLabels = RequiredValue(v, "--labels", "Off", "On", "All")); + + this.Add("trace=", "Set internal trace {LEVEL}.\nValues: Off, Error, Warning, Info, Verbose (Debug)", + v => InternalTraceLevel = RequiredValue(v, "--trace", "Off", "Error", "Warning", "Info", "Verbose", "Debug")); + + this.Add("noheader|noh", "Suppress display of program information at start of run.", + v => NoHeader = v != null); + + this.Add("nocolor|noc", "Displays console output without color.", + v => NoColor = v != null); + + this.Add("verbose|v", "Display additional information as the test runs.", + v => Verbose = v != null); + + this.Add("help|h", "Display this message and exit.", + v => ShowHelp = v != null); + + // Default + this.Add("<>", v => + { + if (v.StartsWith("-") || v.StartsWith("/") && Path.DirectorySeparatorChar != '/') + ErrorMessages.Add("Invalid argument: " + v); + else + InputFiles.Add(v); + }); + + if (args != null) + this.Parse(args); + } + + #endregion + + #region Properties + + // Action to Perform + + public bool Explore { get; private set; } + + public bool ShowHelp { get; private set; } + + + // Select tests + + private List inputFiles = new List(); + public IList InputFiles { get { return inputFiles; } } + + private List testList = new List(); + public IList TestList { get { return testList; } } + + public string Include { get; private set; } + + public string Exclude { get; private set; } + + public string ActiveConfig { get; private set; } + + // Where to Run Tests + + public string ProcessModel { get; private set; } + + public string DomainUsage { get; private set; } + + // How to Run Tests + + public string Framework { get; private set; } + + private int defaultTimeout = -1; + public int DefaultTimeout { get { return defaultTimeout; } } + + private int randomSeed = -1; + public int RandomSeed { get { return randomSeed; } } + + private int numWorkers = -1; + public int NumWorkers { get { return numWorkers; } } + + public bool StopOnError { get; private set; } + + public bool WaitBeforeExit { get; private set; } + + public bool PauseBeforeRun { get; private set; } + + // Output Control + + public bool NoHeader { get; private set; } + + public bool NoColor { get; private set; } + + public bool Verbose { get; private set; } + + public string OutFile { get; private set; } + + public string ErrFile { get; private set; } + + public string DisplayTestLabels { get; private set; } + + public string WorkDirectory { get; private set; } + + public string InternalTraceLevel { get; private set; } + + private List resultOutputSpecifications = new List(); + public IList ResultOutputSpecifications + { + get + { + if (noresult) + return new OutputSpecification[0]; + + if (resultOutputSpecifications.Count == 0) + resultOutputSpecifications.Add(new OutputSpecification("TestResult.xml")); + + return resultOutputSpecifications; + } + } + + private List exploreOutputSpecifications = new List(); + public IList ExploreOutputSpecifications { get { return exploreOutputSpecifications; } } + + // Error Processing + + public List errorMessages = new List(); + public IList ErrorMessages { get { return errorMessages; } } + + #endregion + + #region Public Methods + + public bool Validate() + { + if (!validated) + { + // Additional Checks here + + validated = true; + } + + return ErrorMessages.Count == 0; + } + + #endregion + + #region Helper Methods + + private string RequiredValue(string val, string option, params string[] validValues) + { + if (val == null || val == string.Empty) + ErrorMessages.Add("Missing required value for option '" + option + "'."); + + bool isValid = true; + + if (validValues != null && validValues.Length > 0) + { + isValid = false; + + foreach (string valid in validValues) + if (string.Compare(valid, val, true) == 0) + isValid = true; + + } + + if (!isValid) + ErrorMessages.Add(string.Format("The value '{0}' is not valid for option '{1}'.", val, option)); + + return val; + } + + private int RequiredInt(string val, string option) + { + // We have to return something even though the value will + // be ignored if an error is reported. The -1 value seems + // like a safe bet in case it isn't ignored due to a bug. + int result = -1; + + if (val == null || val == string.Empty) + ErrorMessages.Add("Missing required value for option '" + option + "'."); + else + { + int r; + if (int.TryParse(val, out r)) + result = r; + else + ErrorMessages.Add("An int value was exprected for option '{0}' but a value of '{1}' was used"); + } + + return result; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/nunit-console/Options/Options.cs b/src/nunit-console/Options/Options.cs new file mode 100644 index 00000000..ec6ea187 --- /dev/null +++ b/src/nunit-console/Options/Options.cs @@ -0,0 +1,1101 @@ +// +// Options.cs +// +// Authors: +// Jonathan Pryor +// +// Copyright (C) 2008 Novell (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +// Compile With: +// gmcs -debug+ -r:System.Core Options.cs -o:NDesk.Options.dll +// gmcs -debug+ -d:LINQ -r:System.Core Options.cs -o:NDesk.Options.dll +// +// The LINQ version just changes the implementation of +// OptionSet.Parse(IEnumerable), and confers no semantic changes. + +// +// A Getopt::Long-inspired option parsing library for C#. +// +// NDesk.Options.OptionSet is built upon a key/value table, where the +// key is a option format string and the value is a delegate that is +// invoked when the format string is matched. +// +// Option format strings: +// Regex-like BNF Grammar: +// name: .+ +// type: [=:] +// sep: ( [^{}]+ | '{' .+ '}' )? +// aliases: ( name type sep ) ( '|' name type sep )* +// +// Each '|'-delimited name is an alias for the associated action. If the +// format string ends in a '=', it has a required value. If the format +// string ends in a ':', it has an optional value. If neither '=' or ':' +// is present, no value is supported. `=' or `:' need only be defined on one +// alias, but if they are provided on more than one they must be consistent. +// +// Each alias portion may also end with a "key/value separator", which is used +// to split option values if the option accepts > 1 value. If not specified, +// it defaults to '=' and ':'. If specified, it can be any character except +// '{' and '}' OR the *string* between '{' and '}'. If no separator should be +// used (i.e. the separate values should be distinct arguments), then "{}" +// should be used as the separator. +// +// Options are extracted either from the current option by looking for +// the option name followed by an '=' or ':', or is taken from the +// following option IFF: +// - The current option does not contain a '=' or a ':' +// - The current option requires a value (i.e. not a Option type of ':') +// +// The `name' used in the option format string does NOT include any leading +// option indicator, such as '-', '--', or '/'. All three of these are +// permitted/required on any named option. +// +// Option bundling is permitted so long as: +// - '-' is used to start the option group +// - all of the bundled options are a single character +// - at most one of the bundled options accepts a value, and the value +// provided starts from the next character to the end of the string. +// +// This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value' +// as '-Dname=value'. +// +// Option processing is disabled by specifying "--". All options after "--" +// are returned by OptionSet.Parse() unchanged and unprocessed. +// +// Unprocessed options are returned from OptionSet.Parse(). +// +// Examples: +// int verbose = 0; +// OptionSet p = new OptionSet () +// .Add ("v", v => ++verbose) +// .Add ("name=|value=", v => Console.WriteLine (v)); +// p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"}); +// +// The above would parse the argument string array, and would invoke the +// lambda expression three times, setting `verbose' to 3 when complete. +// It would also print out "A" and "B" to standard output. +// The returned array would contain the string "extra". +// +// C# 3.0 collection initializers are supported and encouraged: +// var p = new OptionSet () { +// { "h|?|help", v => ShowHelp () }, +// }; +// +// System.ComponentModel.TypeConverter is also supported, allowing the use of +// custom data types in the callback type; TypeConverter.ConvertFromString() +// is used to convert the value option to an instance of the specified +// type: +// +// var p = new OptionSet () { +// { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) }, +// }; +// +// Random other tidbits: +// - Boolean options (those w/o '=' or ':' in the option format string) +// are explicitly enabled if they are followed with '+', and explicitly +// disabled if they are followed with '-': +// string a = null; +// var p = new OptionSet () { +// { "a", s => a = s }, +// }; +// p.Parse (new string[]{"-a"}); // sets v != null +// p.Parse (new string[]{"-a+"}); // sets v != null +// p.Parse (new string[]{"-a-"}); // sets v == null +// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.Runtime.Serialization; +using System.Security.Permissions; +using System.Text; +using System.Text.RegularExpressions; + +#if LINQ +using System.Linq; +#endif + +#if TEST +using NDesk.Options; +#endif + +#if NDESK_OPTIONS +namespace NDesk.Options +#else +namespace Mono.Options +#endif +{ + public class OptionValueCollection : IList, IList { + + List values = new List (); + OptionContext c; + + internal OptionValueCollection (OptionContext c) + { + this.c = c; + } + + #region ICollection + void ICollection.CopyTo (Array array, int index) {(values as ICollection).CopyTo (array, index);} + bool ICollection.IsSynchronized {get {return (values as ICollection).IsSynchronized;}} + object ICollection.SyncRoot {get {return (values as ICollection).SyncRoot;}} + #endregion + + #region ICollection + public void Add (string item) {values.Add (item);} + public void Clear () {values.Clear ();} + public bool Contains (string item) {return values.Contains (item);} + public void CopyTo (string[] array, int arrayIndex) {values.CopyTo (array, arrayIndex);} + public bool Remove (string item) {return values.Remove (item);} + public int Count {get {return values.Count;}} + public bool IsReadOnly {get {return false;}} + #endregion + + #region IEnumerable + IEnumerator IEnumerable.GetEnumerator () {return values.GetEnumerator ();} + #endregion + + #region IEnumerable + public IEnumerator GetEnumerator () {return values.GetEnumerator ();} + #endregion + + #region IList + int IList.Add (object value) {return (values as IList).Add (value);} + bool IList.Contains (object value) {return (values as IList).Contains (value);} + int IList.IndexOf (object value) {return (values as IList).IndexOf (value);} + void IList.Insert (int index, object value) {(values as IList).Insert (index, value);} + void IList.Remove (object value) {(values as IList).Remove (value);} + void IList.RemoveAt (int index) {(values as IList).RemoveAt (index);} + bool IList.IsFixedSize {get {return false;}} + object IList.this [int index] {get {return this [index];} set {(values as IList)[index] = value;}} + #endregion + + #region IList + public int IndexOf (string item) {return values.IndexOf (item);} + public void Insert (int index, string item) {values.Insert (index, item);} + public void RemoveAt (int index) {values.RemoveAt (index);} + + private void AssertValid (int index) + { + if (c.Option == null) + throw new InvalidOperationException ("OptionContext.Option is null."); + if (index >= c.Option.MaxValueCount) + throw new ArgumentOutOfRangeException ("index"); + if (c.Option.OptionValueType == OptionValueType.Required && + index >= values.Count) + throw new OptionException (string.Format ( + c.OptionSet.MessageLocalizer ("Missing required value for option '{0}'."), c.OptionName), + c.OptionName); + } + + public string this [int index] { + get { + AssertValid (index); + return index >= values.Count ? null : values [index]; + } + set { + values [index] = value; + } + } + #endregion + + public List ToList () + { + return new List (values); + } + + public string[] ToArray () + { + return values.ToArray (); + } + + public override string ToString () + { + return string.Join (", ", values.ToArray ()); + } + } + + public class OptionContext { + private Option option; + private string name; + private int index; + private OptionSet set; + private OptionValueCollection c; + + public OptionContext (OptionSet set) + { + this.set = set; + this.c = new OptionValueCollection (this); + } + + public Option Option { + get {return option;} + set {option = value;} + } + + public string OptionName { + get {return name;} + set {name = value;} + } + + public int OptionIndex { + get {return index;} + set {index = value;} + } + + public OptionSet OptionSet { + get {return set;} + } + + public OptionValueCollection OptionValues { + get {return c;} + } + } + + public enum OptionValueType { + None, + Optional, + Required, + } + + public abstract class Option { + string prototype, description; + string[] names; + OptionValueType type; + int count; + string[] separators; + + protected Option (string prototype, string description) + : this (prototype, description, 1) + { + } + + protected Option (string prototype, string description, int maxValueCount) + { + if (prototype == null) + throw new ArgumentNullException ("prototype"); + if (prototype.Length == 0) + throw new ArgumentException ("Cannot be the empty string.", "prototype"); + if (maxValueCount < 0) + throw new ArgumentOutOfRangeException ("maxValueCount"); + + this.prototype = prototype; + this.names = prototype.Split ('|'); + this.description = description; + this.count = maxValueCount; + this.type = ParsePrototype (); + + if (this.count == 0 && type != OptionValueType.None) + throw new ArgumentException ( + "Cannot provide maxValueCount of 0 for OptionValueType.Required or " + + "OptionValueType.Optional.", + "maxValueCount"); + if (this.type == OptionValueType.None && maxValueCount > 1) + throw new ArgumentException ( + string.Format ("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount), + "maxValueCount"); + if (Array.IndexOf (names, "<>") >= 0 && + ((names.Length == 1 && this.type != OptionValueType.None) || + (names.Length > 1 && this.MaxValueCount > 1))) + throw new ArgumentException ( + "The default option handler '<>' cannot require values.", + "prototype"); + } + + public string Prototype {get {return prototype;}} + public string Description {get {return description;}} + public OptionValueType OptionValueType {get {return type;}} + public int MaxValueCount {get {return count;}} + + public string[] GetNames () + { + return (string[]) names.Clone (); + } + + public string[] GetValueSeparators () + { + if (separators == null) + return new string [0]; + return (string[]) separators.Clone (); + } + + protected static T Parse (string value, OptionContext c) + { + Type tt = typeof (T); + bool nullable = tt.IsValueType && tt.IsGenericType && + !tt.IsGenericTypeDefinition && + tt.GetGenericTypeDefinition () == typeof (Nullable<>); + Type targetType = nullable ? tt.GetGenericArguments () [0] : typeof (T); + TypeConverter conv = TypeDescriptor.GetConverter (targetType); + T t = default (T); + try { + if (value != null) + t = (T) conv.ConvertFromString (value); + } + catch (Exception e) { + throw new OptionException ( + string.Format ( + c.OptionSet.MessageLocalizer ("Could not convert string `{0}' to type {1} for option `{2}'."), + value, targetType.Name, c.OptionName), + c.OptionName, e); + } + return t; + } + + internal string[] Names {get {return names;}} + internal string[] ValueSeparators {get {return separators;}} + + static readonly char[] NameTerminator = new char[]{'=', ':'}; + + private OptionValueType ParsePrototype () + { + char type = '\0'; + List seps = new List (); + for (int i = 0; i < names.Length; ++i) { + string name = names [i]; + if (name.Length == 0) + throw new ArgumentException ("Empty option names are not supported.", "prototype"); + + int end = name.IndexOfAny (NameTerminator); + if (end == -1) + continue; + names [i] = name.Substring (0, end); + if (type == '\0' || type == name [end]) + type = name [end]; + else + throw new ArgumentException ( + string.Format ("Conflicting option types: '{0}' vs. '{1}'.", type, name [end]), + "prototype"); + AddSeparators (name, end, seps); + } + + if (type == '\0') + return OptionValueType.None; + + if (count <= 1 && seps.Count != 0) + throw new ArgumentException ( + string.Format ("Cannot provide key/value separators for Options taking {0} value(s).", count), + "prototype"); + if (count > 1) { + if (seps.Count == 0) + this.separators = new string[]{":", "="}; + else if (seps.Count == 1 && seps [0].Length == 0) + this.separators = null; + else + this.separators = seps.ToArray (); + } + + return type == '=' ? OptionValueType.Required : OptionValueType.Optional; + } + + private static void AddSeparators (string name, int end, ICollection seps) + { + int start = -1; + for (int i = end+1; i < name.Length; ++i) { + switch (name [i]) { + case '{': + if (start != -1) + throw new ArgumentException ( + string.Format ("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + start = i+1; + break; + case '}': + if (start == -1) + throw new ArgumentException ( + string.Format ("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + seps.Add (name.Substring (start, i-start)); + start = -1; + break; + default: + if (start == -1) + seps.Add (name [i].ToString ()); + break; + } + } + if (start != -1) + throw new ArgumentException ( + string.Format ("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + } + + public void Invoke (OptionContext c) + { + OnParseComplete (c); + c.OptionName = null; + c.Option = null; + c.OptionValues.Clear (); + } + + protected abstract void OnParseComplete (OptionContext c); + + public override string ToString () + { + return Prototype; + } + } + + [Serializable] + public class OptionException : Exception { + private string option; + + public OptionException () + { + } + + public OptionException (string message, string optionName) + : base (message) + { + this.option = optionName; + } + + public OptionException (string message, string optionName, Exception innerException) + : base (message, innerException) + { + this.option = optionName; + } + + protected OptionException (SerializationInfo info, StreamingContext context) + : base (info, context) + { + this.option = info.GetString ("OptionName"); + } + + public string OptionName { + get {return this.option;} + } + + [SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)] + public override void GetObjectData (SerializationInfo info, StreamingContext context) + { + base.GetObjectData (info, context); + info.AddValue ("OptionName", option); + } + } + + public delegate void OptionAction (TKey key, TValue value); + + public class OptionSet : KeyedCollection + { + public OptionSet () + : this (delegate (string f) {return f;}) + { + } + + public OptionSet (Converter localizer) + { + this.localizer = localizer; + } + + Converter localizer; + + public Converter MessageLocalizer { + get {return localizer;} + } + + protected override string GetKeyForItem (Option item) + { + if (item == null) + throw new ArgumentNullException ("option"); + if (item.Names != null && item.Names.Length > 0) + return item.Names [0]; + // This should never happen, as it's invalid for Option to be + // constructed w/o any names. + throw new InvalidOperationException ("Option has no names!"); + } + + [Obsolete ("Use KeyedCollection.this[string]")] + protected Option GetOptionForName (string option) + { + if (option == null) + throw new ArgumentNullException ("option"); + try { + return base [option]; + } + catch (KeyNotFoundException) { + return null; + } + } + + protected override void InsertItem (int index, Option item) + { + base.InsertItem (index, item); + AddImpl (item); + } + + protected override void RemoveItem (int index) + { + base.RemoveItem (index); + Option p = Items [index]; + // KeyedCollection.RemoveItem() handles the 0th item + for (int i = 1; i < p.Names.Length; ++i) { + Dictionary.Remove (p.Names [i]); + } + } + + protected override void SetItem (int index, Option item) + { + base.SetItem (index, item); + RemoveItem (index); + AddImpl (item); + } + + private void AddImpl (Option option) + { + if (option == null) + throw new ArgumentNullException ("option"); + List added = new List (option.Names.Length); + try { + // KeyedCollection.InsertItem/SetItem handle the 0th name. + for (int i = 1; i < option.Names.Length; ++i) { + Dictionary.Add (option.Names [i], option); + added.Add (option.Names [i]); + } + } + catch (Exception) { + foreach (string name in added) + Dictionary.Remove (name); + throw; + } + } + + public new OptionSet Add (Option option) + { + base.Add (option); + return this; + } + + sealed class ActionOption : Option { + Action action; + + public ActionOption (string prototype, string description, int count, Action action) + : base (prototype, description, count) + { + if (action == null) + throw new ArgumentNullException ("action"); + this.action = action; + } + + protected override void OnParseComplete (OptionContext c) + { + action (c.OptionValues); + } + } + + public OptionSet Add (string prototype, Action action) + { + return Add (prototype, null, action); + } + + public OptionSet Add (string prototype, string description, Action action) + { + if (action == null) + throw new ArgumentNullException ("action"); + Option p = new ActionOption (prototype, description, 1, + delegate (OptionValueCollection v) { action (v [0]); }); + base.Add (p); + return this; + } + + public OptionSet Add (string prototype, OptionAction action) + { + return Add (prototype, null, action); + } + + public OptionSet Add (string prototype, string description, OptionAction action) + { + if (action == null) + throw new ArgumentNullException ("action"); + Option p = new ActionOption (prototype, description, 2, + delegate (OptionValueCollection v) {action (v [0], v [1]);}); + base.Add (p); + return this; + } + + sealed class ActionOption : Option { + Action action; + + public ActionOption (string prototype, string description, Action action) + : base (prototype, description, 1) + { + if (action == null) + throw new ArgumentNullException ("action"); + this.action = action; + } + + protected override void OnParseComplete (OptionContext c) + { + action (Parse (c.OptionValues [0], c)); + } + } + + sealed class ActionOption : Option { + OptionAction action; + + public ActionOption (string prototype, string description, OptionAction action) + : base (prototype, description, 2) + { + if (action == null) + throw new ArgumentNullException ("action"); + this.action = action; + } + + protected override void OnParseComplete (OptionContext c) + { + action ( + Parse (c.OptionValues [0], c), + Parse (c.OptionValues [1], c)); + } + } + + public OptionSet Add (string prototype, Action action) + { + return Add (prototype, null, action); + } + + public OptionSet Add (string prototype, string description, Action action) + { + return Add (new ActionOption (prototype, description, action)); + } + + public OptionSet Add (string prototype, OptionAction action) + { + return Add (prototype, null, action); + } + + public OptionSet Add (string prototype, string description, OptionAction action) + { + return Add (new ActionOption (prototype, description, action)); + } + + protected virtual OptionContext CreateOptionContext () + { + return new OptionContext (this); + } + +#if LINQ + public List Parse (IEnumerable arguments) + { + bool process = true; + OptionContext c = CreateOptionContext (); + c.OptionIndex = -1; + var def = GetOptionForName ("<>"); + var unprocessed = + from argument in arguments + where ++c.OptionIndex >= 0 && (process || def != null) + ? process + ? argument == "--" + ? (process = false) + : !Parse (argument, c) + ? def != null + ? Unprocessed (null, def, c, argument) + : true + : false + : def != null + ? Unprocessed (null, def, c, argument) + : true + : true + select argument; + List r = unprocessed.ToList (); + if (c.Option != null) + c.Option.Invoke (c); + return r; + } +#else + public List Parse (IEnumerable arguments) + { + OptionContext c = CreateOptionContext (); + c.OptionIndex = -1; + bool process = true; + List unprocessed = new List (); + Option def = Contains ("<>") ? this ["<>"] : null; + foreach (string argument in arguments) { + ++c.OptionIndex; + if (argument == "--") { + process = false; + continue; + } + if (!process) { + Unprocessed (unprocessed, def, c, argument); + continue; + } + if (!Parse (argument, c)) + Unprocessed (unprocessed, def, c, argument); + } + if (c.Option != null) + c.Option.Invoke (c); + return unprocessed; + } +#endif + + private static bool Unprocessed (ICollection extra, Option def, OptionContext c, string argument) + { + if (def == null) { + extra.Add (argument); + return false; + } + c.OptionValues.Add (argument); + c.Option = def; + c.Option.Invoke (c); + return false; + } + + private readonly Regex ValueOption = new Regex ( + @"^(?--|-|/)(?[^:=]+)((?[:=])(?.*))?$"); + + protected bool GetOptionParts (string argument, out string flag, out string name, out string sep, out string value) + { + if (argument == null) + throw new ArgumentNullException ("argument"); + + flag = name = sep = value = null; + Match m = ValueOption.Match (argument); + if (!m.Success) { + return false; + } + flag = m.Groups ["flag"].Value; + name = m.Groups ["name"].Value; + if (m.Groups ["sep"].Success && m.Groups ["value"].Success) { + sep = m.Groups ["sep"].Value; + value = m.Groups ["value"].Value; + } + return true; + } + + protected virtual bool Parse (string argument, OptionContext c) + { + if (c.Option != null) { + ParseValue (argument, c); + return true; + } + + string f, n, s, v; + if (!GetOptionParts (argument, out f, out n, out s, out v)) + return false; + + Option p; + if (Contains (n)) { + p = this [n]; + c.OptionName = f + n; + c.Option = p; + switch (p.OptionValueType) { + case OptionValueType.None: + c.OptionValues.Add (n); + c.Option.Invoke (c); + break; + case OptionValueType.Optional: + case OptionValueType.Required: + ParseValue (v, c); + break; + } + return true; + } + // no match; is it a bool option? + if (ParseBool (argument, n, c)) + return true; + // is it a bundled option? + if (ParseBundledValue (f, string.Concat (n + s + v), c)) + return true; + + return false; + } + + private void ParseValue (string option, OptionContext c) + { + if (option != null) + foreach (string o in c.Option.ValueSeparators != null + ? option.Split (c.Option.ValueSeparators, StringSplitOptions.None) + : new string[]{option}) { + c.OptionValues.Add (o); + } + if (c.OptionValues.Count == c.Option.MaxValueCount || + c.Option.OptionValueType == OptionValueType.Optional) + c.Option.Invoke (c); + else if (c.OptionValues.Count > c.Option.MaxValueCount) { + throw new OptionException (localizer (string.Format ( + "Error: Found {0} option values when expecting {1}.", + c.OptionValues.Count, c.Option.MaxValueCount)), + c.OptionName); + } + } + + private bool ParseBool (string option, string n, OptionContext c) + { + Option p; + string rn; + if (n.Length >= 1 && (n [n.Length-1] == '+' || n [n.Length-1] == '-') && + Contains ((rn = n.Substring (0, n.Length-1)))) { + p = this [rn]; + string v = n [n.Length-1] == '+' ? option : null; + c.OptionName = option; + c.Option = p; + c.OptionValues.Add (v); + p.Invoke (c); + return true; + } + return false; + } + + private bool ParseBundledValue (string f, string n, OptionContext c) + { + if (f != "-") + return false; + for (int i = 0; i < n.Length; ++i) { + Option p; + string opt = f + n [i].ToString (); + string rn = n [i].ToString (); + if (!Contains (rn)) { + if (i == 0) + return false; + throw new OptionException (string.Format (localizer ( + "Cannot bundle unregistered option '{0}'."), opt), opt); + } + p = this [rn]; + switch (p.OptionValueType) { + case OptionValueType.None: + Invoke (c, opt, n, p); + break; + case OptionValueType.Optional: + case OptionValueType.Required: { + string v = n.Substring (i+1); + c.Option = p; + c.OptionName = opt; + ParseValue (v.Length != 0 ? v : null, c); + return true; + } + default: + throw new InvalidOperationException ("Unknown OptionValueType: " + p.OptionValueType); + } + } + return true; + } + + private static void Invoke (OptionContext c, string name, string value, Option option) + { + c.OptionName = name; + c.Option = option; + c.OptionValues.Add (value); + option.Invoke (c); + } + + private const int OptionWidth = 29; + + public void WriteOptionDescriptions (TextWriter o) + { + foreach (Option p in this) { + int written = 0; + if (!WriteOptionPrototype (o, p, ref written)) + continue; + + if (written < OptionWidth) + o.Write (new string (' ', OptionWidth - written)); + else { + o.WriteLine (); + o.Write (new string (' ', OptionWidth)); + } + + bool indent = false; + string prefix = new string (' ', OptionWidth+2); + foreach (string line in GetLines (localizer (GetDescription (p.Description)))) { + if (indent) + o.Write (prefix); + o.WriteLine (line); + indent = true; + } + } + } + + bool WriteOptionPrototype (TextWriter o, Option p, ref int written) + { + string[] names = p.Names; + + int i = GetNextOptionIndex (names, 0); + if (i == names.Length) + return false; + + if (names [i].Length == 1) { + Write (o, ref written, " -"); + Write (o, ref written, names [0]); + } + else { + Write (o, ref written, " --"); + Write (o, ref written, names [0]); + } + + for ( i = GetNextOptionIndex (names, i+1); + i < names.Length; i = GetNextOptionIndex (names, i+1)) { + Write (o, ref written, ", "); + Write (o, ref written, names [i].Length == 1 ? "-" : "--"); + Write (o, ref written, names [i]); + } + + if (p.OptionValueType == OptionValueType.Optional || + p.OptionValueType == OptionValueType.Required) { + if (p.OptionValueType == OptionValueType.Optional) { + Write (o, ref written, localizer ("[")); + } + Write (o, ref written, localizer ("=" + GetArgumentName (0, p.MaxValueCount, p.Description))); + string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0 + ? p.ValueSeparators [0] + : " "; + for (int c = 1; c < p.MaxValueCount; ++c) { + Write (o, ref written, localizer (sep + GetArgumentName (c, p.MaxValueCount, p.Description))); + } + if (p.OptionValueType == OptionValueType.Optional) { + Write (o, ref written, localizer ("]")); + } + } + return true; + } + + static int GetNextOptionIndex (string[] names, int i) + { + while (i < names.Length && names [i] == "<>") { + ++i; + } + return i; + } + + static void Write (TextWriter o, ref int n, string s) + { + n += s.Length; + o.Write (s); + } + + private static string GetArgumentName (int index, int maxIndex, string description) + { + if (description == null) + return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); + string[] nameStart; + if (maxIndex == 1) + nameStart = new string[]{"{0:", "{"}; + else + nameStart = new string[]{"{" + index + ":"}; + for (int i = 0; i < nameStart.Length; ++i) { + int start, j = 0; + do { + start = description.IndexOf (nameStart [i], j); + } while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false); + if (start == -1) + continue; + int end = description.IndexOf ("}", start); + if (end == -1) + continue; + return description.Substring (start + nameStart [i].Length, end - start - nameStart [i].Length); + } + return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); + } + + private static string GetDescription (string description) + { + if (description == null) + return string.Empty; + StringBuilder sb = new StringBuilder (description.Length); + int start = -1; + for (int i = 0; i < description.Length; ++i) { + switch (description [i]) { + case '{': + if (i == start) { + sb.Append ('{'); + start = -1; + } + else if (start < 0) + start = i + 1; + break; + case '}': + if (start < 0) { + if ((i+1) == description.Length || description [i+1] != '}') + throw new InvalidOperationException ("Invalid option description: " + description); + ++i; + sb.Append ("}"); + } + else { + sb.Append (description.Substring (start, i - start)); + start = -1; + } + break; + case ':': + if (start < 0) + goto default; + start = i + 1; + break; + default: + if (start < 0) + sb.Append (description [i]); + break; + } + } + return sb.ToString (); + } + + private static IEnumerable GetLines (string description) + { + if (string.IsNullOrEmpty (description)) { + yield return string.Empty; + yield break; + } + int length = 80 - OptionWidth - 1; + int start = 0, end; + do { + end = GetLineEnd (start, length, description); + char c = description [end-1]; + if (char.IsWhiteSpace (c)) + --end; + bool writeContinuation = end != description.Length && !IsEolChar (c); + string line = description.Substring (start, end - start) + + (writeContinuation ? "-" : ""); + yield return line; + start = end; + if (char.IsWhiteSpace (c)) + ++start; + length = 80 - OptionWidth - 2 - 1; + } while (end < description.Length); + } + + private static bool IsEolChar (char c) + { + return !char.IsLetterOrDigit (c); + } + + private static int GetLineEnd (int start, int length, string description) + { + int end = System.Math.Min (start + length, description.Length); + int sep = -1; + for (int i = start+1; i < end; ++i) { + if (description [i] == '\n') + return i+1; + if (IsEolChar (description [i])) + sep = i+1; + } + if (sep == -1 || end == description.Length) + return end; + return sep; + } + } +} + diff --git a/src/nunit-console/OutputManager.cs b/src/nunit-console/OutputManager.cs new file mode 100644 index 00000000..8a77eb06 --- /dev/null +++ b/src/nunit-console/OutputManager.cs @@ -0,0 +1,104 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using System.Reflection; +using System.Xml; + +namespace NUnit.ConsoleRunner +{ + public class OutputManager + { + private XmlNode result; + private string workDirectory; + + public OutputManager(XmlNode result, string workDirectory) + { + this.result = result; + this.workDirectory = workDirectory; + } + + public void WriteResultFile(OutputSpecification spec, DateTime startTime) + { + string outputPath = Path.Combine(workDirectory, spec.OutputPath); + IResultWriter outputWriter = null; + + switch (spec.Format) + { + case "nunit3": + outputWriter = new NUnit3XmlOutputWriter(); + break; + + case "nunit2": + outputWriter = new NUnit2XmlOutputWriter(); + break; + + case "user": + Uri uri = new Uri(Assembly.GetExecutingAssembly().CodeBase); + string dir = Path.GetDirectoryName(uri.LocalPath); + outputWriter = new XmlTransformOutputWriter(Path.Combine(dir, spec.Transform)); + break; + + default: + throw new ArgumentException( + string.Format("Invalid XML output format '{0}'", spec.Format), + "spec"); + } + + outputWriter.WriteResultFile(result, outputPath); + Console.WriteLine("Results ({0}) saved as {1}", spec.Format, outputPath); + } + + public void WriteTestFile(OutputSpecification spec) + { + string outputPath = Path.Combine(workDirectory, spec.OutputPath); + IResultWriter outputWriter = null; + + switch (spec.Format) + { + case "nunit3": + outputWriter = new NUnit3XmlOutputWriter(); + break; + + case "cases": + outputWriter = new TestCaseOutputWriter(); + break; + + case "user": + Uri uri = new Uri(Assembly.GetExecutingAssembly().CodeBase); + string dir = Path.GetDirectoryName(uri.LocalPath); + outputWriter = new XmlTransformOutputWriter(Path.Combine(dir, spec.Transform)); + break; + + default: + throw new ArgumentException( + string.Format("Invalid XML output format '{0}'", spec.Format), + "spec"); + } + + outputWriter.WriteResultFile(result, outputPath); + Console.WriteLine("Tests ({0}) saved as {1}", spec.Format, outputPath); + } + } +} diff --git a/src/nunit-console/OutputSpecification.cs b/src/nunit-console/OutputSpecification.cs new file mode 100644 index 00000000..bde84084 --- /dev/null +++ b/src/nunit-console/OutputSpecification.cs @@ -0,0 +1,92 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; + +namespace NUnit.ConsoleRunner +{ + public class OutputSpecification + { + #region Constructor + + public OutputSpecification(string spec) + { + if (spec == null) + throw new NullReferenceException("Output spec may not be null"); + + string[] parts = spec.Split(';'); + this.OutputPath = parts[0]; + + for (int i = 1; i < parts.Length; i++) + { + string[] opt = parts[i].Split('='); + + if (opt.Length != 2) + throw new ArgumentException(); + + switch (opt[0].Trim()) + { + case "format": + string fmt = opt[1].Trim(); + + if (this.Format != null && this.Format != fmt) + throw new ArgumentException( + string.Format("Conflicting format options: {0}", spec)); + + this.Format = fmt; + break; + + case "transform": + string val = opt[1].Trim(); + + if (this.Transform != null && this.Transform != val) + throw new ArgumentException( + string.Format("Conflicting transform options: {0}", spec)); + + if (this.Format != null && this.Format != "user") + throw new ArgumentException( + string.Format("Conflicting format options: {0}", spec)); + + this.Format = "user"; + this.Transform = opt[1].Trim(); + break; + } + } + + if (Format == null) + Format = "nunit3"; + } + + #endregion + + #region Properties + + public string OutputPath { get; private set; } + + public string Format { get; private set; } + + public string Transform { get; private set; } + + #endregion + } +} diff --git a/src/nunit-console/OutputWriters/NUnit2XmlOutputWriter.cs b/src/nunit-console/OutputWriters/NUnit2XmlOutputWriter.cs new file mode 100644 index 00000000..09bee72e --- /dev/null +++ b/src/nunit-console/OutputWriters/NUnit2XmlOutputWriter.cs @@ -0,0 +1,340 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using System.Text; +using System.Xml; +using System.IO; + +namespace NUnit.ConsoleRunner +{ + using Utilities; + + public class NUnit2XmlOutputWriter : IResultWriter + { + private XmlWriter xmlWriter; + + private static Dictionary resultStates = new Dictionary(); + + static NUnit2XmlOutputWriter() + { + resultStates["Passed"] = "Success"; + resultStates["Failed"] = "Failure"; + resultStates["Failed:Error"] = "Error"; + resultStates["Failed:Cancelled"] = "Cancelled"; + resultStates["Inconclusive"] = "Inconclusive"; + resultStates["Skipped"] = "Skipped"; + resultStates["Skipped:Ignored"] = "Ignored"; + resultStates["Skipped:Invalid"] = "NotRunnable"; + } + + public void WriteResultFile(XmlNode result, string outputPath) + { + using (StreamWriter writer = new StreamWriter(outputPath, false, Encoding.UTF8)) + { + WriteResultFile(result, writer); + } + } + + public void WriteResultFile(XmlNode result, TextWriter writer) + { + using (XmlTextWriter xmlWriter = new XmlTextWriter(writer)) + { + xmlWriter.Formatting = Formatting.Indented; + WriteXmlOutput(result, xmlWriter); + } + } + + private void WriteXmlOutput(XmlNode result, XmlWriter xmlWriter) + { + this.xmlWriter = xmlWriter; + + InitializeXmlFile(result); + + foreach (XmlNode child in result.ChildNodes) + if (child.Name.StartsWith("test-")) + WriteResultElement(child); + + TerminateXmlFile(); + } + + private void InitializeXmlFile(XmlNode result) + { + ResultSummary summaryResults = new ResultSummary(result); + + xmlWriter.WriteStartDocument(false); + xmlWriter.WriteComment("This file represents the results of running a test suite"); + + xmlWriter.WriteStartElement("test-results"); + + xmlWriter.WriteAttributeString("name", result.GetAttribute("fullname")); + xmlWriter.WriteAttributeString("total", summaryResults.TestsRun.ToString()); + xmlWriter.WriteAttributeString("errors", summaryResults.Errors.ToString()); + xmlWriter.WriteAttributeString("failures", summaryResults.Failures.ToString()); + xmlWriter.WriteAttributeString("not-run", summaryResults.TestsNotRun.ToString()); + xmlWriter.WriteAttributeString("inconclusive", summaryResults.Inconclusive.ToString()); + xmlWriter.WriteAttributeString("ignored", summaryResults.Ignored.ToString()); + xmlWriter.WriteAttributeString("skipped", summaryResults.Skipped.ToString()); + xmlWriter.WriteAttributeString("invalid", summaryResults.NotRunnable.ToString()); + + DateTime start = result.GetAttribute("start-time", DateTime.UtcNow); + xmlWriter.WriteAttributeString("date", start.ToString("yyyy-MM-dd")); + xmlWriter.WriteAttributeString("time", start.ToString("HH:mm:ss")); + WriteEnvironment(); + WriteCultureInfo(); + } + + private void WriteCultureInfo() + { + xmlWriter.WriteStartElement("culture-info"); + xmlWriter.WriteAttributeString("current-culture", + CultureInfo.CurrentCulture.ToString()); + xmlWriter.WriteAttributeString("current-uiculture", + CultureInfo.CurrentUICulture.ToString()); + xmlWriter.WriteEndElement(); + } + + private void WriteEnvironment() + { + xmlWriter.WriteStartElement("environment"); + xmlWriter.WriteAttributeString("nunit-version", + Assembly.GetExecutingAssembly().GetName().Version.ToString()); + xmlWriter.WriteAttributeString("clr-version", + Environment.Version.ToString()); + xmlWriter.WriteAttributeString("os-version", + Environment.OSVersion.ToString()); + xmlWriter.WriteAttributeString("platform", + Environment.OSVersion.Platform.ToString()); + xmlWriter.WriteAttributeString("cwd", + Environment.CurrentDirectory); + xmlWriter.WriteAttributeString("machine-name", + Environment.MachineName); + xmlWriter.WriteAttributeString("user", + Environment.UserName); + xmlWriter.WriteAttributeString("user-domain", + Environment.UserDomainName); + xmlWriter.WriteEndElement(); + } + + private void WriteResultElement(XmlNode result) + { + StartTestElement(result); + + var categories = result.SelectSingleNode("categories"); + if (categories != null) + WriteCategoriesElement(categories); + + var properties = result.SelectSingleNode("properties"); + if (properties != null) + WritePropertiesElement(properties); + + var message = result.SelectSingleNode("reason/message"); + if (message != null) + WriteReasonElement(message.InnerText); + + message = result.SelectSingleNode("failure/message"); + var stackTrace = result.SelectSingleNode("failure/stack-trace"); + if (message != null) + WriteFailureElement(message.InnerText, stackTrace == null ? null : stackTrace.InnerText); + + if (result.Name != "test-case") + WriteChildResults(result); + + xmlWriter.WriteEndElement(); // test element + } + + private void TerminateXmlFile() + { + xmlWriter.WriteEndElement(); // test-results + xmlWriter.WriteEndDocument(); + xmlWriter.Flush(); + xmlWriter.Close(); + } + + + #region Element Creation Helpers + + private void StartTestElement(XmlNode result) + { + if (result.Name == "test-case") + { + xmlWriter.WriteStartElement("test-case"); + xmlWriter.WriteAttributeString("name", result.GetAttribute("name")); + } + else + { + xmlWriter.WriteStartElement("test-suite"); + xmlWriter.WriteAttributeString("type", result.GetAttribute("type")); + string nameAttr = result.Name == "test-assembly" || result.Name == "test-project" ? "fullname" : "name"; + xmlWriter.WriteAttributeString("name", result.GetAttribute(nameAttr)); + } + + string description = result.GetAttribute("description"); + if (description != null) + xmlWriter.WriteAttributeString("description", description); + + string resultState = result.GetAttribute("result"); + string label = result.GetAttribute("label"); + string executed = resultState == "Skipped" ? "False" : "True"; + string success = resultState == "Passed" ? "True" : "False"; + + double duration = result.GetAttribute("duration", 0.0); + string asserts = result.GetAttribute("asserts"); + + if (label != null && label != string.Empty) + resultState += ":" + label; + + xmlWriter.WriteAttributeString("executed", executed); + xmlWriter.WriteAttributeString("result", resultStates[resultState]); + + if (executed == "True") + { + xmlWriter.WriteAttributeString("success", success); + xmlWriter.WriteAttributeString("time", duration.ToString("0.000")); + xmlWriter.WriteAttributeString("asserts", asserts); + } + } + + private void WriteCategoriesElement(XmlNode categories) + { + xmlWriter.WriteStartElement("categories"); + var items = categories.SelectNodes("category"); + foreach (XmlNode item in items) + { + xmlWriter.WriteStartElement("category"); + xmlWriter.WriteAttributeString("name", item.GetAttribute("name")); + xmlWriter.WriteEndElement(); + } + xmlWriter.WriteEndElement(); + } + + private void WritePropertiesElement(XmlNode properties) + { + xmlWriter.WriteStartElement("properties"); + + var items = properties.SelectNodes("property"); + foreach (XmlNode item in items) + { + xmlWriter.WriteStartElement("property"); + xmlWriter.WriteAttributeString("name", item.GetAttribute("name")); + xmlWriter.WriteAttributeString("value", item.GetAttribute("value")); + xmlWriter.WriteEndElement(); + } + + xmlWriter.WriteEndElement(); + } + + private void WriteReasonElement(string message) + { + xmlWriter.WriteStartElement("reason"); + xmlWriter.WriteStartElement("message"); + xmlWriter.WriteCData(message); + xmlWriter.WriteEndElement(); + xmlWriter.WriteEndElement(); + } + + private void WriteFailureElement(string message, string stackTrace) + { + xmlWriter.WriteStartElement("failure"); + xmlWriter.WriteStartElement("message"); + WriteCData(message); + xmlWriter.WriteEndElement(); + xmlWriter.WriteStartElement("stack-trace"); + if (stackTrace != null) + WriteCData(stackTrace); + xmlWriter.WriteEndElement(); + xmlWriter.WriteEndElement(); + } + + private void WriteChildResults(XmlNode result) + { + xmlWriter.WriteStartElement("results"); + + foreach (XmlNode childResult in result.ChildNodes) + if (childResult.Name.StartsWith("test-")) + WriteResultElement(childResult); + + xmlWriter.WriteEndElement(); + } + #endregion + + #region Output Helpers + /// + /// Makes string safe for xml parsing, replacing control chars with '?' + /// + /// string to make safe + /// xml safe string + private static string CharacterSafeString(string encodedString) + { + /*The default code page for the system will be used. + Since all code pages use the same lower 128 bytes, this should be sufficient + for finding uprintable control characters that make the xslt processor error. + We use characters encoded by the default code page to avoid mistaking bytes as + individual characters on non-latin code pages.*/ + char[] encodedChars = System.Text.Encoding.Default.GetChars(System.Text.Encoding.Default.GetBytes(encodedString)); + + System.Collections.ArrayList pos = new System.Collections.ArrayList(); + for (int x = 0; x < encodedChars.Length; x++) + { + char currentChar = encodedChars[x]; + //unprintable characters are below 0x20 in Unicode tables + //some control characters are acceptable. (carriage return 0x0D, line feed 0x0A, horizontal tab 0x09) + if (currentChar < 32 && (currentChar != 9 && currentChar != 10 && currentChar != 13)) + { + //save the array index for later replacement. + pos.Add(x); + } + } + foreach (int index in pos) + { + encodedChars[index] = '?';//replace unprintable control characters with ?(3F) + } + return System.Text.Encoding.Default.GetString(System.Text.Encoding.Default.GetBytes(encodedChars)); + } + + private void WriteCData(string text) + { + int start = 0; + while (true) + { + int illegal = text.IndexOf("]]>", start); + if (illegal < 0) + break; + xmlWriter.WriteCData(text.Substring(start, illegal - start + 2)); + start = illegal + 2; + if (start >= text.Length) + return; + } + + if (start > 0) + xmlWriter.WriteCData(text.Substring(start)); + else + xmlWriter.WriteCData(text); + } + + #endregion + } +} diff --git a/src/nunit-console/OutputWriters/NUnit3XmlOutputWriter.cs b/src/nunit-console/OutputWriters/NUnit3XmlOutputWriter.cs new file mode 100644 index 00000000..b4fe5af6 --- /dev/null +++ b/src/nunit-console/OutputWriters/NUnit3XmlOutputWriter.cs @@ -0,0 +1,58 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Reflection; +using System.Text; +using System.Xml; +using System.IO; + +namespace NUnit.ConsoleRunner +{ + /// + /// NUnit3XmlOutputWriter is responsible for writing the results + /// of a test to a file in NUnit 3.0 format. + /// + public class NUnit3XmlOutputWriter : IResultWriter + { + public void WriteResultFile(XmlNode resultNode, string outputPath) + { + using (StreamWriter writer = new StreamWriter(outputPath, false, Encoding.UTF8)) + { + WriteResultFile(resultNode, writer); + } + } + + private void WriteResultFile(XmlNode resultNode, TextWriter writer) + { + XmlWriterSettings settings = new XmlWriterSettings(); + settings.Indent = true; + + using (XmlWriter xmlWriter = XmlWriter.Create(writer, settings)) + { + xmlWriter.WriteStartDocument(false); + resultNode.WriteTo(xmlWriter); + } + } + } +} diff --git a/src/nunit-console/OutputWriters/TestCaseOutputWriter.cs b/src/nunit-console/OutputWriters/TestCaseOutputWriter.cs new file mode 100644 index 00000000..eb0c4957 --- /dev/null +++ b/src/nunit-console/OutputWriters/TestCaseOutputWriter.cs @@ -0,0 +1,46 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System.IO; +using System.Text; +using System.Xml; + +namespace NUnit.ConsoleRunner +{ + public class TestCaseOutputWriter : IResultWriter + { + public void WriteResultFile(XmlNode resultNode, string outputPath) + { + using (StreamWriter writer = new StreamWriter(outputPath, false, Encoding.UTF8)) + { + WriteResultFile(resultNode, writer); + } + } + + public void WriteResultFile(XmlNode resultNode, TextWriter writer) + { + foreach (XmlNode node in resultNode.SelectNodes("//test-case")) + writer.WriteLine(node.Attributes["fullname"].Value); + } + } +} diff --git a/src/nunit-console/OutputWriters/XmlTransformOutputWriter.cs b/src/nunit-console/OutputWriters/XmlTransformOutputWriter.cs new file mode 100644 index 00000000..2fd49a47 --- /dev/null +++ b/src/nunit-console/OutputWriters/XmlTransformOutputWriter.cs @@ -0,0 +1,60 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System.IO; +using System.Text; +using System.Xml; +using System.Xml.Xsl; + +namespace NUnit.ConsoleRunner +{ + public class XmlTransformOutputWriter : IResultWriter + { + private string xsltFile; + private XslCompiledTransform transform = new XslCompiledTransform(); + + public XmlTransformOutputWriter(string xsltFile) + { + this.xsltFile = xsltFile; + transform.Load(xsltFile); + } + + public void WriteResultFile(XmlNode result, TextWriter writer) + { + using (XmlTextWriter xmlWriter = new XmlTextWriter(writer)) + { + xmlWriter.Formatting = Formatting.Indented; + transform.Transform(result, xmlWriter); + } + } + + public void WriteResultFile(XmlNode result, string outputPath) + { + using (XmlTextWriter xmlWriter = new XmlTextWriter(outputPath, Encoding.Default)) + { + xmlWriter.Formatting = Formatting.Indented; + transform.Transform(result, xmlWriter); + } + } + } +} diff --git a/src/nunit-console/Program.cs b/src/nunit-console/Program.cs new file mode 100644 index 00000000..75542d05 --- /dev/null +++ b/src/nunit-console/Program.cs @@ -0,0 +1,260 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using System.Reflection; +using Mono.Options; +using NUnit.Engine; + +namespace NUnit.ConsoleRunner +{ + using Options; + using Utilities; + + /// + /// This class provides the entry point for the console runner. + /// + public class Program + { + //static Logger log = InternalTrace.GetLogger(typeof(Runner)); + + [STAThread] + public static int Main(string[] args) + { + ConsoleOptions options = new ConsoleOptions(); + + try + { + options.Parse(args); + } + catch (OptionException ex) + { + WriteHeader(); + ColorConsole.WriteLine(ColorStyle.Error, string.Format(ex.Message, ex.OptionName)); + return ConsoleRunner.INVALID_ARG; + } + + ColorConsole.Enabled = !options.NoColor; + + // Create SettingsService early so we know the trace level right at the start + //SettingsService settingsService = new SettingsService(); + //InternalTraceLevel level = (InternalTraceLevel)settingsService.GetSetting("Options.InternalTraceLevel", InternalTraceLevel.Default); + //if (options.trace != InternalTraceLevel.Default) + // level = options.trace; + + //InternalTrace.Initialize("nunit-console_%p.log", level); + + //log.Info("NUnit-console.exe starting"); + try + { + if (options.PauseBeforeRun) + { + ColorConsole.WriteLine(ColorStyle.Warning, "Press any key to continue . . ."); + Console.ReadKey(true); + } + + if (!options.NoHeader) + WriteHeader(); + + if (options.ShowHelp) + { + WriteHelpText(options); + return ConsoleRunner.OK; + } + + if (!options.Validate()) + { + using (new ColorConsole(ColorStyle.Error)) + { + foreach (string message in options.ErrorMessages) + Console.Error.WriteLine(message); + } + + return ConsoleRunner.INVALID_ARG; + } + + if (options.InputFiles.Count == 0) + { + using (new ColorConsole(ColorStyle.Error)) + Console.Error.WriteLine("Error: no inputs specified"); + return ConsoleRunner.OK; + } + + // TODO: Move this to engine + foreach (string file in options.InputFiles) + { + //if (!Services.ProjectService.CanLoadProject(file) && !PathUtils.IsAssemblyFileType(file)) + string ext = Path.GetExtension(file); + if (ext != ".dll" && ext != ".exe" && ext != ".nunit") + { + ColorConsole.WriteLine(ColorStyle.Warning, "File type not known: " + file); + return ConsoleRunner.INVALID_ARG; + } + } + + using (ITestEngine engine = TestEngineActivator.CreateInstance()) + { + if (options.WorkDirectory != null) + engine.WorkDirectory = options.WorkDirectory; + + if (options.InternalTraceLevel != null) + engine.InternalTraceLevel = (InternalTraceLevel)Enum.Parse(typeof(InternalTraceLevel), options.InternalTraceLevel); + + try + { + return new ConsoleRunner(engine, options).Execute(); + } + catch (NUnitEngineException ex) + { + ColorConsole.WriteLine(ColorStyle.Error, ex.Message); + return ConsoleRunner.INVALID_ARG; + } + catch (FileNotFoundException ex) + { + ColorConsole.WriteLine(ColorStyle.Error, ex.Message); +#if DEBUG + ColorConsole.WriteLine(ColorStyle.Error, ex.StackTrace); +#endif + return ConsoleRunner.FILE_NOT_FOUND; + } + catch (DirectoryNotFoundException ex) + { + ColorConsole.WriteLine(ColorStyle.Error, ex.Message); + return ConsoleRunner.FILE_NOT_FOUND; + } + catch (Exception ex) + { + ColorConsole.WriteLine(ColorStyle.Error, ex.Message); + return ConsoleRunner.UNEXPECTED_ERROR; + } + finally + { + if (options.WaitBeforeExit) + { + using (new ColorConsole(ColorStyle.Warning)) + { + Console.Out.WriteLine("\nPress any key to continue . . ."); + Console.ReadKey(true); + } + } + + // log.Info( "NUnit-console.exe terminating" ); + } + } + } + finally + { + Console.ResetColor(); + } + } + + private static void WriteHeader() + { + Assembly executingAssembly = Assembly.GetExecutingAssembly(); + string versionText = executingAssembly.GetName().Version.ToString(3); + + string programName = "NUnit Console Runner"; + string copyrightText = "Copyright (C) 2014 Charlie Poole.\r\nAll Rights Reserved."; + string configText = String.Empty; + + object[] attrs = executingAssembly.GetCustomAttributes(typeof(AssemblyTitleAttribute), false); + if (attrs.Length > 0) + programName = ((AssemblyTitleAttribute)attrs[0]).Title; + + attrs = executingAssembly.GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false); + if ( attrs.Length > 0 ) + copyrightText = ((AssemblyCopyrightAttribute)attrs[0]).Copyright; + + attrs = executingAssembly.GetCustomAttributes(typeof(AssemblyConfigurationAttribute), false); + if ( attrs.Length > 0 ) + { + string configuration = ( ( AssemblyConfigurationAttribute )attrs[0] ).Configuration; + if ( !String.IsNullOrEmpty( configuration ) ) + { + configText = string.Format( "({0})", ( ( AssemblyConfigurationAttribute )attrs[0] ).Configuration ); + } + } + + ColorConsole.WriteLine(ColorStyle.Header, string.Format( "{0} {1} {2}", programName, versionText, configText )); + ColorConsole.WriteLine(ColorStyle.SubHeader, copyrightText); + Console.WriteLine(); + ColorConsole.WriteLine(ColorStyle.SectionHeader, "Runtime Environment"); + ColorConsole.WriteLabel(" OS Version: ", Environment.OSVersion.ToString(), true); + ColorConsole.WriteLabel(" CLR Version: ", Environment.Version.ToString(), true); + Console.WriteLine(); + } + + private static void WriteHelpText(ConsoleOptions options) + { + Console.WriteLine(); + ColorConsole.WriteLine(ColorStyle.Header, "NUNIT-CONSOLE [inputfiles] [options]"); + Console.WriteLine(); + ColorConsole.WriteLine(ColorStyle.Default, "Runs a set of NUnit tests from the console."); + Console.WriteLine(); + ColorConsole.WriteLine(ColorStyle.SectionHeader, "InputFiles:"); + ColorConsole.WriteLine(ColorStyle.Default, " One or more assemblies or test projects of a recognized type."); + Console.WriteLine(); + ColorConsole.WriteLine(ColorStyle.SectionHeader, "Options:"); + using (new ColorConsole(ColorStyle.Default)) + { + options.WriteOptionDescriptions(Console.Out); + } + Console.WriteLine(); + ColorConsole.WriteLine(ColorStyle.SectionHeader, "Description:"); + using (new ColorConsole(ColorStyle.Default)) + { + Console.WriteLine(" By default, this command runs the tests contained in the"); + Console.WriteLine(" assemblies and projects specified. If the --explore option"); + Console.WriteLine(" is used, no tests are executed but a description of the tests"); + Console.WriteLine(" is saved in the specified or default format."); + Console.WriteLine(); + Console.WriteLine(" Several options that specify processing of XML output take"); + Console.WriteLine(" an output specification as a value. A SPEC may take one of"); + Console.WriteLine(" the following forms:"); + Console.WriteLine(" --OPTION:filename"); + Console.WriteLine(" --OPTION:filename;format=formatname"); + Console.WriteLine(" --OPTION:filename;transform=xsltfile"); + Console.WriteLine(); + Console.WriteLine(" The --result option may use any of the following formats:"); + Console.WriteLine(" nunit3 - the native XML format for NUnit 3.0"); + Console.WriteLine(" nunit2 - legacy XML format used by earlier releases of NUnit"); + Console.WriteLine(); + Console.WriteLine(" The --explore option may use any of the following formats:"); + Console.WriteLine(" nunit3 - the native XML format for NUnit 3.0"); + Console.WriteLine(" cases - a text file listing the full names of all test cases."); + Console.WriteLine(" If --explore is used without any specification following, a list of"); + Console.WriteLine(" test cases is output to the console."); + Console.WriteLine(); + Console.WriteLine(" If none of the options {--result, --explore, --noxml} is used,"); + Console.WriteLine(" NUnit saves the results to TestResult.xml in nunit3 format"); + Console.WriteLine(); + Console.WriteLine(" Any transforms provided must handle input in the native nunit3 format."); + Console.WriteLine(); + //Console.WriteLine("Options that take values may use an equal sign, a colon"); + //Console.WriteLine("or a space to separate the option from its value."); + //Console.WriteLine(); + } + } + } +} diff --git a/src/nunit-console/Properties/AssemblyInfo.cs b/src/nunit-console/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..4d39cae5 --- /dev/null +++ b/src/nunit-console/Properties/AssemblyInfo.cs @@ -0,0 +1,16 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NUnit Console Runner")] +[assembly: AssemblyDescription("The console command-line runner for NUnit")] + +// 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("b2dfdf79-289b-4b45-ab40-8d461b7d56d7")] diff --git a/src/nunit-console/ResultReporter.cs b/src/nunit-console/ResultReporter.cs new file mode 100644 index 00000000..ab9b6c37 --- /dev/null +++ b/src/nunit-console/ResultReporter.cs @@ -0,0 +1,248 @@ +// *********************************************************************** +// Copyright (c) 2009 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Globalization; +using System.Xml; + +namespace NUnit.ConsoleRunner +{ + using Options; + using Utilities; + + public class ResultReporter + { + XmlNode result; + string testRunResult; + ConsoleOptions options; + ResultSummary summary; + + int reportIndex = 0; + + public ResultReporter(XmlNode result, ConsoleOptions options) + { + this.result = result; + this.testRunResult = result.GetAttribute("result"); + this.options = options; + this.summary = new ResultSummary(result); + } + + public ResultSummary Summary + { + get { return summary; } + } + + /// + /// Reports the results to the console + /// + public void ReportResults() + { + if (options.StopOnError && summary.ErrorsAndFailures > 0) + { + ColorConsole.WriteLine(ColorStyle.Failure, "Execution terminated after first error"); + Console.WriteLine(); + } + + WriteSummaryReport(); + + if (ListAssembliesWithNoTests(result) > 0) + Console.WriteLine(); + + if (testRunResult == "Failed") + WriteErrorsAndFailuresReport(); + + if (summary.TestsNotRun > 0) + WriteNotRunReport(); + } + + private void WriteSummaryReport() + { + ColorStyle overall = testRunResult == "Passed" + ? ColorStyle.Pass + : ( testRunResult == "Failed" ? ColorStyle.Failure : ColorStyle.Warning ); + Console.WriteLine(); + ColorConsole.WriteLine(ColorStyle.SectionHeader, "Test Run Summary"); + ColorConsole.WriteLabel(" Overall result: ", testRunResult, overall, true); + + ColorConsole.WriteLabel(" Tests run: ", summary.TestsRun.ToString(CultureInfo.CurrentUICulture), false); + ColorConsole.WriteLabel(", Errors: ", summary.Errors.ToString(CultureInfo.CurrentUICulture), false); + ColorConsole.WriteLabel(", Failures: ", summary.Failures.ToString(CultureInfo.CurrentUICulture), false); + ColorConsole.WriteLabel(", Inconclusive: ", summary.Inconclusive.ToString(CultureInfo.CurrentUICulture), true); + + ColorConsole.WriteLabel(" Not run: ", summary.TestsNotRun.ToString(CultureInfo.CurrentUICulture), false); + ColorConsole.WriteLabel(", Invalid: ", summary.NotRunnable.ToString(CultureInfo.CurrentUICulture), false); + ColorConsole.WriteLabel(", Ignored: ", summary.Ignored.ToString(CultureInfo.CurrentUICulture), false); + ColorConsole.WriteLabel(", Skipped: ", summary.Skipped.ToString(CultureInfo.CurrentUICulture), true); + + ColorConsole.WriteLabel(" Start time: ", summary.StartTime.ToString("u"), true); + ColorConsole.WriteLabel(" End time: ", summary.EndTime.ToString("u"), true); + ColorConsole.WriteLabel(" Duration: ", string.Format("{0} seconds", summary.Duration.ToString("0.000")), true); + Console.WriteLine(); + } + + private int ListAssembliesWithNoTests(XmlNode result) + { + int count = 0; + + switch (result.Name) + { + case "test-run": + foreach (XmlNode child in result.ChildNodes) + count += ListAssembliesWithNoTests(child); + break; + + case "test-suite": + if (result.GetAttribute("type") == "Assembly") + { + if (result.GetAttribute("total") == "0") + { + ColorConsole.WriteLine(ColorStyle.Warning, "Warning: No tests found in " + result.GetAttribute("name")); + } + count++; + break; + } + + foreach (XmlNode child in result.ChildNodes) + count += ListAssembliesWithNoTests(child); + break; + } + + return count; + } + + private void WriteErrorsAndFailuresReport() + { + this.reportIndex = 0; + ColorConsole.WriteLine(ColorStyle.SectionHeader, "Errors and Failures"); + WriteErrorsAndFailures(result); + Console.WriteLine(); + } + + private void WriteErrorsAndFailures(XmlNode result) + { + switch(result.Name) + { + case "test-case": + string resultState = result.GetAttribute("result"); + if (resultState == "Failed") + { + using (new ColorConsole(ColorStyle.Failure)) + WriteSingleResult(result); + } + else if (resultState == "Error") + { + using (new ColorConsole(ColorStyle.Error)) + WriteSingleResult(result); + } + else if (resultState == "Cancelled") + { + using (new ColorConsole(ColorStyle.Warning)) + WriteSingleResult(result); + } + return; + + case "test-run": + break; + + case "test-suite": + string resultType = result.GetAttribute("type"); + if (resultType == "Theory") + { + resultState = result.GetAttribute("result"); + if (resultState == "Failed") + { + using (new ColorConsole(ColorStyle.Failure)) + WriteSingleResult(result); + } + } + break; + } + + // TODO: Display failures in fixture setup or teardown + foreach (XmlNode childResult in result.ChildNodes) + WriteErrorsAndFailures(childResult); + } + + + public void WriteNotRunReport() + { + this.reportIndex = 0; + ColorConsole.WriteLine(ColorStyle.SectionHeader, "Tests Not Run"); + WriteNotRunResults(result); + Console.WriteLine(); + } + + private void WriteNotRunResults(XmlNode result) + { + if (result.Name == "test-case") + { + string resultState = result.GetAttribute("result"); + if (resultState == "Skipped" || resultState == "Ignored" || resultState == "NotRunnable") + { + using (new ColorConsole(ColorStyle.Warning)) + WriteSingleResult(result); + } + } + else + { + foreach (XmlNode childResult in result.ChildNodes) + WriteNotRunResults(childResult); + } + } + + private void WriteSingleResult(XmlNode result) + { + string status = result.GetAttribute("label"); + if (status == null) + status = result.GetAttribute("result"); + string fullName = result.GetAttribute("fullname"); + + Console.WriteLine("{0}) {1} : {2}", ++reportIndex, status, fullName); + + XmlNode failureNode = result.SelectSingleNode("failure"); + if (failureNode != null) + { + XmlNode message = failureNode.SelectSingleNode("message"); + XmlNode stacktrace = failureNode.SelectSingleNode("stack-trace"); + + if (message != null) + Console.WriteLine(message.InnerText); + + if (stacktrace != null) + Console.WriteLine(status == "Failed" + ? StackTraceFilter.Filter(stacktrace.InnerText) + : stacktrace.InnerText + Environment.NewLine); + //Console.WriteLine(stacktrace.InnerText + Environment.NewLine); + } + + XmlNode reasonNode = result.SelectSingleNode("reason"); + if (reasonNode != null) + { + XmlNode message = reasonNode.SelectSingleNode("message"); + + if (message != null) + Console.WriteLine(message.InnerText); + } + } + } +} diff --git a/src/nunit-console/ResultSummary.cs b/src/nunit-console/ResultSummary.cs new file mode 100644 index 00000000..f385fd26 --- /dev/null +++ b/src/nunit-console/ResultSummary.cs @@ -0,0 +1,241 @@ +// *********************************************************************** +// Copyright (c) 2007 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Globalization; +using System.Xml; + +namespace NUnit.ConsoleRunner +{ + using Utilities; + + /// + /// Summary description for ResultSummary. + /// + public class ResultSummary + { + private int resultCount = 0; + private int testsRun = 0; + private int failureCount = 0; + private int errorCount = 0; + private int successCount = 0; + private int inconclusiveCount = 0; + private int skipCount = 0; + private int ignoreCount = 0; + private int notRunnable = 0; + + private DateTime startTime = DateTime.MinValue; + private DateTime endTime = DateTime.MaxValue; + private double duration = 0.0d; + private string name; + + public ResultSummary() { } + + public ResultSummary(XmlNode result) + { + if (result.Name != "test-run") + throw new InvalidOperationException("Expected as top-level element but was <" + result.Name + ">"); + + name = result.GetAttribute("name"); + duration = result.GetAttribute("duration", 0.0); + startTime = result.GetAttribute("start-time", DateTime.MinValue); + endTime = result.GetAttribute("end-time", DateTime.MaxValue); + + Summarize(result); + } + + private void Summarize(XmlNode node) + { + switch (node.Name) + { + case "test-case": + resultCount++; + + string outcome = node.GetAttribute("result"); + string label = node.GetAttribute("label"); + if (label != null) + outcome = label; + + switch (outcome) + { + case "Passed": + successCount++; + testsRun++; + break; + case "Failed": + failureCount++; + testsRun++; + break; + case "Error": + case "Cancelled": + errorCount++; + testsRun++; + break; + case "Inconclusive": + inconclusiveCount++; + testsRun++; + break; + case "NotRunnable": + notRunnable++; + //errorCount++; + break; + case "Ignored": + ignoreCount++; + break; + case "Skipped": + default: + skipCount++; + break; + } + break; + + //case "test-suite": + //case "test-fixture": + //case "method-group": + default: + foreach (XmlNode childResult in node.ChildNodes) + Summarize(childResult); + break; + } + } + + public string Name + { + get { return name; } + } + + public bool Success + { + get { return failureCount == 0; } + } + + /// + /// Returns the number of test cases for which results + /// have been summarized. Any tests excluded by use of + /// Category or Explicit attributes are not counted. + /// + public int ResultCount + { + get { return resultCount; } + } + + /// + /// Returns the number of test cases actually run, which + /// is the same as ResultCount, less any Skipped, Ignored + /// or NonRunnable tests. + /// + public int TestsRun + { + get { return testsRun; } + } + + /// + /// Returns the number of tests that passed + /// + public int Passed + { + get { return successCount; } + } + + /// + /// Returns the number of test cases that had an error. + /// + public int Errors + { + get { return errorCount; } + } + + /// + /// Returns the number of test cases that failed. + /// + public int Failures + { + get { return failureCount; } + } + + /// + /// Returns the number of test cases that failed. + /// + public int Inconclusive + { + get { return inconclusiveCount; } + } + + /// + /// Returns the number of test cases that were not runnable + /// due to errors in the signature of the class or method. + /// Such tests are also counted as Errors. + /// + public int NotRunnable + { + get { return notRunnable; } + } + + /// + /// Returns the number of test cases that were skipped. + /// + public int Skipped + { + get { return skipCount; } + } + + public int Ignored + { + get { return ignoreCount; } + } + + /// + /// Gets the start time of the test run. + /// + public DateTime StartTime + { + get { return startTime; } + } + + /// + /// Gets the end time of the test run. + /// + public DateTime EndTime + { + get { return endTime; } + } + + /// + /// Gets the duration of the test run. + /// + public double Duration + { + get { return duration; } + } + + public int TestsNotRun + { + get { return skipCount + ignoreCount + notRunnable; } + } + + public int ErrorsAndFailures + { + get { return errorCount + failureCount; } + } + } +} diff --git a/src/nunit-console/TestEventHandler.cs b/src/nunit-console/TestEventHandler.cs new file mode 100644 index 00000000..f71bdec9 --- /dev/null +++ b/src/nunit-console/TestEventHandler.cs @@ -0,0 +1,218 @@ +// *********************************************************************** +// Copyright (c) 2007 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using System.Diagnostics; +using System.Text.RegularExpressions; +using System.Collections.Generic; +using System.Xml; +using NUnit.Engine; + +namespace NUnit.ConsoleRunner +{ + using Utilities; + + /// + /// TestEventHandler processes events from the running + /// test for the console runner. + /// + public class TestEventHandler : MarshalByRefObject, ITestEventListener + { + private int _testRunCount; + private int _testIgnoreCount; + private int _failureCount; + private int _level; + + private string _displayLabels; + private TextWriter _outWriter; + + private List _messages = new List(); + + + public TestEventHandler(TextWriter outWriter, string displayLabels) + { + _level = 0; + _displayLabels = displayLabels; + _outWriter = outWriter; + } + + #region ITestEventHandler Members + + public void OnTestEvent(string report) + { + XmlDocument doc = new XmlDocument(); + doc.LoadXml(report); + XmlNode testEvent = doc.FirstChild; + + switch (testEvent.Name) + { + case "start-test": + TestStarted(testEvent); + break; + + case "start-suite": + SuiteStarted(testEvent); + break; + + case "start-run": + //RunStarted(testEvent); + break; + + case "test-case": + TestFinished(testEvent); + break; + + case "test-suite": + SuiteFinished(testEvent); + break; + } + + } + + #endregion + + #region Individual Handlers + + private void TestStarted(XmlNode startNode) + { + // Placeolder for TeamCity output + } + + public void SuiteStarted(XmlNode startNode) + { + if (_level++ == 0) + { + _testRunCount = 0; + _testIgnoreCount = 0; + _failureCount = 0; + + XmlAttribute nameAttr = startNode.Attributes["fullname"]; + string suiteName = (nameAttr != null) + ? nameAttr.Value + : ""; + + Trace.WriteLine("################################ UNIT TESTS ################################"); + Trace.WriteLine("Running tests in '" + suiteName + "'..."); + } + } + + public void TestFinished(XmlNode testResult) + { + XmlAttribute resultAttr = testResult.Attributes["result"]; + string resultState = resultAttr == null + ? "Unknown" + : resultAttr.Value; + XmlAttribute nameAttr = testResult.Attributes["fullname"]; + + switch (resultState) + { + case "Failed": + _testRunCount++; + _failureCount++; + + _messages.Add(string.Format("{0}) {1} :", _failureCount, nameAttr.Value)); + break; + + case "Inconclusive": + case "Passed": + _testRunCount++; + break; + + case "Skipped": + _testIgnoreCount++; + break; + } + + var outputNode = testResult.SelectSingleNode("output"); + + if (_displayLabels == "ALL") + WriteTestLabel(nameAttr.Value); + if (outputNode != null) + { + if (_displayLabels == "ON") + WriteTestLabel(nameAttr.Value); + + WriteTestOutput(outputNode); + } + } + + private void WriteTestLabel(string name) + { + using (new ColorConsole(ColorStyle.SectionHeader)) + _outWriter.WriteLine("=> {0}", name); + } + + private void WriteTestOutput(XmlNode outputNode) + { + using (new ColorConsole(ColorStyle.Output)) + { + _outWriter.Write(outputNode.InnerText); + // Some labels were being shown on the same line as the previous output + if (!outputNode.InnerText.EndsWith("\n")) + { + _outWriter.WriteLine(); + } + } + } + + public void SuiteFinished(XmlNode suiteResult) + { + if (--_level == 0) + { + XmlAttribute durationAttribute = suiteResult.Attributes["duration"]; + + Trace.WriteLine("############################################################################"); + + if (_messages.Count == 0) + { + Trace.WriteLine("############## S U C C E S S #################"); + } + else + { + Trace.WriteLine("############## F A I L U R E S #################"); + + foreach (string s in _messages) + { + Trace.WriteLine(s); + } + } + + Trace.WriteLine("############################################################################"); + Trace.WriteLine("Executed tests : " + _testRunCount); + Trace.WriteLine("Ignored tests : " + _testIgnoreCount); + Trace.WriteLine("Failed tests : " + _failureCount); + if ( durationAttribute != null ) + Trace.WriteLine("Total duration : " + durationAttribute.Value + " seconds"); + Trace.WriteLine("############################################################################"); + } + } + + #endregion + + public override object InitializeLifetimeService() + { + return null; + } + } +} diff --git a/src/nunit-console/Utilities/ColorConsole.cs b/src/nunit-console/Utilities/ColorConsole.cs new file mode 100644 index 00000000..50cd365e --- /dev/null +++ b/src/nunit-console/Utilities/ColorConsole.cs @@ -0,0 +1,253 @@ +// *********************************************************************** +// Copyright (c) 2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; + +namespace NUnit.ConsoleRunner.Utilities +{ + public enum ColorStyle + { + /// + /// Color for headers + /// + Header, + /// + /// Color for sub-headers + /// + SubHeader, + /// + /// Color for each of the section headers + /// + SectionHeader, + /// + /// The default color for items that don't fit into the other categories + /// + Default, + /// + /// Test output + /// + Output, + /// + /// Color for labels + /// + Label, + /// + /// Color for values, usually go beside labels + /// + Value, + /// + /// Color for passed tests + /// + Pass, + /// + /// Color for failed tests + /// + Failure, + /// + /// Color for warnings, ignored or skipped tests + /// + Warning, + /// + /// Color for errors and exceptions + /// + Error + } + + /// + /// Sets the console color in the constructor and resets it in the dispose + /// + public class ColorConsole : IDisposable + { + /// + /// Gets or sets the Enabled flag, indicating whether color is + /// being used. This must be set at program startup. + /// + public static bool Enabled { get; set; } + + public ColorConsole(ColorStyle style) + { + if (ColorConsole.Enabled) + Console.ForegroundColor = GetColor(style); + } + + /// + /// Writes the value with the specified style. + /// + /// The style. + /// The value. + public static void Write(ColorStyle style, string value) + { + using (new ColorConsole(style)) + { + Console.Write(value); + } + } + + /// + /// Writes the value with the specified style. + /// + /// The style. + /// The value. + public static void WriteLine(ColorStyle style, string value) + { + using (new ColorConsole(style)) + { + Console.WriteLine(value); + } + } + + /// + /// Writes the label and the option that goes with it and optionally writes a new line. + /// + /// The label. + /// The option. + /// if set to true [write line]. + public static void WriteLabel(string label, string option, bool writeLine) + { + WriteLabel(label, option, ColorStyle.Value, writeLine); + } + + /// + /// Writes the label and the option that goes with it and optionally writes a new line. + /// + /// The label. + /// The option. + /// The color to display the value with + /// if set to true [write line]. + public static void WriteLabel(string label, string option, ColorStyle valueStyle, bool writeLine) + { + Write(ColorStyle.Label, label); + Write(valueStyle, option); + if (writeLine) + Console.WriteLine(); + } + + /// + /// By using styles, we can keep everything consistent + /// + /// + /// + public static ConsoleColor GetColor(ColorStyle style) + { + switch (Console.BackgroundColor) + { + case ConsoleColor.White: + switch (style) + { + case ColorStyle.Header: + return ConsoleColor.DarkBlue; + case ColorStyle.SubHeader: + return ConsoleColor.DarkGray; + case ColorStyle.SectionHeader: + return ConsoleColor.DarkBlue; + case ColorStyle.Label: + return ConsoleColor.DarkGreen; + case ColorStyle.Value: + return ConsoleColor.Blue; + case ColorStyle.Pass: + return ConsoleColor.Green; + case ColorStyle.Failure: + return ConsoleColor.Red; + case ColorStyle.Warning: + return ConsoleColor.Yellow; + case ColorStyle.Error: + return ConsoleColor.Red; + case ColorStyle.Output: + return ConsoleColor.DarkGray; + case ColorStyle.Default: + default: + return ConsoleColor.Green; + } + + case ConsoleColor.Gray: + switch (style) + { + case ColorStyle.Header: + return ConsoleColor.White; + case ColorStyle.SubHeader: + return ConsoleColor.DarkGray; + case ColorStyle.SectionHeader: + return ConsoleColor.Cyan; + case ColorStyle.Label: + return ConsoleColor.Green; + case ColorStyle.Value: + return ConsoleColor.White; + case ColorStyle.Pass: + return ConsoleColor.Green; + case ColorStyle.Failure: + return ConsoleColor.Red; + case ColorStyle.Warning: + return ConsoleColor.Yellow; + case ColorStyle.Error: + return ConsoleColor.Red; + case ColorStyle.Output: + return ConsoleColor.DarkGray; + case ColorStyle.Default: + default: + return ConsoleColor.Green; + } + + default: + switch (style) + { + case ColorStyle.Header: + return ConsoleColor.White; + case ColorStyle.SubHeader: + return ConsoleColor.Gray; + case ColorStyle.SectionHeader: + return ConsoleColor.Cyan; + case ColorStyle.Label: + return ConsoleColor.Green; + case ColorStyle.Value: + return ConsoleColor.White; + case ColorStyle.Pass: + return ConsoleColor.Green; + case ColorStyle.Failure: + return ConsoleColor.Red; + case ColorStyle.Warning: + return ConsoleColor.Yellow; + case ColorStyle.Error: + return ConsoleColor.Red; + case ColorStyle.Output: + return ConsoleColor.Gray; + case ColorStyle.Default: + default: + return ConsoleColor.Green; + } + } + } + + #region Implementation of IDisposable + + /// + /// If color is enabled, restores the console colors to their defaults + /// + public void Dispose() + { + if (ColorConsole.Enabled) + Console.ResetColor(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/nunit-console/Utilities/PackageSettings.cs b/src/nunit-console/Utilities/PackageSettings.cs new file mode 100644 index 00000000..49d58efe --- /dev/null +++ b/src/nunit-console/Utilities/PackageSettings.cs @@ -0,0 +1,131 @@ +// *********************************************************************** +// Copyright (c) 2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +namespace NUnit.ConsoleRunner.Utilities +{ + /// + /// PackageSettings is a static class containing constant values that + /// are used as keys in setting up a TestPackage. These values duplicate + /// settings in the engine and framework. + /// + public static class PackageSettings + { + #region Settings taken from RunnerSettings in the engine + + /// + /// The config to use in loading a project + /// + public const string ActiveConfig = "ActiveConfig"; + + /// + /// If true, the engine should determine the private bin + /// path by examining the paths to all the tests. + /// + public const string AutoBinPath = "AutoBinPath"; + + /// + /// The ApplicationBase to use in loading the tests. + /// + public const string BasePath = "BasePath"; + + /// + /// The config file to use in running the tests + /// + public const string ConfigurationFile = "ConfigurationFile"; + + /// + /// Indicates how to load tests across AppDomains + /// + public const string DomainUsage = "DomainUsage"; + + /// + /// The private binpath used to locate assemblies + /// + public const string PrivateBinPath = "PrivateBinPath"; + + /// + /// Indicates how to allocate assemblies to processes + /// + public const string ProcessModel = "ProcessModel"; + + /// + /// Indicates the desired runtime to use for the tests. + /// + public const string RuntimeFramework = "RuntimeFramework"; + + #endregion + + #region Settings taken from DriverSettings in the framework + + /// + /// The default log threshold to be captured. Defaults to "Error" + /// Use "None" to turn off capture. Other values depend on the + /// logging subsystem. Currently, log4net is supported. + /// + public const string DefaultLogThreshold = "DefaultLogThreshold"; + + /// + /// Integer value in milliseconds for the default timeout value + /// for test cases. If not specified, there is no timeout. + /// + public const string DefaultTimeout = "DefaultTimeout"; + + /// + /// An InternalTraceLevel enumeration value for this run. + /// + public const string InternalTraceLevel = "InternalTraceLevel"; + + /// + /// A TextWriter to which the internal trace will be sent. + /// + public const string InternalTraceWriter = "InternalTraceWriter"; + + /// + /// A list of tests to be loaded. + /// + // TODO: Remove? + public const string LOAD = "LOAD"; + + /// + /// The number of test threads to run for the assembly. + /// + public const string NumberOfTestWorkers = "NumberOfTestWorkers"; + + /// + /// The random seed to be used for this assembly. + /// + public const string RandomSeed = "RandomSeed"; + + /// + /// If true, execution stops after the first error or failure. + /// + public const string StopOnError = "StopOnError"; + + /// + /// Full path of the directory to be used for work and result files. + /// + public const string WorkDirectory = "WorkDirectory"; + + #endregion + } +} diff --git a/src/nunit-console/Utilities/StackTraceFilter.cs b/src/nunit-console/Utilities/StackTraceFilter.cs new file mode 100644 index 00000000..1b597da1 --- /dev/null +++ b/src/nunit-console/Utilities/StackTraceFilter.cs @@ -0,0 +1,73 @@ +// *********************************************************************** +// Copyright (c) 2007 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +namespace NUnit.ConsoleRunner.Utilities +{ + using System; + using System.IO; + + /// + /// Summary description for StackTraceFilter. + /// + public static class StackTraceFilter + { + public static string Filter(string stack) + { + if (stack == null) return null; + StringWriter sw = new StringWriter(); + StringReader sr = new StringReader(stack); + + try + { + string line; + while ((line = sr.ReadLine()) != null) + { + if (!FilterLine(line)) + sw.WriteLine(line.Trim()); + } + } + catch (Exception) + { + return stack; + } + return sw.ToString(); + } + + static bool FilterLine(string line) + { + string[] patterns = new string[] + { + "NUnit.Framework.Assert" + }; + + for (int i = 0; i < patterns.Length; i++) + { + if (line.IndexOf(patterns[i]) > 0) + return true; + } + + return false; + } + + } +} diff --git a/src/nunit-console/Utilities/TestFilterBuilder.cs b/src/nunit-console/Utilities/TestFilterBuilder.cs new file mode 100644 index 00000000..d6883226 --- /dev/null +++ b/src/nunit-console/Utilities/TestFilterBuilder.cs @@ -0,0 +1,96 @@ +// *********************************************************************** +// Copyright (c) 2013 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtainingn +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Engine; + +namespace NUnit.ConsoleRunner.Utilities +{ + public class TestFilterBuilder + { + public IList Tests { get; set; } + public IList Include { get; set; } + public IList Exclude { get; set; } + + public TestFilterBuilder() + { + this.Tests = new List(); + this.Include = new List(); + this.Exclude = new List(); + } + + public TestFilter GetFilter() + { + var tests = new StringBuilder(); + var include = new StringBuilder(); + var exclude = new StringBuilder(); + + if (Tests.Count > 0) + { + tests.Append(""); + foreach (string test in Tests) + tests.AppendFormat("{0}", test); + tests.Append(""); + } + + if (Include.Count > 0) + { + include.Append(""); + bool needComma = false; + foreach (string category in Include) + { + if (needComma) include.Append(','); + include.Append(category); + needComma = true; + } + include.Append(""); + } + + if (Exclude.Count > 0) + { + exclude.Append(""); + bool needComma = false; + foreach (string category in Exclude) + { + if (needComma) exclude.Append(','); + exclude.Append(category); + needComma = true; + } + exclude.Append(""); + } + + var testFilter = new StringBuilder(""); + if (tests.Length > 0) + testFilter.Append(tests.ToString()); + if (include.Length > 0) + testFilter.Append(include.ToString()); + if (exclude.Length > 0) + testFilter.Append(exclude.ToString()); + testFilter.Append(""); + + return new TestFilter(testFilter.ToString()); + } + } +} diff --git a/src/nunit-console/Utilities/TestNameParser.cs b/src/nunit-console/Utilities/TestNameParser.cs new file mode 100644 index 00000000..27ce7086 --- /dev/null +++ b/src/nunit-console/Utilities/TestNameParser.cs @@ -0,0 +1,91 @@ +// **************************************************************** +// Copyright 2011, Charlie Poole +// This is free software licensed under the NUnit license. You may +// obtain a copy of the license at http://nunit.org +// **************************************************************** + +using System.Collections.Generic; + +namespace NUnit.ConsoleRunner.Utilities +{ + /// + /// TestNameParser is used to parse the arguments to the + /// -run option, separating testnames at the correct point. + /// + public class TestNameParser + { + /// + /// Parse the -run argument and return an array of argument + /// + /// argument + /// + public static string[] Parse(string argument) + { + List list = new List(); + + int index = 0; + while (index < argument.Length) + { + string name = GetTestName(argument, ref index); + if (name != null && name != string.Empty) + list.Add(name); + } + + return list.ToArray(); + } + + private static string GetTestName(string argument, ref int index) + { + int separator = GetSeparator(argument, index); + string result; + + if (separator >= 0) + { + result = argument.Substring(index, separator - index).Trim(); + index = separator + 1; + } + else + { + result = argument.Substring(index).Trim(); + index = argument.Length; + } + + return result; + } + + private static int GetSeparator(string argument, int index) + { + int nest = 0; + + while (index < argument.Length) + { + switch (argument[index]) + { + case ',': + if (nest == 0) + return index; + break; + + case '"': + while (++index < argument.Length && argument[index] != '"') + ; + break; + + case '(': + case '<': + nest++; + break; + + case ')': + case '>': + nest--; + break; + } + + index++; + } + + return -1; + } + } +} diff --git a/src/nunit-console/Utilities/XmlHelper.cs b/src/nunit-console/Utilities/XmlHelper.cs new file mode 100644 index 00000000..5919e71d --- /dev/null +++ b/src/nunit-console/Utilities/XmlHelper.cs @@ -0,0 +1,125 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml; + +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] + public sealed class ExtensionAttribute : Attribute { } +} + +namespace NUnit.ConsoleRunner.Utilities +{ + /// + /// XmlHelper provides static methods for basic XML operations + /// + public static class XmlHelper + { + /// + /// Creates a new top level element node. + /// + /// The element name. + /// + public static XmlNode CreateTopLevelElement(string name) + { + XmlDocument doc = new XmlDocument(); + doc.LoadXml( "<" + name + "/>" ); + return doc.FirstChild; + } + + #region Safe Attribute Access + + /// + /// Gets the value of the given attribute. + /// + /// The result. + /// The name. + /// + public static string GetAttribute(this XmlNode result, string name) + { + XmlAttribute attr = result.Attributes[name]; + + return attr == null ? null : attr.Value; + } + + /// + /// Gets the value of the given attribute as an int. + /// + /// The result. + /// The name. + /// The default value. + /// + public static int GetAttribute(this XmlNode result, string name, int defaultValue) + { + XmlAttribute attr = result.Attributes[name]; + + return attr == null + ? defaultValue + : int.Parse(attr.Value, System.Globalization.CultureInfo.InvariantCulture); + } + + /// + /// Gets the value of the given attribute as a double. + /// + /// The result. + /// The name. + /// The default value. + /// + public static double GetAttribute(this XmlNode result, string name, double defaultValue) + { + var attr = result.Attributes[name]; + + double attributeValue; + if ( attr == null || !double.TryParse(attr.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out attributeValue) ) + return defaultValue; + + return attributeValue; + } + + /// + /// Gets the value of the given attribute as a DateTime. + /// + /// The result. + /// The name. + /// The default value. + /// + public static DateTime GetAttribute(this XmlNode result, string name, DateTime defaultValue) + { + string dateStr = GetAttribute(result, name); + if ( dateStr == null ) + return defaultValue; + + DateTime date; + if ( !DateTime.TryParse(dateStr, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AllowWhiteSpaces, out date) ) + return defaultValue; + + return date; + } + + #endregion + } +} diff --git a/src/nunit-console/app.config b/src/nunit-console/app.config new file mode 100644 index 00000000..87f0014b --- /dev/null +++ b/src/nunit-console/app.config @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/nunit-console/nunit-console.build b/src/nunit-console/nunit-console.build new file mode 100644 index 00000000..41c6c25d --- /dev/null +++ b/src/nunit-console/nunit-console.build @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/nunit-console/nunit-console.csproj b/src/nunit-console/nunit-console.csproj new file mode 100644 index 00000000..42576314 --- /dev/null +++ b/src/nunit-console/nunit-console.csproj @@ -0,0 +1,97 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {0DE218CA-AFB8-423A-9CD2-E22DEAC55C46} + Exe + Properties + NUnit.ConsoleRunner + nunit-console + v2.0 + 512 + + + 3.5 + + + + true + full + false + ..\..\bin\Debug\ + TRACE;DEBUG + prompt + 4 + true + nunit-console.tests.dll + + + pdbonly + true + ..\..\bin\Release\ + TRACE + prompt + 4 + + + + + + + ..\..\lib\nunit.framework.dll + + + + + CommonAssemblyInfo.cs + + + CommonVersionInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {775FAD50-3623-4922-97C4-DFB29A8BE4C7} + nunit.engine.api + + + + + \ No newline at end of file diff --git a/src/nunit.engine.api/AssemblyInfo.cs b/src/nunit.engine.api/AssemblyInfo.cs new file mode 100644 index 00000000..72738a97 --- /dev/null +++ b/src/nunit.engine.api/AssemblyInfo.cs @@ -0,0 +1,21 @@ +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("NUnit Engine API")] +[assembly: AssemblyDescription("Defines the interfaces used to access the NUnit Engine")] +[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("c686a4e8-7c4b-4c9e-88e7-65cd1b90ba1e")] + +// The API Assembly is maintained at a fixed version number +[assembly: AssemblyVersion("3.0.0.0")] diff --git a/src/nunit.engine.api/IFrameworkDriver.cs b/src/nunit.engine.api/IFrameworkDriver.cs new file mode 100644 index 00000000..d8ea2f3f --- /dev/null +++ b/src/nunit.engine.api/IFrameworkDriver.cs @@ -0,0 +1,71 @@ +// *********************************************************************** +// Copyright (c) 2009 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Xml; + +namespace NUnit.Engine +{ + /// + /// The IFrameworkDriver interface is implemented by a class that + /// is able to use an external framework to explore or run tests + /// under the engine. + /// + public interface IFrameworkDriver + { + /// + /// Loads the tests in an assembly. + /// + /// An Xml string representing the loaded test + string Load(); + + /// + /// Count the test cases that would be executed. + /// + /// A TestFilter to use in counting the tests + /// The number of test cases counted + int CountTestCases(TestFilter filter); + + /// + /// Executes the tests in an assembly. + /// + /// An ITestEventHandler that receives progress notices + /// A filter that controls which tests are executed + /// An Xml string representing the result + string Run(ITestEventListener listener, TestFilter filter); + + /// + /// Returns information about the tests in an assembly. + /// + /// A filter indicating which tests to include + /// An Xml string representing the tests + string Explore(TestFilter filter); + + /// + /// Cancel the ongoing test run. If no test is running, the call is ignored. + /// + /// If true, cancel any ongoing test threads, otherwise wait for them to complete. + void StopRun(bool force); + } +} diff --git a/src/nunit.engine.api/ILogger.cs b/src/nunit.engine.api/ILogger.cs new file mode 100644 index 00000000..89b7d1bb --- /dev/null +++ b/src/nunit.engine.api/ILogger.cs @@ -0,0 +1,85 @@ +// *********************************************************************** +// Copyright (c) 2012 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; + +namespace NUnit.Engine +{ + /// + /// Interface for logging within the engine + /// + public interface ILogger + { + /// + /// Logs the specified message at the error level. + /// + /// The message. + void Error(string message); + + /// + /// Logs the specified message at the error level. + /// + /// The message. + /// The arguments. + void Error(string message, params object[] args); + + /// + /// Logs the specified message at the warning level. + /// + /// The message. + void Warning(string message); + + /// + /// Logs the specified message at the warning level. + /// + /// The message. + /// The arguments. + void Warning(string message, params object[] args); + + /// + /// Logs the specified message at the info level. + /// + /// The message. + void Info(string message); + + /// + /// Logs the specified message at the info level. + /// + /// The message. + /// The arguments. + void Info(string message, params object[] args); + + /// + /// Logs the specified message at the debug level. + /// + /// The message. + void Debug(string message); + + /// + /// Logs the specified message at the debug level. + /// + /// The message. + /// The arguments. + void Debug(string message, params object[] args); + } +} diff --git a/src/nunit.engine.api/ILogging.cs b/src/nunit.engine.api/ILogging.cs new file mode 100644 index 00000000..83cf2111 --- /dev/null +++ b/src/nunit.engine.api/ILogging.cs @@ -0,0 +1,38 @@ +// *********************************************************************** +// Copyright (c) 2013 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +namespace NUnit.Engine +{ + /// + /// Interface to abstract getting loggers + /// + public interface ILogging + { + /// + /// Gets the logger. + /// + /// The name of the logger to get. + /// + ILogger GetLogger(string name); + } +} diff --git a/src/nunit.engine.api/IRecentFiles.cs b/src/nunit.engine.api/IRecentFiles.cs new file mode 100644 index 00000000..2c46fde2 --- /dev/null +++ b/src/nunit.engine.api/IRecentFiles.cs @@ -0,0 +1,60 @@ +// *********************************************************************** +// Copyright (c) 2007 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; + +namespace NUnit.Engine +{ + /// + /// The IRecentFiles interface is used to isolate the app + /// from various implementations of recent files. + /// + public interface IRecentFiles + { + /// + /// The max number of files saved + /// + int MaxFiles { get; set; } + + /// + /// Get a list of all the file entries + /// + /// The most recent file list + IList Entries { get; } + + /// + /// Set the most recent file name, reordering + /// the saved names as needed and removing the oldest + /// if the max number of files would be exceeded. + /// The current CLR version is used to create the entry. + /// + void SetMostRecent( string fileName ); + + /// + /// Remove a file from the list + /// + /// The name of the file to remove + void Remove( string fileName ); + } +} diff --git a/src/nunit.engine.api/IServiceLocator.cs b/src/nunit.engine.api/IServiceLocator.cs new file mode 100644 index 00000000..f06729fd --- /dev/null +++ b/src/nunit.engine.api/IServiceLocator.cs @@ -0,0 +1,46 @@ +// *********************************************************************** +// Copyright (c) 2013 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; + +namespace NUnit.Engine +{ + /// + /// IServiceLocator allows clients to locate any NUnit services + /// for which the interface is referenced. In normal use, this + /// linits it to those services using interfaces defined in the + /// nunit.engine.api assembly. + /// + public interface IServiceLocator + { + /// + /// Return a specified type of service + /// + T GetService() where T : class; + + /// + /// Return a specified type of service + /// + object GetService(Type serviceType); + } +} diff --git a/src/nunit.engine.api/ISettings.cs b/src/nunit.engine.api/ISettings.cs new file mode 100644 index 00000000..ecb43ce8 --- /dev/null +++ b/src/nunit.engine.api/ISettings.cs @@ -0,0 +1,100 @@ +// *********************************************************************** +// Copyright (c) 2013 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; + +namespace NUnit.Engine +{ + /// + /// Event handler for settings changes + /// + /// The sender. + /// The instance containing the event data. + public delegate void SettingsEventHandler(object sender, SettingsEventArgs args); + + /// + /// Event argument for settings changes + /// + public class SettingsEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// Name of the setting that has changed. + public SettingsEventArgs(string settingName) + { + SettingName = settingName; + } + + /// + /// Gets the name of the setting that has changed + /// + public string SettingName { get; private set; } + } + + /// + /// The ISettings interface is used to access all user + /// settings and options. + /// + public interface ISettings + { + /// + /// Occurs when the settings are changed. + /// + event SettingsEventHandler Changed; + + /// + /// Load a setting from the storage. + /// + /// Name of the setting to load + /// Value of the setting or null + object GetSetting(string settingName); + + /// + /// Load a setting from the storage or return a default value + /// + /// Name of the setting to load + /// Value to return if the setting is missing + /// Value of the setting or the default value + T GetSetting(string settingName, T defaultValue); + + /// + /// Remove a setting from the storage + /// + /// Name of the setting to remove + void RemoveSetting(string settingName); + + /// + /// Remove an entire group of settings from the storage + /// + /// Name of the group to remove + void RemoveGroup(string groupName); + + /// + /// Save a setting in the storage + /// + /// Name of the setting to save + /// Value to be saved + void SaveSetting(string settingName, object settingValue); + } +} diff --git a/src/nunit.engine.api/ITestEngine.cs b/src/nunit.engine.api/ITestEngine.cs new file mode 100644 index 00000000..18b7011e --- /dev/null +++ b/src/nunit.engine.api/ITestEngine.cs @@ -0,0 +1,76 @@ +// *********************************************************************** +// Copyright (c) 2011-2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Xml; + +namespace NUnit.Engine +{ + /// + /// ITestEngine represents an instance of the test engine. + /// Clients wanting to discover, explore or run tests start + /// require an instance of the engine, which is generally + /// acquired from the TestEngineActivator class. + /// + public interface ITestEngine : IDisposable + { + /// + /// Gets the IServiceLocator interface, which gives access to + /// certain services provided by the engine. + /// + IServiceLocator Services { get; } + + /// + /// Gets and sets the directory path used by the engine for saving files. + /// Some services may ignore changes to this path made after initialization. + /// The default value is the current directory. + /// + string WorkDirectory { get; set; } + + /// + /// Gets and sets the InternalTraceLevel used by the engine. Changing this + /// setting after initialization will have no effect. The default value + /// is the value saved in the NUnit settings. + /// + InternalTraceLevel InternalTraceLevel { get; set; } + + /// + /// Create and initialize the standard set of services used in the Engine. + /// The + /// This interface is not normally called by user code. Programs linking + /// only to the nunit.engine.api assembly are given a + /// pre-initialized instance of TestEngine. Programs + /// that link directly to nunit.engine usually do so + /// in order to perform custom initialization. + /// + void InitializeServices(); + + /// + /// Returns a test runner instance for use by clients in discovering, + /// exploring and exeuting tests. + /// + /// The TestPackage for which the runner is intended. + /// An ITestRunner. + ITestRunner GetRunner(TestPackage package); + } +} diff --git a/src/nunit.engine.api/ITestEventListener.cs b/src/nunit.engine.api/ITestEventListener.cs new file mode 100644 index 00000000..cdbe0407 --- /dev/null +++ b/src/nunit.engine.api/ITestEventListener.cs @@ -0,0 +1,44 @@ +// *********************************************************************** +// Copyright (c) 2009 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System.Xml; + +namespace NUnit.Engine +{ + /// + /// The ITestListener interface is used to receive notices of significant + /// events while a test is running. It's single method accepts an Xml string, + /// which may represent any event generated by the test framework, the driver + /// or any of the runners internal to the engine. Use of Xml means that + /// any driver and framework may add additional events and the engine will + /// simply pass them on through this interface. + /// + public interface ITestEventListener + { + /// + /// Handle a progress report or other event. + /// + /// An XML progress report. + void OnTestEvent(string report); + } +} diff --git a/src/nunit.engine.api/ITestRun.cs b/src/nunit.engine.api/ITestRun.cs new file mode 100644 index 00000000..e68e209b --- /dev/null +++ b/src/nunit.engine.api/ITestRun.cs @@ -0,0 +1,53 @@ +// *********************************************************************** +// Copyright (c) 2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml; + +namespace NUnit.Engine +{ + /// + /// The ITestRun class represents an ongoing test run. + /// + public interface ITestRun + { + /// + /// Get the result of the test. + /// + /// An XmlNode representing the test run result + XmlNode Result { get; } + + /// + /// Stop the current test run, specifying whether to force cancellation. + /// If no test is running, the method returns without error. + /// + /// If true, force the stop by cancelling all threads. + /// + /// Note that cancelling the threads is intrinsically unsafe and is only + /// provided on the assumption that tests do not impact production data. + /// + void Stop(bool force); + } +} diff --git a/src/nunit.engine.api/ITestRunner.cs b/src/nunit.engine.api/ITestRunner.cs new file mode 100644 index 00000000..fa17752c --- /dev/null +++ b/src/nunit.engine.api/ITestRunner.cs @@ -0,0 +1,101 @@ +// *********************************************************************** +// Copyright (c) 2011-2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Xml; + +namespace NUnit.Engine +{ + /// + /// Interface implemented by all test runners. + /// + public interface ITestRunner : IDisposable + { + /// + /// Get a flag indicating whether a test is running + /// + bool IsTestRunning { get; } + + /// + /// Load a TestPackage for possible execution + /// + /// An XmlNode representing the loaded package. + /// + /// This method is normally optional, since Explore and Run call + /// it automatially when necessary. The method is kept in order + /// to make it easier to convert older programs that use it. + /// + XmlNode Load(); + + /// + /// Unload any loaded TestPackage. If none is loaded, + /// the call is ignored. + /// + void Unload(); + + /// + /// Reload the current TestPackage + /// + /// An XmlNode representing the loaded package. + XmlNode Reload(); + + /// + /// Count the test cases that would be run under + /// the specified filter. + /// + /// A TestFilter + /// The count of test cases + int CountTestCases(TestFilter filter); + + /// + /// Run the tests in the loaded TestPackage and return a test result. The tests + /// are run synchronously and the listener interface is notified as it progresses. + /// + /// The listener that is notified as the run progresses + /// A TestFilter used to select tests + /// An XmlNode giving the result of the test execution + XmlNode Run(ITestEventListener listener, TestFilter filter); + + /// + /// Start a run of the tests in the loaded TestPackage. The tests are run + /// asynchronously and the listener interface is notified as it progresses. + /// + /// The listener that is notified as the run progresses + /// A TestFilter used to select tests + /// + ITestRun RunAsync(ITestEventListener listener, TestFilter filter); + + /// + /// Cancel the ongoing test run. If no test is running, the call is ignored. + /// + /// If true, cancel any ongoing test threads, otherwise wait for them to complete. + void StopRun(bool force); + + /// + /// Explore a loaded TestPackage and return information about the tests found. + /// + /// The TestFilter to be used in selecting tests to explore. + /// An XmlNode representing the tests fould. + XmlNode Explore(TestFilter filter); + } +} diff --git a/src/nunit.engine.api/InternalTraceLevel.cs b/src/nunit.engine.api/InternalTraceLevel.cs new file mode 100644 index 00000000..f28b7b3e --- /dev/null +++ b/src/nunit.engine.api/InternalTraceLevel.cs @@ -0,0 +1,67 @@ +// *********************************************************************** +// Copyright (c) 2012 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +namespace NUnit.Engine +{ + /// + /// InternalTraceLevel is an enumeration controlling the + /// level of detailed presented in the internal log. + /// + public enum InternalTraceLevel + { + /// + /// Use the default settings as specified by the user. + /// + Default, + + /// + /// Do not display any trace messages + /// + Off, + + /// + /// Display Error messages only + /// + Error, + + /// + /// Display Warning level and higher messages + /// + Warning, + + /// + /// Display informational and higher messages + /// + Info, + + /// + /// Display debug messages and higher - i.e. all messages + /// + Debug, + + /// + /// Display debug messages and higher - i.e. all messages + /// + Verbose = Debug + } +} diff --git a/src/nunit.engine.api/NUnitEngineException.cs b/src/nunit.engine.api/NUnitEngineException.cs new file mode 100644 index 00000000..83f51b35 --- /dev/null +++ b/src/nunit.engine.api/NUnitEngineException.cs @@ -0,0 +1,54 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Runtime.Serialization; + +namespace NUnit.Engine +{ + /// + /// NUnitEngineException is thrown when the engine has been + /// called with improper values or when a particular facility + /// is not available. + /// + [Serializable] + public class NUnitEngineException : Exception + { + /// + /// Construct with a message + /// + public NUnitEngineException(string message) : base(message) { } + + /// + /// Construct with a message and inner exception + /// + /// + /// + public NUnitEngineException(string message, Exception innerException) : base(message, innerException) { } + + /// + /// Serialization constructor + /// + public NUnitEngineException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} diff --git a/src/nunit.engine.api/TestEngineActivator.cs b/src/nunit.engine.api/TestEngineActivator.cs new file mode 100644 index 00000000..72cfc316 --- /dev/null +++ b/src/nunit.engine.api/TestEngineActivator.cs @@ -0,0 +1,75 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; + +namespace NUnit.Engine +{ + // This is a preliminary implementation, which requires a specific version of the engine and only + // loads it if it can be found in the current AppBase and ProbingPath or in the GAC. + // + // TODO: Find the engine in established locations known to NUnit or stored in the registry. + // + // TODO: Find the best available version of the engine. + + /// + /// TestEngineActivator creates an instance of the test engine and returns an ITestEngine interface. + /// + public static class TestEngineActivator + { + private const string DefaultAssemblyName = "nunit.engine, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"; + private const string DefaultTypeName = "NUnit.Engine.TestEngine"; + + #region Public Methods + + /// + /// Create an instance of the test engine using default values for the assembly and type names. + /// + /// An ITestEngine. + public static ITestEngine CreateInstance() + { + return CreateInstance(DefaultAssemblyName, DefaultTypeName); + } + + /// + /// Create an instance of the test engine using provided values for the assembly and type names. + /// This method is intended for use in experimenting with alternative implementations. + /// + /// The name of the assembly to be used. + /// The name of the Type to be used. + /// An ITestEngine. + public static ITestEngine CreateInstance(string assemblyName, string typeName) + { + try + { + return (ITestEngine)AppDomain.CurrentDomain.CreateInstanceAndUnwrap(assemblyName, typeName); + } + catch (Exception ex) + { + throw new Exception("Failed to load the test engine", ex); + } + } + + #endregion + } +} diff --git a/src/nunit.engine.api/TestFilter.cs b/src/nunit.engine.api/TestFilter.cs new file mode 100644 index 00000000..1867b1cf --- /dev/null +++ b/src/nunit.engine.api/TestFilter.cs @@ -0,0 +1,88 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Xml; + +namespace NUnit.Engine +{ + /// + /// Abstract base for all test filters. A filter is represented + /// by an XmlNode with <filter> as it's topmost element. + /// In the console runner, filters serve only to carry this + /// XML representation, as all filtering is done by the engine. + /// + [Serializable] + public class TestFilter + { + [NonSerialized] + private XmlNode xmlNode; + + /// + /// Initializes a new instance of the class. + /// + /// The XML text that specifies the filter. + public TestFilter(string xmlText) + { + Text = xmlText; + } + + /// + /// Initializes a new instance of the class. + /// + /// The XML node that specifies the filter. + public TestFilter(XmlNode node) + { + xmlNode = node; + Text = xmlNode.OuterXml; + } + + /// + /// The empty filter - one that always passes. + /// + public static readonly TestFilter Empty = new TestFilter(""); + + /// + /// Gets the XML representation of this filter as a string. + /// + public string Text { get; private set; } + + /// + /// Gets the XML representation of this filter as an XmlNode + /// + public XmlNode Xml + { + get + { + if(xmlNode == null) + { + XmlDocument doc = new XmlDocument(); + doc.LoadXml(Text); + xmlNode = doc.FirstChild; + } + + return xmlNode; + } + } + } +} diff --git a/src/nunit.engine.api/TestPackage.cs b/src/nunit.engine.api/TestPackage.cs new file mode 100644 index 00000000..a6c30504 --- /dev/null +++ b/src/nunit.engine.api/TestPackage.cs @@ -0,0 +1,143 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.IO; + +namespace NUnit.Engine +{ + /// + /// TestPackage holds information about a set of tests to + /// be loaded by a TestRunner. Each TestPackage represents + /// tests for a single assembly. Multiple assemblies are + /// represented by use of subpackages. + /// + [Serializable] + public class TestPackage + { + private string fullName; + private List testFiles = new List(); + private Dictionary settings = new Dictionary(); + + #region Constructors + + /// + /// Construct a TestPackage, specifying a file path for + /// the assembly or project to be used. + /// + /// The file path. + public TestPackage(string filePath) + { + fullName = Path.GetFullPath(filePath); + if (IsAssemblyFileType(filePath)) + testFiles.Add(FullName); + } + + /// + /// Construct an anonymous TestPackage that wraps + /// multiple assemblies or projects as subpackages. + /// + /// + public TestPackage(IList testFiles) + { + foreach (string testFile in testFiles) + this.testFiles.Add(Path.GetFullPath(testFile)); + } + + #endregion + + #region Properties + + /// + /// Gets the name of the package + /// + public string Name + { + get { return fullName == null ? null : Path.GetFileName(fullName); } + } + + /// + /// Gets the path to the file containing tests. It may be + /// an assembly or a recognized project type. + /// + public string FullName + { + get { return fullName; } + } + + /// + /// Gets an array of the test files contained in this package + /// + public string[] TestFiles + { + get { return testFiles.ToArray(); } + } + + /// + /// Gets the settings dictionary for this package. + /// + public IDictionary Settings + { + get { return settings; } + } + + #endregion + + #region Public Methods + + /// + /// Add a test file to the package. + /// + /// The test file to be added + public void Add(string testFile) + { + testFiles.Add(testFile); + } + + /// + /// Return the value of a setting or a default. + /// + /// The name of the setting + /// The default value + /// + public T GetSetting(string name, T defaultSetting) + { + return Settings.ContainsKey(name) + ? (T)Settings[name] + : defaultSetting; + } + + #endregion + + #region Helper Methods + + private static bool IsAssemblyFileType(string path) + { + string extension = Path.GetExtension(path).ToLower(); + return extension == ".dll" || extension == ".exe"; + } + + #endregion + } +} diff --git a/src/nunit.engine.api/nunit.engine.api.build b/src/nunit.engine.api/nunit.engine.api.build new file mode 100644 index 00000000..0dbbfee6 --- /dev/null +++ b/src/nunit.engine.api/nunit.engine.api.build @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/nunit.engine.api/nunit.engine.api.csproj b/src/nunit.engine.api/nunit.engine.api.csproj new file mode 100644 index 00000000..d7cde9a9 --- /dev/null +++ b/src/nunit.engine.api/nunit.engine.api.csproj @@ -0,0 +1,84 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {775FAD50-3623-4922-97C4-DFB29A8BE4C7} + Library + Properties + NUnit.Engine + nunit.engine.api + v2.0 + 512 + + + 3.5 + + + + true + full + false + ..\..\bin\Debug\ + TRACE;DEBUG + prompt + 4 + ..\..\bin\Debug\nunit.engine.api.xml + + + pdbonly + true + ..\..\bin\Release\ + TRACE + prompt + 4 + ..\..\bin\Release\nunit.engine.api.xml + + + false + + + + + + + + + + + + + + CommonAssemblyInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/nunit.engine.tests/Api/ServiceLocatorTests.cs b/src/nunit.engine.tests/Api/ServiceLocatorTests.cs new file mode 100644 index 00000000..7a0a017e --- /dev/null +++ b/src/nunit.engine.tests/Api/ServiceLocatorTests.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace NUnit.Engine.Api.Tests +{ + public class ServiceLocatorTests + { + private ITestEngine testEngine; + + [SetUp] + public void CreateEngine() + { + testEngine = new TestEngine(); + testEngine.InternalTraceLevel = InternalTraceLevel.Off; + } + + private void CheckAccessToService(Type serviceType) + { + object service = testEngine.Services.GetService(serviceType); + Assert.NotNull(service, "GetService(Type) returned null"); + Assert.That(service, Is.InstanceOf(serviceType)); + } + + private void CheckAccessToService() where T: class + { + T service = testEngine.Services.GetService(); + Assert.NotNull(service, "GetService() returned null"); + } + + [TestCase(typeof(ISettings))] + [TestCase(typeof(IRecentFiles))] + [TestCase(typeof(ITestAgency))] + public void CanAccessUserSettings(Type serviceType) + { + CheckAccessToService(serviceType); + } + } +} diff --git a/src/nunit.engine.tests/Api/TestFilterTests.cs b/src/nunit.engine.tests/Api/TestFilterTests.cs new file mode 100644 index 00000000..2864ae9a --- /dev/null +++ b/src/nunit.engine.tests/Api/TestFilterTests.cs @@ -0,0 +1,66 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtainingn +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Xml; +using NUnit.Engine; +using NUnit.Framework; + +namespace NUnit.Engine.Api.Tests +{ + public class TestFilterTests + { + [Test] + public void EmptyFilter() + { + TestFilter filter = TestFilter.Empty; + Assert.That(filter.Text, Is.EqualTo("")); + Assert.That(filter.Xml.Name, Is.EqualTo("filter")); + Assert.That(filter.Xml.ChildNodes.Count, Is.EqualTo(0)); + } + + [Test] + public void FilterWithOneTest() + { + string text = "My.Test.Name"; + TestFilter filter = new TestFilter(text); + Assert.That(filter.Text, Is.EqualTo(text)); + Assert.That(filter.Xml.Name, Is.EqualTo("filter")); + Assert.That(filter.Xml.SelectSingleNode("tests/test").InnerText, Is.EqualTo("My.Test.Name")); + } + + [Test] + public void FilterWithThreeTests() + { + string text = "My.First.TestMy.Second.TestMy.Third.Test"; + TestFilter filter = new TestFilter(text); + Assert.That(filter.Text, Is.EqualTo(text)); + Assert.That(filter.Xml.Name, Is.EqualTo("filter")); + XmlNodeList testNodes = filter.Xml.SelectNodes("tests/test"); + Assert.That(testNodes.Count, Is.EqualTo(3)); + Assert.That(testNodes[0].InnerText, Is.EqualTo("My.First.Test")); + Assert.That(testNodes[1].InnerText, Is.EqualTo("My.Second.Test")); + Assert.That(testNodes[2].InnerText, Is.EqualTo("My.Third.Test")); + } + } +} diff --git a/src/nunit.engine.tests/Api/TestPackageTests.cs b/src/nunit.engine.tests/Api/TestPackageTests.cs new file mode 100644 index 00000000..41ce44e8 --- /dev/null +++ b/src/nunit.engine.tests/Api/TestPackageTests.cs @@ -0,0 +1,87 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System.IO; +using NUnit.Framework; + +namespace NUnit.Engine.Api.Tests +{ + public class TestPackageTests_SingleAssembly + { + private TestPackage package; + + [SetUp] + public void CreatePackage() + { + package = new TestPackage("test.dll"); + } + + [Test] + public void AssemblyPathIsUsedAsFilePath() + { + Assert.AreEqual(Path.GetFullPath("test.dll"), package.FullName); + } + + [Test] + public void AssemblyPathIsIncludedInList() + { + Assert.AreEqual( + new string[] { Path.GetFullPath("test.dll") }, + package.TestFiles); + } + + [Test] + public void FileNameIsUsedAsPackageName() + { + Assert.That(package.Name, Is.EqualTo("test.dll")); + } + } + + public class TestPackageTests_MultipleAssemblies + { + private TestPackage package; + + [SetUp] + public void CreatePackage() + { + package = new TestPackage(new string[] { "test1.dll", "test2.dll", "test3.dll" }); + } + + [Test] + public void PackageIsAnonymous() + { + Assert.Null(package.FullName); + } + + [Test] + public void AssemblyPathsAreIncludedInList() + { + string[] expectedAssemblies = new string[] { + Path.GetFullPath("test1.dll"), + Path.GetFullPath("test2.dll"), + Path.GetFullPath("test3.dll") }; + + Assert.AreEqual(expectedAssemblies, package.TestFiles); + } + } +} diff --git a/src/nunit.engine.tests/AssemblyInfo.cs b/src/nunit.engine.tests/AssemblyInfo.cs new file mode 100644 index 00000000..4e338a04 --- /dev/null +++ b/src/nunit.engine.tests/AssemblyInfo.cs @@ -0,0 +1,17 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("tests")] +[assembly: AssemblyDescription("Tests of the NUnit Engine")] +[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("37f4a88d-9f41-462e-ac05-93f1d390b700")] diff --git a/src/nunit.engine.tests/Internal/AssemblyHelperTests.cs b/src/nunit.engine.tests/Internal/AssemblyHelperTests.cs new file mode 100644 index 00000000..72a92694 --- /dev/null +++ b/src/nunit.engine.tests/Internal/AssemblyHelperTests.cs @@ -0,0 +1,56 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using NUnit.Framework; + +namespace NUnit.Engine.Internal.Tests +{ + [TestFixture] + public class AssemblyHelperTests + { + [Test] + public void GetPathForAssembly() + { + string path = AssemblyHelper.GetAssemblyPath(this.GetType().Assembly); + Assert.That(Path.GetFileName(path), Is.EqualTo("nunit.engine.tests.dll").IgnoreCase); + Assert.That(File.Exists(path)); + } + + [Test] + public void GetPathForType() + { + string path = AssemblyHelper.GetAssemblyPath(this.GetType()); + Assert.That(Path.GetFileName(path), Is.EqualTo("nunit.engine.tests.dll").IgnoreCase); + Assert.That(File.Exists(path)); + } + + [Test] + public void GetDirectoryName() + { + string path = AssemblyHelper.GetDirectoryName(this.GetType().Assembly); + Assert.That(File.Exists(Path.Combine(path, "nunit.engine.tests.dll"))); + } + } +} diff --git a/src/nunit.engine.tests/Internal/AssemblyReaderTests.cs b/src/nunit.engine.tests/Internal/AssemblyReaderTests.cs new file mode 100644 index 00000000..27f24896 --- /dev/null +++ b/src/nunit.engine.tests/Internal/AssemblyReaderTests.cs @@ -0,0 +1,96 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using NUnit.Framework; +using System.IO; + +namespace NUnit.Engine.Internal.Tests +{ + [TestFixture] + public class AssemblyReaderTests + { + private AssemblyReader rdr; + + [SetUp] + public void CreateReader() + { + rdr = new AssemblyReader( this.GetType().Assembly ); + } + + [TearDown] + public void DisposeReader() + { + if ( rdr != null ) + rdr.Dispose(); + + rdr = null; + } + + [Test] + public void CreateFromPath() + { + string path = AssemblyHelper.GetAssemblyPath(System.Reflection.Assembly.GetAssembly(GetType())); + Assert.AreEqual(path, new AssemblyReader(path).AssemblyPath); + } + + [Test] + public void CreateFromAssembly() + { + string path = AssemblyHelper.GetAssemblyPath(System.Reflection.Assembly.GetAssembly(GetType())); + Assert.AreEqual(path, rdr.AssemblyPath); + } + + [Test] + public void IsValidPeFile() + { + Assert.IsTrue(rdr.IsValidPeFile); + } + + [Test] + public void IsValidPeFile_Fails() + { + string path = AssemblyHelper.GetAssemblyPath(System.Reflection.Assembly.GetAssembly(GetType())); + path = Path.Combine(Path.GetDirectoryName(path), "nunit.engine.api.xml"); + Assert.IsFalse(new AssemblyReader(path).IsValidPeFile); + } + + [Test] + public void IsDotNetFile() + { + Assert.IsTrue( rdr.IsDotNetFile ); + } + + [Test] + public void ImageRuntimeVersion() + { + string runtimeVersion = rdr.ImageRuntimeVersion; + + StringAssert.StartsWith( "v", runtimeVersion ); + new Version( runtimeVersion.Substring( 1 ) ); + // This fails when we force running under a prior version + // Assert.LessOrEqual( version, Environment.Version ); + } + + } +} diff --git a/src/nunit.engine.tests/Internal/NUnitFrameworkDriverTests.cs b/src/nunit.engine.tests/Internal/NUnitFrameworkDriverTests.cs new file mode 100644 index 00000000..f1177eb5 --- /dev/null +++ b/src/nunit.engine.tests/Internal/NUnitFrameworkDriverTests.cs @@ -0,0 +1,291 @@ +// *********************************************************************** +// Copyright (c) 2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Web.UI; +using System.Xml; +using NUnit.Engine.Tests.Assemblies; +using NUnit.Framework; +using NUnit.Framework.Internal; + +namespace NUnit.Engine.Internal.Tests +{ + // Functional tests of the NUnitFrameworkDriver calling into the framework. + public class NUnitFrameworkDriverTests + { + private string MOCK_ASSEMBLY = "mock-assembly.dll"; + private const string MISSING_FILE = "junk.dll"; + private const string BAD_FILE = "mock-assembly.pdb"; + + private IDictionary _settings = new Dictionary(); + private NUnitFrameworkDriver _driver; + private ICallbackEventHandler _handler; + private string _mockAssemblyPath; + + [SetUp] + public void CreateDriver() + { + _mockAssemblyPath = PathUtils.Combine(TestContext.CurrentContext.TestDirectory, MOCK_ASSEMBLY); + _driver = new NUnitFrameworkDriver(AppDomain.CurrentDomain, _mockAssemblyPath, _settings); + _handler = new CallbackEventHandler(); + } + + #region Construction Test + //[Test] + //public void ConstructContoller() + //{ + // Assert.That(_controller..Builder, Is.TypeOf()); + // Assert.That(_controller.Runner, Is.TypeOf()); + // Assert.That(_controller.AssemblyPath, Is.EqualTo(MOCK_ASSEMBLY)); + // Assert.That(_controller.Settings, Is.SameAs(_settings)); + //} + #endregion + + #region Load + [Test] + public void Load_GoodFile_ReturnsRunnableSuite() + { + var result = XmlHelper.CreateXmlNode(_driver.Load()); + + Assert.That(result.Name, Is.EqualTo("test-suite")); + Assert.That(result.GetAttribute("type"), Is.EqualTo("Assembly")); + Assert.That(result.GetAttribute("runstate"), Is.EqualTo("Runnable")); + Assert.That(result.GetAttribute("testcasecount"), Is.EqualTo(MockAssembly.Tests.ToString())); + Assert.That(result.SelectNodes("test-suite").Count, Is.EqualTo(0), "Load result should not have child tests"); + } + + [Test] + public void Load_FileNotFound_ReturnsNonRunnableSuite() + { + var result = XmlHelper.CreateXmlNode( + new NUnitFrameworkDriver(AppDomain.CurrentDomain, MISSING_FILE, _settings).Load()); + + Assert.That(result.Name, Is.EqualTo("test-suite")); + Assert.That(result.GetAttribute("type"), Is.EqualTo("Assembly")); + Assert.That(result.GetAttribute("runstate"), Is.EqualTo("NotRunnable")); + Assert.That(result.GetAttribute("testcasecount"), Is.EqualTo("0")); + Assert.That(GetSkipReason(result), Is.StringStarting("Could not load").And.Contains(MISSING_FILE)); + Assert.That(result.SelectNodes("test-suite").Count, Is.EqualTo(0), "Load result should not have child tests"); + } + + [Test] + public void Load_BadFile_ReturnsNonRunnableSuite() + { + var result = XmlHelper.CreateXmlNode( + new NUnitFrameworkDriver(AppDomain.CurrentDomain, BAD_FILE, _settings).Load()); + + Assert.That(result.Name, Is.EqualTo("test-suite")); + Assert.That(result.GetAttribute("type"), Is.EqualTo("Assembly")); + Assert.That(result.GetAttribute("runstate"), Is.EqualTo("NotRunnable")); + Assert.That(GetSkipReason(result), Is.StringStarting("Could not load").And.Contains(BAD_FILE)); + Assert.That(result.SelectNodes("test-suite").Count, Is.EqualTo(0), "Load result should not have child tests"); + } + #endregion + + #region Explore + [Test] + public void Explore_AfterLoad_ReturnsRunnableSuite() + { + _driver.Load(); + var result = XmlHelper.CreateXmlNode(_driver.Explore(TestFilter.Empty)); + + Assert.That(result.Name, Is.EqualTo("test-suite")); + Assert.That(result.GetAttribute("type"), Is.EqualTo("Assembly")); + Assert.That(result.GetAttribute("runstate"), Is.EqualTo("Runnable")); + Assert.That(result.GetAttribute("testcasecount"), Is.EqualTo(MockAssembly.Tests.ToString())); + Assert.That(result.SelectNodes("test-suite").Count, Is.GreaterThan(0), "Explore result should have child tests"); + } + + [Test] + public void ExploreTestsAction_WithoutLoad_ThrowsInvalidOperationException() + { + var ex = Assert.Catch(() => _driver.Explore(TestFilter.Empty)); + if (ex is System.Reflection.TargetInvocationException) + ex = ex.InnerException; + Assert.That(ex, Is.TypeOf()); + Assert.That(ex.Message, Is.EqualTo("The Explore method was called but no test has been loaded")); + } + + [Test] + public void ExploreTestsAction_FileNotFound_ReturnsNonRunnableSuite() + { + var driver = new NUnitFrameworkDriver(AppDomain.CurrentDomain, MISSING_FILE, _settings); + driver.Load(); + var result = XmlHelper.CreateXmlNode(driver.Explore(TestFilter.Empty)); + + Assert.That(result.Name, Is.EqualTo("test-suite")); + Assert.That(result.GetAttribute("type"), Is.EqualTo("Assembly")); + Assert.That(result.GetAttribute("runstate"), Is.EqualTo("NotRunnable")); + Assert.That(result.GetAttribute("testcasecount"), Is.EqualTo("0")); + Assert.That(GetSkipReason(result), Is.StringStarting("Could not load").And.Contains(MISSING_FILE)); + Assert.That(result.SelectNodes("test-suite").Count, Is.EqualTo(0), "Result should not have child tests"); + } + + [Test] + public void ExploreTestsAction_BadFile_ReturnsNonRunnableSuite() + { + var driver = new NUnitFrameworkDriver(AppDomain.CurrentDomain, BAD_FILE, _settings); + driver.Load(); + var result = XmlHelper.CreateXmlNode(driver.Explore(TestFilter.Empty)); + + Assert.That(result.Name, Is.EqualTo("test-suite")); + Assert.That(result.GetAttribute("type"), Is.EqualTo("Assembly")); + Assert.That(result.GetAttribute("runstate"), Is.EqualTo("NotRunnable")); + Assert.That(result.GetAttribute("testcasecount"), Is.EqualTo("0")); + Assert.That(GetSkipReason(result), Is.StringStarting("Could not load").And.Contains(BAD_FILE)); + Assert.That(result.SelectNodes("test-suite").Count, Is.EqualTo(0), "Result should not have child tests"); + } + #endregion + + #region CountTests + [Test] + public void CountTestsAction_AfterLoad_ReturnsCorrectCount() + { + _driver.Load(); + Assert.That(_driver.CountTestCases(TestFilter.Empty), Is.EqualTo(MockAssembly.Tests - MockAssembly.Explicit)); + } + + [Test] + public void CountTestsAction_WithoutLoad_ThrowsInvalidOperationException() + { + var ex = Assert.Catch(() => _driver.CountTestCases(TestFilter.Empty)); + if (ex is System.Reflection.TargetInvocationException) + ex = ex.InnerException; + Assert.That(ex, Is.TypeOf()); + Assert.That(ex.Message, Is.EqualTo("The CountTestCases method was called but no test has been loaded")); + } + + [Test] + public void CountTestsAction_FileNotFound_ReturnsZero() + { + var driver = new NUnitFrameworkDriver(AppDomain.CurrentDomain, MISSING_FILE, _settings); + driver.Load(); + Assert.That(driver.CountTestCases(TestFilter.Empty), Is.EqualTo(0)); + } + + [Test] + public void CountTestsAction_BadFile_ReturnsZero() + { + var driver = new NUnitFrameworkDriver(AppDomain.CurrentDomain, BAD_FILE, _settings); + driver.Load(); + Assert.That(driver.CountTestCases(TestFilter.Empty), Is.EqualTo(0)); + } + #endregion + + #region RunTests + [Test] + public void RunTestsAction_AfterLoad_ReturnsRunnableSuite() + { + _driver.Load(); + var result = XmlHelper.CreateXmlNode(_driver.Run(new NullListener(), TestFilter.Empty)); + + Assert.That(result.Name, Is.EqualTo("test-suite")); + Assert.That(result.GetAttribute("type"), Is.EqualTo("Assembly")); + Assert.That(result.GetAttribute("runstate"), Is.EqualTo("Runnable")); + Assert.That(result.GetAttribute("testcasecount"), Is.EqualTo(MockAssembly.Tests.ToString())); + Assert.That(result.GetAttribute("result"), Is.EqualTo("Failed")); + Assert.That(result.GetAttribute("passed"), Is.EqualTo(MockAssembly.Success.ToString())); + Assert.That(result.GetAttribute("failed"), Is.EqualTo(MockAssembly.ErrorsAndFailures.ToString())); + Assert.That(result.GetAttribute("skipped"), Is.EqualTo((MockAssembly.NotRunnable + MockAssembly.Ignored).ToString())); + Assert.That(result.GetAttribute("inconclusive"), Is.EqualTo(MockAssembly.Inconclusive.ToString())); + Assert.That(result.SelectNodes("test-suite").Count, Is.GreaterThan(0), "Explore result should have child tests"); + } + + [Test] + public void RunTestsAction_WithoutLoad_ThrowsInvalidOperationException() + { + var ex = Assert.Catch(() => _driver.Run(new NullListener(), TestFilter.Empty)); + if (ex is System.Reflection.TargetInvocationException) + ex = ex.InnerException; + Assert.That(ex, Is.TypeOf()); + Assert.That(ex.Message, Is.EqualTo("The Run method was called but no test has been loaded")); + } + + [Test] + public void RunTestsAction_FileNotFound_ReturnsNonRunnableSuite() + { + var driver = new NUnitFrameworkDriver(AppDomain.CurrentDomain, MISSING_FILE, _settings); + driver.Load(); + var result = XmlHelper.CreateXmlNode(driver.Run(new NullListener(), TestFilter.Empty)); + + Assert.That(result.Name, Is.EqualTo("test-suite")); + Assert.That(result.GetAttribute("type"), Is.EqualTo("Assembly")); + Assert.That(result.GetAttribute("runstate"), Is.EqualTo("NotRunnable")); + Assert.That(result.GetAttribute("testcasecount"), Is.EqualTo("0")); + Assert.That(GetSkipReason(result), Is.StringStarting("Could not load").And.Contains(MISSING_FILE)); + Assert.That(result.SelectNodes("test-suite").Count, Is.EqualTo(0), "Load result should not have child tests"); + } + + [Test] + public void RunTestsAction_BadFile_ReturnsNonRunnableSuite() + { + var driver = new NUnitFrameworkDriver(AppDomain.CurrentDomain, BAD_FILE, _settings); + driver.Load(); + var result = XmlHelper.CreateXmlNode(driver.Run(new NullListener(), TestFilter.Empty)); + + Assert.That(result.Name, Is.EqualTo("test-suite")); + Assert.That(result.GetAttribute("type"), Is.EqualTo("Assembly")); + Assert.That(result.GetAttribute("runstate"), Is.EqualTo("NotRunnable")); + Assert.That(result.GetAttribute("testcasecount"), Is.EqualTo("0")); + Assert.That(GetSkipReason(result), Is.StringStarting("Could not load").And.Contains(BAD_FILE)); + Assert.That(result.SelectNodes("test-suite").Count, Is.EqualTo(0), "Load result should not have child tests"); + } + #endregion + + #region Helper Methods + private static string GetSkipReason(XmlNode result) + { + var propNode = result.SelectSingleNode(string.Format("properties/property[@name='{0}']", PropertyNames.SkipReason)); + return propNode == null ? null : propNode.GetAttribute("value"); + } + #endregion + + #region Nested Callback Class + private class CallbackEventHandler : System.Web.UI.ICallbackEventHandler + { + private string _result; + + public string GetCallbackResult() + { + return _result; + } + + public void RaiseCallbackEvent(string eventArgument) + { + _result = eventArgument; + } + } + #endregion + + #region Nested NullListener Class + public class NullListener : ITestEventListener + { + public void OnTestEvent(string testEvent) + { + // No action + } + } + #endregion + } +} diff --git a/src/nunit.engine.tests/Internal/NUnitProjectTests.cs b/src/nunit.engine.tests/Internal/NUnitProjectTests.cs new file mode 100644 index 00000000..b2d83bcf --- /dev/null +++ b/src/nunit.engine.tests/Internal/NUnitProjectTests.cs @@ -0,0 +1,198 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using NUnit.Framework; + +namespace NUnit.Engine.Internal.Tests +{ + [TestFixture] + public class NUnitProjectTests + { + private NUnitProject project; + private static readonly char SEP = Path.DirectorySeparatorChar; + + [SetUp] + public void Setup() + { + project = new NUnitProject(); + } + + [Test] + public void CanLoadEmptyProject() + { + project.LoadXml(NUnitProjectXml.EmptyProject); + + //Assert.AreEqual(Path.GetFullPath(xmlfile), project.ProjectPath); + //Assert.AreEqual(Path.GetDirectoryName(project.ProjectPath), project.EffectiveBasePath); + + Assert.AreEqual(0, project.Configs.Count); + + Assert.AreEqual(null, project.ActiveConfig); + } + + [Test] + public void LoadEmptyConfigs() + { + project.LoadXml(NUnitProjectXml.EmptyConfigs); + + //Assert.AreEqual(Path.GetFullPath(xmlfile), project.ProjectPath); + //Assert.AreEqual(Path.GetDirectoryName(project.ProjectPath), project.EffectiveBasePath); + + Assert.AreEqual(2, project.Configs.Count); + Assert.AreEqual("Debug", project.ActiveConfig.Name); + + IProjectConfig config1 = project.Configs["Debug"]; + Assert.AreEqual("Debug", config1.Name); + Assert.AreEqual(1, config1.Settings.Count); + Assert.AreEqual(true, config1.Settings["AutoBinPath"]); + + IProjectConfig config2 = project.Configs["Release"]; + Assert.AreEqual("Release", config2.Name); + Assert.AreEqual(1, config2.Settings.Count); + Assert.AreEqual(true, config2.Settings["AutoBinPath"]); + } + + [Test] + public void LoadNormalProject() + { + project.LoadXml(NUnitProjectXml.NormalProject); + + //Assert.AreEqual(Path.GetFullPath(xmlfile), project.ProjectPath); + //Assert.AreEqual(Path.GetDirectoryName(project.ProjectPath), project.EffectiveBasePath); + + Assert.AreEqual(2, project.Configs.Count); + Assert.AreEqual("Debug", project.ActiveConfig.Name); + + IProjectConfig config1 = project.Configs["Debug"]; + Assert.AreEqual(2, config1.Assemblies.Length); + Assert.AreEqual( + "bin" + SEP + "debug" + SEP + "assembly1.dll", + config1.Assemblies[0]); + Assert.AreEqual( + "bin" + SEP + "debug" + SEP + "assembly2.dll", + config1.Assemblies[1]); + + Assert.AreEqual(2, config1.Settings.Count); + Assert.AreEqual("bin" + SEP + "debug", config1.Settings["BasePath"]); + Assert.AreEqual(true, config1.Settings["AutoBinPath"]); + + IProjectConfig config2 = project.Configs["Release"]; + Assert.AreEqual(2, config2.Assemblies.Length); + Assert.AreEqual( + "bin" + SEP + "release" + SEP + "assembly1.dll", + config2.Assemblies[0]); + Assert.AreEqual( + "bin" + SEP + "release" + SEP + "assembly2.dll", + config2.Assemblies[1]); + + Assert.AreEqual(2, config2.Settings.Count); + Assert.AreEqual("bin" + SEP + "release", config2.Settings["BasePath"]); + Assert.AreEqual(true, config2.Settings["AutoBinPath"]); + } + + [Test] + public void LoadProjectWithManualBinPath() + { + project.LoadXml(NUnitProjectXml.ManualBinPathProject); + + //Assert.AreEqual(Path.GetFullPath(xmlfile), project.ProjectPath); + //Assert.AreEqual(Path.GetDirectoryName(project.ProjectPath), project.EffectiveBasePath); + + Assert.AreEqual(1, project.Configs.Count); + + IProjectConfig config1 = project.Configs["Debug"]; + Assert.AreEqual("Debug", config1.Name); + Assert.AreEqual(1, config1.Settings.Count); + Assert.AreEqual("bin_path_value", config1.Settings["PrivateBinPath"]); + } + + [Test] + public void LoadProjectWithComplexSettings() + { + project.LoadXml(NUnitProjectXml.ComplexSettingsProject); + + Assert.AreEqual(2, project.Configs.Count); + + IProjectConfig config1 = project.Configs["Debug"]; + Assert.AreEqual(5, config1.Settings.Count); + Assert.AreEqual("bin" + SEP + "debug", config1.Settings["BasePath"]); + Assert.AreEqual(true, config1.Settings["AutoBinPath"]); + Assert.AreEqual("Separate", config1.Settings["ProcessModel"]); + Assert.AreEqual("Multiple", config1.Settings["DomainUsage"]); + Assert.AreEqual("v2.0", config1.Settings["RuntimeFramework"]); + + Assert.AreEqual(2, config1.Assemblies.Length); + Assert.AreEqual( + "bin" + SEP + "debug" + SEP + "assembly1.dll", + config1.Assemblies[0]); + Assert.AreEqual( + "bin" + SEP + "debug" + SEP + "assembly2.dll", + config1.Assemblies[1]); + + IProjectConfig config2 = project.Configs["Release"]; + Assert.AreEqual(5, config2.Settings.Count); + Assert.AreEqual(true, config2.Settings["AutoBinPath"]); + Assert.AreEqual("Separate", config2.Settings["ProcessModel"]); + Assert.AreEqual("Multiple", config2.Settings["DomainUsage"]); + Assert.AreEqual("v4.0", config2.Settings["RuntimeFramework"]); + + Assert.AreEqual(2, config2.Assemblies.Length); + Assert.AreEqual( + "bin" + SEP + "release", + config2.Settings["BasePath"]); + Assert.AreEqual( + "bin" + SEP + "release" + SEP + "assembly1.dll", + config2.Assemblies[0]); + Assert.AreEqual( + "bin" + SEP + "release" + SEP + "assembly2.dll", + config2.Assemblies[1]); + } + + //[Test] + //public void MakeTestPackage() + //{ + // project.LoadXml(NUnitProjectXml.ComplexSettingsProject); + // IProjectConfig config = project.Configs["Release"]; + // TestPackage package = config.MakeTestPackage(); + + // Assert.AreEqual("bin" + SEP + "release", package.Settings["BasePath"]); + // Assert.AreEqual(ProcessModel.Separate, package.Settings["ProcessModel"]); + // Assert.AreEqual(DomainUsage.Multiple, package.Settings["DomainUsage"]); + + // RuntimeFramework framework = (RuntimeFramework)package.Settings["RuntimeFramework"]; + // Assert.AreEqual(RuntimeType.Any, framework.Runtime); + // Assert.AreEqual(new Version(4, 0), framework.ClrVersion); + + // Assert.True(package.HasSubPackages); + // Assert.AreEqual(2, package.SubPackages.Length); + // Assert.AreEqual( + // Path.GetFullPath(config.Assemblies[0]), + // package.SubPackages[0].FilePath); + // Assert.AreEqual( + // Path.GetFullPath(config.Assemblies[1]), + // package.SubPackages[1].FilePath); + //} + } +} diff --git a/src/nunit.engine.tests/Internal/NUnitProjectXml.cs b/src/nunit.engine.tests/Internal/NUnitProjectXml.cs new file mode 100644 index 00000000..285b21c8 --- /dev/null +++ b/src/nunit.engine.tests/Internal/NUnitProjectXml.cs @@ -0,0 +1,75 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; + +namespace NUnit.Engine.Internal.Tests +{ + /// + /// Summary description for NUnitProjectXml. + /// + public class NUnitProjectXml + { + public static readonly string EmptyProject = ""; + + public static readonly string EmptyConfigs = + "" + System.Environment.NewLine + + " " + System.Environment.NewLine + + " " + System.Environment.NewLine + + " " + System.Environment.NewLine + + ""; + + public static readonly string NormalProject = + "" + System.Environment.NewLine + + " " + System.Environment.NewLine + + " " + System.Environment.NewLine + + " " + System.Environment.NewLine + + " " + System.Environment.NewLine + + " " + System.Environment.NewLine + + " " + System.Environment.NewLine + + " " + System.Environment.NewLine + + " " + System.Environment.NewLine + + " " + System.Environment.NewLine + + ""; + + public static readonly string ManualBinPathProject = + "" + System.Environment.NewLine + + " " + System.Environment.NewLine + + " " + System.Environment.NewLine + + ""; + + public static readonly string ComplexSettingsProject = + "" + System.Environment.NewLine + + " " + System.Environment.NewLine + + " " + System.Environment.NewLine + + " " + System.Environment.NewLine + + " " + System.Environment.NewLine + + " " + System.Environment.NewLine + + " " + System.Environment.NewLine + + " " + System.Environment.NewLine + + " " + System.Environment.NewLine + + " " + System.Environment.NewLine + + ""; + } +} diff --git a/src/nunit.engine.tests/Internal/PathUtilTests.cs b/src/nunit.engine.tests/Internal/PathUtilTests.cs new file mode 100644 index 00000000..6259854b --- /dev/null +++ b/src/nunit.engine.tests/Internal/PathUtilTests.cs @@ -0,0 +1,217 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using NUnit.Framework; + +namespace NUnit.Engine.Internal.Tests +{ + [TestFixture] + public class PathUtilTests : PathUtils + { + [Test] + public void CheckDefaults() + { + Assert.AreEqual( Path.DirectorySeparatorChar, PathUtils.DirectorySeparatorChar ); + Assert.AreEqual( Path.AltDirectorySeparatorChar, PathUtils.AltDirectorySeparatorChar ); + } + } + + // Local Assert extension + internal class Assert : NUnit.Framework.Assert + { + public static void SamePathOrUnder( string path1, string path2 ) + { + string msg = "\r\n\texpected: Same path or under <{0}>\r\n\t but was: <{1}>"; + Assert.IsTrue( PathUtils.SamePathOrUnder( path1, path2 ), msg, path1, path2 ); + } + + public static void NotSamePathOrUnder( string path1, string path2 ) + { + string msg = "\r\n\texpected: Not same path or under <{0}>\r\n\t but was: <{1}>"; + Assert.IsFalse( PathUtils.SamePathOrUnder( path1, path2 ), msg, path1, path2 ); + } + } + + [TestFixture] + public class PathUtilTests_Windows : PathUtils + { + [OneTimeSetUp] + public static void SetUpUnixSeparators() + { + PathUtils.DirectorySeparatorChar = '\\'; + PathUtils.AltDirectorySeparatorChar = '/'; + } + + [OneTimeTearDown] + public static void RestoreDefaultSeparators() + { + PathUtils.DirectorySeparatorChar = System.IO.Path.DirectorySeparatorChar; + PathUtils.AltDirectorySeparatorChar = System.IO.Path.AltDirectorySeparatorChar; + } + + [Test] + public void Canonicalize() + { + Assert.AreEqual( @"C:\folder1\file.tmp", + PathUtils.Canonicalize( @"C:\folder1\.\folder2\..\file.tmp" ) ); + Assert.AreEqual( @"folder1\file.tmp", + PathUtils.Canonicalize( @"folder1\.\folder2\..\file.tmp" ) ); + Assert.AreEqual( @"folder1\file.tmp", + PathUtils.Canonicalize( @"folder1\folder2\.\..\file.tmp" ) ); + Assert.AreEqual( @"file.tmp", + PathUtils.Canonicalize( @"folder1\folder2\..\.\..\file.tmp" ) ); + Assert.AreEqual( @"file.tmp", + PathUtils.Canonicalize( @"folder1\folder2\..\..\..\file.tmp" ) ); + } + + [Test] + [Platform(Exclude="Linux")] + public void RelativePath() + { + Assert.AreEqual( @"folder2\folder3", PathUtils.RelativePath( + @"c:\folder1", @"c:\folder1\folder2\folder3" ) ); + Assert.AreEqual( @"..\folder2\folder3", PathUtils.RelativePath( + @"c:\folder1", @"c:\folder2\folder3" ) ); + Assert.AreEqual( @"bin\debug", PathUtils.RelativePath( + @"c:\folder1", @"bin\debug" ) ); + Assert.IsNull( PathUtils.RelativePath( @"C:\folder", @"D:\folder" ), + "Unrelated paths should return null" ); + Assert.IsNull(PathUtils.RelativePath(@"C:\", @"D:\"), + "Unrelated roots should return null"); + Assert.IsNull(PathUtils.RelativePath(@"C:", @"D:"), + "Unrelated roots (no trailing separators) should return null"); + Assert.AreEqual(string.Empty, + PathUtils.RelativePath(@"C:\folder1", @"C:\folder1")); + Assert.AreEqual(string.Empty, + PathUtils.RelativePath(@"C:\", @"C:\")); + + // First filePath consisting just of a root: + Assert.AreEqual(@"folder1\folder2", PathUtils.RelativePath( + @"C:\", @"C:\folder1\folder2")); + + // Trailing directory separator in first filePath shall be ignored: + Assert.AreEqual(@"folder2\folder3", PathUtils.RelativePath( + @"c:\folder1\", @"c:\folder1\folder2\folder3")); + + // Case-insensitive behaviour, preserving 2nd filePath directories in result: + Assert.AreEqual(@"Folder2\Folder3", PathUtils.RelativePath( + @"C:\folder1", @"c:\folder1\Folder2\Folder3")); + Assert.AreEqual(@"..\Folder2\folder3", PathUtils.RelativePath( + @"c:\folder1", @"C:\Folder2\folder3")); + } + + [Test] + public void SamePathOrUnder() + { + Assert.SamePathOrUnder( @"C:\folder1\folder2\folder3", @"c:\folder1\.\folder2\junk\..\folder3" ); + Assert.SamePathOrUnder( @"C:\folder1\folder2\", @"c:\folder1\.\folder2\junk\..\folder3" ); + Assert.SamePathOrUnder( @"C:\folder1\folder2", @"c:\folder1\.\folder2\junk\..\folder3" ); + Assert.SamePathOrUnder( @"C:\folder1\folder2", @"c:\folder1\.\Folder2\junk\..\folder3" ); + Assert.NotSamePathOrUnder( @"C:\folder1\folder2", @"c:\folder1\.\folder22\junk\..\folder3" ); + Assert.NotSamePathOrUnder( @"C:\folder1\folder2ile.tmp", @"D:\folder1\.\folder2\folder3\file.tmp" ); + Assert.NotSamePathOrUnder( @"C:\", @"D:\" ); + Assert.SamePathOrUnder( @"C:\", @"c:\" ); + Assert.SamePathOrUnder( @"C:\", @"c:\bin\debug" ); + + } + } + + [TestFixture] + public class PathUtilTests_Unix : PathUtils + { + [OneTimeSetUp] + public static void SetUpUnixSeparators() + { + PathUtils.DirectorySeparatorChar = '/'; + PathUtils.AltDirectorySeparatorChar = '\\'; + } + + [OneTimeTearDown] + public static void RestoreDefaultSeparators() + { + PathUtils.DirectorySeparatorChar = System.IO.Path.DirectorySeparatorChar; + PathUtils.AltDirectorySeparatorChar = System.IO.Path.AltDirectorySeparatorChar; + } + + [Test] + public void Canonicalize() + { + Assert.AreEqual( "/folder1/file.tmp", + PathUtils.Canonicalize( "/folder1/./folder2/../file.tmp" ) ); + Assert.AreEqual( "folder1/file.tmp", + PathUtils.Canonicalize( "folder1/./folder2/../file.tmp" ) ); + Assert.AreEqual( "folder1/file.tmp", + PathUtils.Canonicalize( "folder1/folder2/./../file.tmp" ) ); + Assert.AreEqual( "file.tmp", + PathUtils.Canonicalize( "folder1/folder2/.././../file.tmp" ) ); + Assert.AreEqual( "file.tmp", + PathUtils.Canonicalize( "folder1/folder2/../../../file.tmp" ) ); + } + + [Test] + public void RelativePath() + { + Assert.AreEqual( "folder2/folder3", + PathUtils.RelativePath( "/folder1", "/folder1/folder2/folder3" ) ); + Assert.AreEqual( "../folder2/folder3", + PathUtils.RelativePath( "/folder1", "/folder2/folder3" ) ); + Assert.AreEqual( "bin/debug", + PathUtils.RelativePath( "/folder1", "bin/debug" ) ); + Assert.AreEqual( "../other/folder", + PathUtils.RelativePath( "/folder", "/other/folder" ) ); + Assert.AreEqual( "../../d", + PathUtils.RelativePath( "/a/b/c", "/a/d" ) ); + Assert.AreEqual(string.Empty, + PathUtils.RelativePath("/a/b", "/a/b")); + Assert.AreEqual(string.Empty, + PathUtils.RelativePath("/", "/")); + + // First filePath consisting just of a root: + Assert.AreEqual("folder1/folder2", PathUtils.RelativePath( + "/", "/folder1/folder2")); + + // Trailing directory separator in first filePath shall be ignored: + Assert.AreEqual("folder2/folder3", PathUtils.RelativePath( + "/folder1/", "/folder1/folder2/folder3")); + + // Case-sensitive behaviour: + Assert.AreEqual("../Folder1/Folder2/folder3", + PathUtils.RelativePath("/folder1", "/Folder1/Folder2/folder3"), + "folders differing in case"); + } + + [Test] + public void SamePathOrUnder() + { + Assert.SamePathOrUnder( "/folder1/folder2/folder3", "/folder1/./folder2/junk/../folder3" ); + Assert.SamePathOrUnder( "/folder1/folder2/", "/folder1/./folder2/junk/../folder3" ); + Assert.SamePathOrUnder( "/folder1/folder2", "/folder1/./folder2/junk/../folder3" ); + Assert.NotSamePathOrUnder( "/folder1/folder2", "/folder1/./Folder2/junk/../folder3" ); + Assert.NotSamePathOrUnder( "/folder1/folder2", "/folder1/./folder22/junk/../folder3" ); + Assert.SamePathOrUnder( "/", "/" ); + Assert.SamePathOrUnder( "/", "/bin/debug" ); + } + } +} diff --git a/src/nunit.engine.tests/Internal/ServerUtilityTests.cs b/src/nunit.engine.tests/Internal/ServerUtilityTests.cs new file mode 100644 index 00000000..a6da23c1 --- /dev/null +++ b/src/nunit.engine.tests/Internal/ServerUtilityTests.cs @@ -0,0 +1,72 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections; +using System.Runtime.Remoting.Channels; +using System.Runtime.Remoting.Channels.Tcp; +using NUnit.Framework; + +namespace NUnit.Engine.Internal.Tests +{ + /// + /// Summary description for RemotingUtilitiesTests. + /// + [TestFixture] + public class ServerUtilityTests + { + TcpChannel channel1; + TcpChannel channel2; + + [TearDown] + public void ReleaseChannels() + { + ServerUtilities.SafeReleaseChannel( channel1 ); + ServerUtilities.SafeReleaseChannel( channel2 ); + } + + [Test] + public void CanGetTcpChannelOnSpecifiedPort() + { + channel1 = ServerUtilities.GetTcpChannel( "test", 1234 ); + Assert.AreEqual( "test", channel1.ChannelName ); + channel2 = ServerUtilities.GetTcpChannel( "test", 4321 ); + Assert.AreEqual( "test", channel2.ChannelName ); + Assert.AreEqual( channel1, channel2 ); + Assert.AreSame( channel1, channel2 ); + ChannelDataStore cds = (ChannelDataStore)channel1.ChannelData; + Assert.AreEqual( "tcp://127.0.0.1:1234", cds.ChannelUris[0] ); + } + + [Test] + public void CanGetTcpChannelOnUnpecifiedPort() + { + channel1 = ServerUtilities.GetTcpChannel( "test", 0 ); + Assert.AreEqual( "test", channel1.ChannelName ); + channel2 = ServerUtilities.GetTcpChannel( "test", 0 ); + Assert.AreEqual( "test", channel2.ChannelName ); + Assert.AreEqual( channel1, channel2 ); + Assert.AreSame( channel1, channel2 ); + } + } +} diff --git a/src/nunit.engine.tests/Internal/SettingsGroupTests.cs b/src/nunit.engine.tests/Internal/SettingsGroupTests.cs new file mode 100644 index 00000000..17b8761d --- /dev/null +++ b/src/nunit.engine.tests/Internal/SettingsGroupTests.cs @@ -0,0 +1,107 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using NUnit.Framework; +using Microsoft.Win32; + +namespace NUnit.Engine.Internal.Tests +{ + [TestFixture] + public class SettingsGroupTests + { + private SettingsGroup settings; + + [SetUp] + public void BeforeEachTest() + { + settings = new SettingsGroup(); + } + + [TearDown] + public void AfterEachTest() + { + settings.Dispose(); + } + + [Test] + public void WhenSettingIsNotInitialized_NullIsReturned() + { + Assert.IsNull(settings.GetSetting("X")); + Assert.IsNull(settings.GetSetting("NAME")); + } + + [TestCase("X", 5)] + [TestCase("Y", 2.5)] + [TestCase("NAME", "Charlie")] + [TestCase("Flag", true)] + [TestCase("Priority", PriorityValue.A)] + public void WhenSettingIsInitialized_ValueIsReturned(string name, object expected) + { + settings.SaveSetting(name, expected); + object actual = settings.GetSetting(name); + Assert.AreEqual(expected, actual); + Assert.IsInstanceOf(expected.GetType(),actual); + } + + private enum PriorityValue + { + A, + B, + C + }; + + [Test] + public void WhenSettingIsRemoved_NullIsReturnedAndOtherSettingsAreNotAffected() + { + settings.SaveSetting("X", 5); + settings.SaveSetting("NAME", "Charlie"); + + settings.RemoveSetting("X"); + Assert.IsNull(settings.GetSetting("X"), "X not removed"); + Assert.AreEqual("Charlie", settings.GetSetting("NAME")); + + settings.RemoveSetting("NAME"); + Assert.IsNull(settings.GetSetting("NAME"), "NAME not removed"); + } + + [Test] + public void WhenSettingIsNotInitialized_DefaultValueIsReturned() + { + + Assert.AreEqual( 5, settings.GetSetting( "X", 5 ) ); + Assert.AreEqual( 6, settings.GetSetting( "X", 6 ) ); + Assert.AreEqual( "7", settings.GetSetting( "X", "7" ) ); + + Assert.AreEqual( "Charlie", settings.GetSetting( "NAME", "Charlie" ) ); + Assert.AreEqual( "Fred", settings.GetSetting( "NAME", "Fred" ) ); + } + + [Test] + public void WhenSettingIsNotValid_DefaultSettingIsReturned() + { + settings.SaveSetting( "X", "1y25" ); + Assert.AreEqual( 42, settings.GetSetting( "X", 42 ) ); + } + } +} diff --git a/src/nunit.engine.tests/Internal/XmlHelperTests.cs b/src/nunit.engine.tests/Internal/XmlHelperTests.cs new file mode 100644 index 00000000..256b2d5c --- /dev/null +++ b/src/nunit.engine.tests/Internal/XmlHelperTests.cs @@ -0,0 +1,100 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Xml; +using NUnit.Framework; + +namespace NUnit.Engine.Internal.Tests +{ + public class XmlHelperTests + { + [Test] + public void SingleElement() + { + XmlNode node = XmlHelper.CreateTopLevelElement("myelement"); + + Assert.That(node.Name, Is.EqualTo("myelement")); + Assert.That(node.Attributes.Count, Is.EqualTo(0)); + Assert.That(node.ChildNodes.Count, Is.EqualTo(0)); + } + + [Test] + public void SingleElementWithAttributes() + { + XmlNode node = XmlHelper.CreateTopLevelElement("person"); + XmlHelper.AddAttribute(node, "name", "Fred"); + XmlHelper.AddAttribute(node, "age", "42"); + XmlHelper.AddAttribute(node, "quotes", "'c' is a char but \"c\" is a string"); + + Assert.That(node.Name, Is.EqualTo("person")); + Assert.That(node.Attributes.Count, Is.EqualTo(3)); + Assert.That(node.ChildNodes.Count, Is.EqualTo(0)); + Assert.That(node.Attributes["name"].Value, Is.EqualTo("Fred")); + Assert.That(node.Attributes["age"].Value, Is.EqualTo("42")); + Assert.That(node.Attributes["quotes"].Value, Is.EqualTo("'c' is a char but \"c\" is a string")); + } + + [Test] + public void ElementContainsElementWithInnerText() + { + XmlNode top = XmlHelper.CreateTopLevelElement("top"); + XmlNode message = top.AddElement("message"); + message.InnerText = "This is my message"; + + Assert.That(top.SelectSingleNode("message").InnerText, Is.EqualTo("This is my message")); + } + + [Test] + public void ElementContainsElementWithCData() + { + XmlNode top = XmlHelper.CreateTopLevelElement("top"); + top.AddElementWithCDataSection("message", "x > 5 && x < 7"); + + Assert.That(top.SelectSingleNode("message").InnerText, Is.EqualTo("x > 5 && x < 7")); + } + + [Test] + public void SafeAttributeAccess() + { + XmlNode node = XmlHelper.CreateTopLevelElement("top"); + + Assert.That(XmlHelper.GetAttribute(node, "junk"), Is.Null); + } + + [Test] + public void SafeAttributeAcessWithIntDefaultValue() + { + XmlNode node = XmlHelper.CreateTopLevelElement("top"); + Assert.That(XmlHelper.GetAttribute(node, "junk", 42), Is.EqualTo(42)); + } + + [Test] + public void SafeAttributeAcessWithDoubleDefaultValue() + { + XmlNode node = XmlHelper.CreateTopLevelElement("top"); + Assert.That(node.GetAttribute("junk", 1.234), Is.EqualTo(1.234)); + } + } +} diff --git a/src/nunit.engine.tests/ResultHelperTests.cs b/src/nunit.engine.tests/ResultHelperTests.cs new file mode 100644 index 00000000..63023820 --- /dev/null +++ b/src/nunit.engine.tests/ResultHelperTests.cs @@ -0,0 +1,140 @@ +// *********************************************************************** +// Copyright (c) 2013 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Xml; + +namespace NUnit.Engine.Internal.Tests +{ + using Framework; + + public class ResultHelperTests + { + private const string resultText1 = ""; + private const string resultText2 = ""; + + private TestEngineResult result1; + private TestEngineResult result2; + + private TestEngineResult[] twoResults; + + private XmlNode[] twoNodes; + + [SetUp] + public void SetUp() + { + result1 = new TestEngineResult(resultText1); + result2 = new TestEngineResult(resultText2); + twoResults = new TestEngineResult[] { result1, result2 }; + twoNodes = new XmlNode[] { result1.Xml, result2.Xml }; + } + + [Test] + public void MergeTestResults() + { + TestEngineResult mergedResult = ResultHelper.Merge(twoResults); + + Assert.That(mergedResult.XmlNodes.Count, Is.EqualTo(2)); + Assert.That(mergedResult.XmlNodes[0].OuterXml, Is.EqualTo(resultText1)); + Assert.That(mergedResult.XmlNodes[1].OuterXml, Is.EqualTo(resultText2)); + } + + [Test] + public void AggregateTestResult() + { + TestEngineResult combined = result2.Aggregate("test-run", "NAME", "FULLNAME"); + Assert.That(combined.IsSingle); + + XmlNode combinedNode = combined.Xml; + + Assert.That(combinedNode.Name, Is.EqualTo("test-run")); + Assert.That(combinedNode.Attributes["result"].Value, Is.EqualTo("Failed")); + Assert.That(combinedNode.Attributes["total"].Value, Is.EqualTo("42")); + Assert.That(combinedNode.Attributes["passed"].Value, Is.EqualTo("31")); + Assert.That(combinedNode.Attributes["failed"].Value, Is.EqualTo("4")); + Assert.That(combinedNode.Attributes["inconclusive"].Value, Is.EqualTo("5")); + Assert.That(combinedNode.Attributes["skipped"].Value, Is.EqualTo("2")); + Assert.That(combinedNode.Attributes["asserts"].Value, Is.EqualTo("53")); + } + + [Test] + public void MergeAndAggregateTestResults() + { + TestEngineResult combined = ResultHelper.Merge(twoResults).Aggregate("test-suite", "Project", "NAME", "FULLNAME"); + Assert.That(combined.IsSingle); + + XmlNode combinedNode = combined.Xml; + + Assert.That(combinedNode.Name, Is.EqualTo("test-suite")); + Assert.That(combinedNode.Attributes["type"].Value, Is.EqualTo("Project")); + Assert.That(combinedNode.Attributes["name"].Value, Is.EqualTo("NAME")); + Assert.That(combinedNode.Attributes["fullname"].Value, Is.EqualTo("FULLNAME")); + Assert.That(combinedNode.Attributes["result"].Value, Is.EqualTo("Failed")); + Assert.That(combinedNode.Attributes["total"].Value, Is.EqualTo("65")); + Assert.That(combinedNode.Attributes["passed"].Value, Is.EqualTo("54")); + Assert.That(combinedNode.Attributes["failed"].Value, Is.EqualTo("4")); + Assert.That(combinedNode.Attributes["inconclusive"].Value, Is.EqualTo("5")); + Assert.That(combinedNode.Attributes["skipped"].Value, Is.EqualTo("2")); + Assert.That(combinedNode.Attributes["asserts"].Value, Is.EqualTo("93")); + } + + [Test] + public void AggregateXmlNodes() + { + DateTime startTime = new DateTime(2011, 07, 04, 12, 34, 56); + + XmlNode combined = ResultHelper.Aggregate("test-run", "NAME", "FULLNAME", twoNodes); + + Assert.That(combined.Name, Is.EqualTo("test-run")); + Assert.That(combined.Attributes["name"].Value, Is.EqualTo("NAME")); + Assert.That(combined.Attributes["fullname"].Value, Is.EqualTo("FULLNAME")); + Assert.That(combined.Attributes["result"].Value, Is.EqualTo("Failed")); + Assert.That(combined.Attributes["total"].Value, Is.EqualTo("65")); + Assert.That(combined.Attributes["passed"].Value, Is.EqualTo("54")); + Assert.That(combined.Attributes["failed"].Value, Is.EqualTo("4")); + Assert.That(combined.Attributes["inconclusive"].Value, Is.EqualTo("5")); + Assert.That(combined.Attributes["skipped"].Value, Is.EqualTo("2")); + Assert.That(combined.Attributes["asserts"].Value, Is.EqualTo("93")); + } + + [Test] + public void InsertEnvironmentElement() + { + result1.Xml.InsertEnvironmentElement(); + + var env = result1.Xml.SelectSingleNode("environment"); + Assert.NotNull(env); + + Assert.NotNull(env.GetAttribute("nunit-version")); + Assert.NotNull(env.GetAttribute("clr-version")); + Assert.NotNull(env.GetAttribute("os-version")); + Assert.NotNull(env.GetAttribute("platform")); + Assert.NotNull(env.GetAttribute("cwd")); + Assert.NotNull(env.GetAttribute("machine-name")); + Assert.NotNull(env.GetAttribute("user")); + Assert.NotNull(env.GetAttribute("user-domain")); + Assert.NotNull(env.GetAttribute("culture")); + Assert.NotNull(env.GetAttribute("uiculture")); + } + } +} diff --git a/src/nunit.engine.tests/RuntimeFrameworkTests.cs b/src/nunit.engine.tests/RuntimeFrameworkTests.cs new file mode 100644 index 00000000..1238ed75 --- /dev/null +++ b/src/nunit.engine.tests/RuntimeFrameworkTests.cs @@ -0,0 +1,246 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using NUnit.Framework; + +namespace NUnit.Engine.Tests +{ + [TestFixture] + public class RuntimeFrameworkTests + { + static RuntimeType currentRuntime = + Type.GetType("Mono.Runtime", false) != null + ? RuntimeType.Mono + : Environment.OSVersion.Platform == PlatformID.WinCE + ? RuntimeType.NetCF + : RuntimeType.Net; + + [Test] + public void CanGetCurrentFramework() + { + RuntimeFramework framework = RuntimeFramework.CurrentFramework; + + Assert.That(framework.Runtime, Is.EqualTo(currentRuntime)); + Assert.That(framework.ClrVersion, Is.EqualTo(Environment.Version)); + } + + [Test] + public void CurrentFrameworkHasBuildSpecified() + { + Assert.That(RuntimeFramework.CurrentFramework.ClrVersion.Build, Is.GreaterThan(0)); + } + + [Test] + public void CurrentFrameworkMustBeAvailable() + { + Assert.That(RuntimeFramework.CurrentFramework.IsAvailable); + } + + [Test] + public void CanListAvailableFrameworks() + { + RuntimeFramework[] available = RuntimeFramework.AvailableFrameworks; + Assert.That(available, Has.Length.GreaterThan(0) ); + bool foundCurrent = false; + foreach (RuntimeFramework framework in available) + { + Console.WriteLine("Available: {0}", framework.DisplayName); + foundCurrent |= RuntimeFramework.CurrentFramework.Supports(framework); + } + Assert.That(foundCurrent, "CurrentFramework not listed"); + } + + [TestCaseSource("frameworkData")] + public void CanCreateUsingFrameworkVersion(FrameworkData data) + { + RuntimeFramework framework = new RuntimeFramework(data.runtime, data.frameworkVersion); + Assert.AreEqual(data.runtime, framework.Runtime); + Assert.AreEqual(data.frameworkVersion, framework.FrameworkVersion); + Assert.AreEqual(data.clrVersion, framework.ClrVersion); + } + + [TestCaseSource("frameworkData")] + public void CanCreateUsingClrVersion(FrameworkData data) + { + Assume.That(data.frameworkVersion.Major != 3); + + RuntimeFramework framework = new RuntimeFramework(data.runtime, data.clrVersion); + Assert.AreEqual(data.runtime, framework.Runtime); + Assert.AreEqual(data.frameworkVersion, framework.FrameworkVersion); + Assert.AreEqual(data.clrVersion, framework.ClrVersion); + } + + [TestCaseSource("frameworkData")] + public void CanParseRuntimeFramework(FrameworkData data) + { + RuntimeFramework framework = RuntimeFramework.Parse(data.representation); + Assert.AreEqual(data.runtime, framework.Runtime); + Assert.AreEqual(data.clrVersion, framework.ClrVersion); + } + + [TestCaseSource("frameworkData")] + public void CanDisplayFrameworkAsString(FrameworkData data) + { + RuntimeFramework framework = new RuntimeFramework(data.runtime, data.frameworkVersion); + Assert.AreEqual(data.representation, framework.ToString()); + Assert.AreEqual(data.displayName, framework.DisplayName); + } + + [TestCaseSource("matchData")] + public bool CanMatchRuntimes(RuntimeFramework f1, RuntimeFramework f2) + { + return f1.Supports(f2); + } + + internal static TestCaseData[] matchData = new TestCaseData[] { + new TestCaseData( + new RuntimeFramework(RuntimeType.Net, new Version(3,5)), + new RuntimeFramework(RuntimeType.Net, new Version(2,0))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(RuntimeType.Net, new Version(2,0)), + new RuntimeFramework(RuntimeType.Net, new Version(3,5))) + .Returns(false), + new TestCaseData( + new RuntimeFramework(RuntimeType.Net, new Version(3,5)), + new RuntimeFramework(RuntimeType.Net, new Version(3,5))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(RuntimeType.Net, new Version(2,0)), + new RuntimeFramework(RuntimeType.Net, new Version(2,0))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(RuntimeType.Net, new Version(2,0)), + new RuntimeFramework(RuntimeType.Net, new Version(2,0,50727))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(RuntimeType.Net, new Version(2,0,50727)), + new RuntimeFramework(RuntimeType.Net, new Version(2,0))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(RuntimeType.Net, new Version(2,0,50727)), + new RuntimeFramework(RuntimeType.Net, new Version(2,0))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(RuntimeType.Net, new Version(2,0)), + new RuntimeFramework(RuntimeType.Mono, new Version(2,0))) + .Returns(false), + new TestCaseData( + new RuntimeFramework(RuntimeType.Net, new Version(2,0)), + new RuntimeFramework(RuntimeType.Net, new Version(1,1))) + .Returns(false), + new TestCaseData( + new RuntimeFramework(RuntimeType.Net, new Version(2,0,50727)), + new RuntimeFramework(RuntimeType.Net, new Version(2,0,40607))) + .Returns(false), + new TestCaseData( + new RuntimeFramework(RuntimeType.Mono, new Version(1,1)), // non-existent version but it works + new RuntimeFramework(RuntimeType.Mono, new Version(1,0))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(RuntimeType.Mono, new Version(2,0)), + new RuntimeFramework(RuntimeType.Any, new Version(2,0))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(RuntimeType.Any, new Version(2,0)), + new RuntimeFramework(RuntimeType.Mono, new Version(2,0))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(RuntimeType.Any, new Version(2,0)), + new RuntimeFramework(RuntimeType.Any, new Version(2,0))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(RuntimeType.Any, new Version(2,0)), + new RuntimeFramework(RuntimeType.Any, new Version(4,0))) + .Returns(false), + new TestCaseData( + new RuntimeFramework(RuntimeType.Net, RuntimeFramework.DefaultVersion), + new RuntimeFramework(RuntimeType.Net, new Version(2,0))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(RuntimeType.Net, new Version(2,0)), + new RuntimeFramework(RuntimeType.Net, RuntimeFramework.DefaultVersion)) + .Returns(true), + new TestCaseData( + new RuntimeFramework(RuntimeType.Any, RuntimeFramework.DefaultVersion), + new RuntimeFramework(RuntimeType.Net, new Version(2,0))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(RuntimeType.Net, new Version(2,0)), + new RuntimeFramework(RuntimeType.Any, RuntimeFramework.DefaultVersion)) + .Returns(true) + }; + + public struct FrameworkData + { + public RuntimeType runtime; + public Version frameworkVersion; + public Version clrVersion; + public string representation; + public string displayName; + + public FrameworkData(RuntimeType runtime, Version frameworkVersion, Version clrVersion, + string representation, string displayName) + { + this.runtime = runtime; + this.frameworkVersion = frameworkVersion; + this.clrVersion = clrVersion; + this.representation = representation; + this.displayName = displayName; + } + + public override string ToString() + { + return string.Format("<{0}-{1}>", this.runtime, this.frameworkVersion); + } + } + + internal FrameworkData[] frameworkData = new FrameworkData[] { + new FrameworkData(RuntimeType.Net, new Version(1,0), new Version(1,0,3705), "net-1.0", "Net 1.0"), + //new FrameworkData(RuntimeType.Net, new Version(1,0,3705), new Version(1,0,3705), "net-1.0.3705", "Net 1.0.3705"), + //new FrameworkData(RuntimeType.Net, new Version(1,0), new Version(1,0,3705), "net-1.0.3705", "Net 1.0.3705"), + new FrameworkData(RuntimeType.Net, new Version(1,1), new Version(1,1,4322), "net-1.1", "Net 1.1"), + //new FrameworkData(RuntimeType.Net, new Version(1,1,4322), new Version(1,1,4322), "net-1.1.4322", "Net 1.1.4322"), + new FrameworkData(RuntimeType.Net, new Version(2,0), new Version(2,0,50727), "net-2.0", "Net 2.0"), + //new FrameworkData(RuntimeType.Net, new Version(2,0,40607), new Version(2,0,40607), "net-2.0.40607", "Net 2.0.40607"), + //new FrameworkData(RuntimeType.Net, new Version(2,0,50727), new Version(2,0,50727), "net-2.0.50727", "Net 2.0.50727"), + new FrameworkData(RuntimeType.Net, new Version(3,0), new Version(2,0,50727), "net-3.0", "Net 3.0"), + new FrameworkData(RuntimeType.Net, new Version(3,5), new Version(2,0,50727), "net-3.5", "Net 3.5"), + new FrameworkData(RuntimeType.Net, new Version(4,0), new Version(4,0,30319), "net-4.0", "Net 4.0"), + new FrameworkData(RuntimeType.Net, RuntimeFramework.DefaultVersion, RuntimeFramework.DefaultVersion, "net", "Net"), + new FrameworkData(RuntimeType.Mono, new Version(1,0), new Version(1,1,4322), "mono-1.0", "Mono 1.0"), + new FrameworkData(RuntimeType.Mono, new Version(2,0), new Version(2,0,50727), "mono-2.0", "Mono 2.0"), + //new FrameworkData(RuntimeType.Mono, new Version(2,0,50727), new Version(2,0,50727), "mono-2.0.50727", "Mono 2.0.50727"), + new FrameworkData(RuntimeType.Mono, new Version(3,5), new Version(2,0,50727), "mono-3.5", "Mono 3.5"), + new FrameworkData(RuntimeType.Mono, new Version(4,0), new Version(4,0,30319), "mono-4.0", "Mono 4.0"), + new FrameworkData(RuntimeType.Mono, RuntimeFramework.DefaultVersion, RuntimeFramework.DefaultVersion, "mono", "Mono"), + new FrameworkData(RuntimeType.Any, new Version(1,1), new Version(1,1,4322), "v1.1", "v1.1"), + new FrameworkData(RuntimeType.Any, new Version(2,0), new Version(2,0,50727), "v2.0", "v2.0"), + //new FrameworkData(RuntimeType.Any, new Version(2,0,50727), new Version(2,0,50727), "v2.0.50727", "v2.0.50727"), + new FrameworkData(RuntimeType.Any, new Version(3,5), new Version(2,0,50727), "v3.5", "v3.5"), + new FrameworkData(RuntimeType.Any, new Version(4,0), new Version(4,0,30319), "v4.0", "v4.0"), + new FrameworkData(RuntimeType.Any, RuntimeFramework.DefaultVersion, RuntimeFramework.DefaultVersion, "any", "Any") + }; + } +} diff --git a/src/nunit.engine.tests/Services/DomainManagerTests.cs b/src/nunit.engine.tests/Services/DomainManagerTests.cs new file mode 100644 index 00000000..8d3055ca --- /dev/null +++ b/src/nunit.engine.tests/Services/DomainManagerTests.cs @@ -0,0 +1,136 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using NUnit.Framework; +using NUnit.Engine.Tests.Assemblies; + +namespace NUnit.Engine.Services.Tests +{ + public class DomainManagerTests + { + static string path1 = TestPath("/test/bin/debug/test1.dll"); + static string path2 = TestPath("/test/bin/debug/test2.dll"); + static string path3 = TestPath("/test/utils/test3.dll"); + + [Test] + public void GetPrivateBinPath() + { + string[] assemblies = new string[] { path1, path2, path3 }; + + Assert.AreEqual( + TestPath("bin/debug") + Path.PathSeparator + TestPath("utils"), + DomainManager.GetPrivateBinPath(TestPath("/test"), assemblies)); + } + + [Test] + public void GetCommonAppBase_OneElement() + { + string[] assemblies = new string[] { path1 }; + + Assert.AreEqual( + TestPath("/test/bin/debug"), + DomainManager.GetCommonAppBase(assemblies)); + } + + [Test] + public void GetCommonAppBase_TwoElements_SameDirectory() + { + string[] assemblies = new string[] { path1, path2 }; + + Assert.AreEqual( + TestPath("/test/bin/debug"), + DomainManager.GetCommonAppBase(assemblies)); + } + + [Test] + public void GetCommonAppBase_TwoElements_DifferentDirectories() + { + string[] assemblies = new string[] { path1, path3 }; + + Assert.AreEqual( + TestPath("/test"), + DomainManager.GetCommonAppBase(assemblies)); + } + + [Test] + public void GetCommonAppBase_ThreeElements_DiferentDirectories() + { + string[] assemblies = new string[] { path1, path2, path3 }; + + Assert.AreEqual( + TestPath("/test"), + DomainManager.GetCommonAppBase(assemblies)); + } + + [Test] + public void UnloadUnloadedDomain() + { + AppDomain domain = AppDomain.CreateDomain("DomainManagerTests-domain"); + AppDomain.Unload(domain); + + DomainManager manager = new DomainManager(); + manager.Unload(domain); + } + + [Test, Platform("Linux,Net", Reason = "get_SetupInformation() fails on Windows+Mono")] + public void AppDomainSetUpCorrect() + { + ServiceContext context = new ServiceContext(); + context.Add(new SettingsService()); + var domainManager = new DomainManager(); + context.Add(domainManager); + context.ServiceManager.InitializeServices(); + + string mockDll = MockAssembly.AssemblyPath; + AppDomainSetup setup = domainManager.CreateAppDomainSetup(new TestPackage(mockDll)); + + Assert.That(setup.ApplicationName, Is.StringStarting("Tests_")); + Assert.That(setup.ApplicationBase, Is.SamePath(Path.GetDirectoryName(mockDll)), "ApplicationBase"); + Assert.That( + Path.GetFileName( setup.ConfigurationFile ), + Is.EqualTo("mock-assembly.dll.config").IgnoreCase, + "ConfigurationFile"); + Assert.AreEqual( null, setup.PrivateBinPath, "PrivateBinPath" ); + Assert.That(setup.ShadowCopyDirectories, Is.SamePath(Path.GetDirectoryName(mockDll)), "ShadowCopyDirectories" ); + } + + /// + /// Take a valid Linux filePath and make a valid windows filePath out of it + /// if we are on Windows. Change slashes to backslashes and, if the + /// filePath starts with a slash, add C: in front of it. + /// + private static string TestPath(string path) + { + if (Path.DirectorySeparatorChar != '/') + { + path = path.Replace('/', Path.DirectorySeparatorChar); + if (path[0] == Path.DirectorySeparatorChar) + path = "C:" + path; + } + + return path; + } + } +} diff --git a/src/nunit.engine.tests/Services/RecentFilesTests.cs b/src/nunit.engine.tests/Services/RecentFilesTests.cs new file mode 100644 index 00000000..e85e7376 --- /dev/null +++ b/src/nunit.engine.tests/Services/RecentFilesTests.cs @@ -0,0 +1,264 @@ +// *********************************************************************** +// Copyright (c) 2007 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +namespace NUnit.Engine.Services.Tests +{ + using System; + using System.Collections; + using Microsoft.Win32; + using NUnit.Engine.Internal; + using NUnit.Framework; + + /// + /// This fixture is used to test both RecentProjects and + /// its base class RecentFiles. If we add any other derived + /// classes, the tests should be refactored. + /// + [TestFixture] + public class RecentFilesTests + { + private const int MAX = 24; + private const int MIN = 0; + private const int DEFAULT = 5; + + RecentFilesService recentFiles; + + [SetUp] + public void SetUp() + { + var services = new ServiceContext(); + services.Add(new Services.SettingsService()); + recentFiles = new RecentFilesService(); + services.Add(recentFiles); + } + + #region Helper Methods + // Set RecentFiles to a list of known values up + // to a maximum. Most recent will be "1", next + // "2", and so on... + private void SetMockValues( int count ) + { + for( int num = count; num > 0; --num ) + recentFiles.SetMostRecent( num.ToString() ); + } + + // Check that the list is set right: 1, 2, ... + private void CheckMockValues( int count ) + { + var files = recentFiles.Entries; + Assert.AreEqual( count, files.Count, "Count" ); + + for( int index = 0; index < count; index++ ) + Assert.AreEqual( (index + 1).ToString(), files[index], "Item" ); + } + + // Check that we can add count items correctly + private void CheckAddItems( int count ) + { + SetMockValues( count ); + Assert.AreEqual( "1", recentFiles.Entries[0], "RecentFile" ); + + CheckMockValues( Math.Min( count, recentFiles.MaxFiles ) ); + } + + // Check that the list contains a set of entries + // in the order given and nothing else. + private void CheckListContains( params int[] item ) + { + var files = recentFiles.Entries; + Assert.AreEqual( item.Length, files.Count, "Count" ); + + for( int index = 0; index < files.Count; index++ ) + Assert.AreEqual( item[index].ToString(), files[index], "Item" ); + } + #endregion + + [Test] + public void CountDefault() + { + Assert.AreEqual( DEFAULT, recentFiles.MaxFiles ); + } + + [Test] + public void CountOverMax() + { + recentFiles.MaxFiles = MAX + 1; + Assert.AreEqual( MAX, recentFiles.MaxFiles ); + } + + [Test] + public void CountUnderMin() + { + recentFiles.MaxFiles = MIN - 1; + Assert.AreEqual( MIN, recentFiles.MaxFiles ); + } + + [Test] + public void CountAtMax() + { + recentFiles.MaxFiles = MAX; + Assert.AreEqual( MAX, recentFiles.MaxFiles ); + } + + [Test] + public void CountAtMin() + { + recentFiles.MaxFiles = MIN; + Assert.AreEqual( MIN, recentFiles.MaxFiles ); + } + + [Test] + public void EmptyList() + { + Assert.IsNotNull( recentFiles.Entries, "Entries should never be null" ); + Assert.AreEqual( 0, recentFiles.Entries.Count ); + } + + [Test] + public void AddSingleItem() + { + CheckAddItems( 1 ); + } + + [Test] + public void AddMaxItems() + { + CheckAddItems( 5 ); + } + + [Test] + public void AddTooManyItems() + { + CheckAddItems( 10 ); + } + + [Test] + public void IncreaseSize() + { + recentFiles.MaxFiles = 10; + CheckAddItems( 10 ); + } + + [Test] + public void ReduceSize() + { + recentFiles.MaxFiles = 3; + CheckAddItems( 10 ); + } + + [Test] + public void IncreaseSizeAfterAdd() + { + SetMockValues(5); + recentFiles.MaxFiles = 7; + recentFiles.SetMostRecent( "30" ); + recentFiles.SetMostRecent( "20" ); + recentFiles.SetMostRecent( "10" ); + CheckListContains( 10, 20, 30, 1, 2, 3, 4 ); + } + + [Test] + public void ReduceSizeAfterAdd() + { + SetMockValues( 5 ); + recentFiles.MaxFiles = 3; + CheckMockValues( 3 ); + } + + [Test] + public void ReorderLastProject() + { + SetMockValues( 5 ); + recentFiles.SetMostRecent( "5" ); + CheckListContains( 5, 1, 2, 3, 4 ); + } + + [Test] + public void ReorderSingleProject() + { + SetMockValues( 5 ); + recentFiles.SetMostRecent( "3" ); + CheckListContains( 3, 1, 2, 4, 5 ); + } + + [Test] + public void ReorderMultipleProjects() + { + SetMockValues( 5 ); + recentFiles.SetMostRecent( "3" ); + recentFiles.SetMostRecent( "5" ); + recentFiles.SetMostRecent( "2" ); + CheckListContains( 2, 5, 3, 1, 4 ); + } + + [Test] + public void ReorderSameProject() + { + SetMockValues( 5 ); + recentFiles.SetMostRecent( "1" ); + CheckListContains( 1, 2, 3, 4, 5 ); + } + + [Test] + public void ReorderWithListNotFull() + { + SetMockValues( 3 ); + recentFiles.SetMostRecent( "3" ); + CheckListContains( 3, 1, 2 ); + } + + [Test] + public void RemoveFirstProject() + { + SetMockValues( 3 ); + recentFiles.Remove("1"); + CheckListContains( 2, 3 ); + } + + [Test] + public void RemoveOneProject() + { + SetMockValues( 4 ); + recentFiles.Remove("2"); + CheckListContains( 1, 3, 4 ); + } + + [Test] + public void RemoveMultipleProjects() + { + SetMockValues( 5 ); + recentFiles.Remove( "3" ); + recentFiles.Remove( "1" ); + recentFiles.Remove( "4" ); + CheckListContains( 2, 5 ); + } + + [Test] + public void RemoveLastProject() + { + SetMockValues( 5 ); + recentFiles.Remove("5"); + CheckListContains( 1, 2, 3, 4 ); + } + } +} diff --git a/src/nunit.engine.tests/TestEngineResultTests.cs b/src/nunit.engine.tests/TestEngineResultTests.cs new file mode 100644 index 00000000..eee1873e --- /dev/null +++ b/src/nunit.engine.tests/TestEngineResultTests.cs @@ -0,0 +1,68 @@ +// *********************************************************************** +// Copyright (c) 2013 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System.Xml; + +namespace NUnit.Engine.Tests +{ + using Framework; + using Internal; + + [TestFixture] + public class TestEngineResultTests + { + private static readonly string xmlText = ""; + + [Test] + public void CanCreateFromXmlString() + { + TestEngineResult result = new TestEngineResult(xmlText); + Assert.True(result.IsSingle); + Assert.That(result.Xml.Name, Is.EqualTo("test-assembly")); + Assert.That(result.Xml.Attributes["result"].Value, Is.EqualTo("Passed")); + Assert.That(result.Xml.Attributes["total"].Value, Is.EqualTo("23")); + Assert.That(result.Xml.Attributes["passed"].Value, Is.EqualTo("23")); + Assert.That(result.Xml.Attributes["failed"].Value, Is.EqualTo("0")); + Assert.That(result.Xml.Attributes["inconclusive"].Value, Is.EqualTo("0")); + Assert.That(result.Xml.Attributes["skipped"].Value, Is.EqualTo("0")); + Assert.That(result.Xml.Attributes["asserts"].Value, Is.EqualTo("40")); + } + + [Test] + public void CanCreateFromXmlNode() + { + XmlNode node = XmlHelper.CreateTopLevelElement("test-assembly"); + XmlHelper.AddAttribute(node, "result", "Passed"); + XmlHelper.AddAttribute(node, "total", "23"); + XmlHelper.AddAttribute(node, "passed", "23"); + XmlHelper.AddAttribute(node, "failed", "0"); + XmlHelper.AddAttribute(node, "inconclusive", "0"); + XmlHelper.AddAttribute(node, "skipped", "0"); + XmlHelper.AddAttribute(node, "asserts", "40"); + + TestEngineResult result = new TestEngineResult(node); + Assert.True(result.IsSingle); + Assert.That(result.Xml.OuterXml, Is.EqualTo(xmlText)); + } + } +} diff --git a/src/nunit.engine.tests/nunit.engine.tests.build b/src/nunit.engine.tests/nunit.engine.tests.build new file mode 100644 index 00000000..b81733d3 --- /dev/null +++ b/src/nunit.engine.tests/nunit.engine.tests.build @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/nunit.engine.tests/nunit.engine.tests.csproj b/src/nunit.engine.tests/nunit.engine.tests.csproj new file mode 100644 index 00000000..1d722f8a --- /dev/null +++ b/src/nunit.engine.tests/nunit.engine.tests.csproj @@ -0,0 +1,110 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {D694CB69-6CFB-4762-86C2-EB27B808B282} + Library + Properties + NUnit.Engine.Tests + nunit.engine.tests + v2.0 + 512 + + + 3.5 + + + + true + full + false + ..\..\bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + ..\..\bin\Release\ + TRACE + prompt + 4 + + + OnBuildSuccess + + + + False + ..\..\lib\nunit.framework.dll + + + + + + + + + + + CommonAssemblyInfo.cs + + + CommonVersionInfo.cs + + + + + + + + + + + + + + + + + + + + + + + {2e368281-3ba8-4050-b05e-0e0e43f8f446} + mock-assembly + + + {775FAD50-3623-4922-97C4-DFB29A8BE4C7} + nunit.engine.api + + + {372A3447-D657-40FF-A089-77C19FEC30C8} + nunit.engine + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/nunit.engine/Agents/RemoteTestAgent.cs b/src/nunit.engine/Agents/RemoteTestAgent.cs new file mode 100644 index 00000000..630ad430 --- /dev/null +++ b/src/nunit.engine/Agents/RemoteTestAgent.cs @@ -0,0 +1,207 @@ +// *********************************************************************** +// Copyright (c) 2008-2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Threading; +using NUnit.Engine.Internal; + +namespace NUnit.Engine.Agents +{ + /// + /// RemoteTestAgent represents a remote agent executing in another process + /// and communicating with NUnit by TCP. Although it is similar to a + /// TestServer, it does not publish a Uri at which clients may connect + /// to it. Rather, it reports back to the sponsoring TestAgency upon + /// startup so that the agency may in turn provide it to clients for use. + /// + public class RemoteTestAgent : TestAgent, ITestEngineRunner + { + static Logger log = InternalTrace.GetLogger(typeof(RemoteTestAgent)); + + #region Fields + + private ITestEngineRunner _runner; + private TestPackage _package; + + private ManualResetEvent stopSignal = new ManualResetEvent(false); + + #endregion + + #region Constructor + + /// + /// Construct a RemoteTestAgent + /// + public RemoteTestAgent( Guid agentId, ITestAgency agency, ServiceContext services ) + : base(agentId, agency, services) + { + } + + #endregion + + #region Properties + public int ProcessId + { + get { return System.Diagnostics.Process.GetCurrentProcess().Id; } + } + #endregion + + #region Public Methods + + public override ITestEngineRunner CreateRunner(TestPackage package) + { + _package = package; + return this; + } + + public override bool Start() + { + log.Info("Agent starting"); + + try + { + this.Agency.Register( this ); + log.Debug( "Registered with TestAgency" ); + } + catch( Exception ex ) + { + log.Error( "RemoteTestAgent: Failed to register with TestAgency", ex ); + return false; + } + + return true; + } + + public override void Stop() + { + log.Info("Stopping"); + // This causes an error in the client because the agent + // database is not thread-safe. + //if (agency != null) + // agency.ReportStatus(this.ProcessId, AgentStatus.Stopping); + + + stopSignal.Set(); + } + + public void WaitForStop() + { + stopSignal.WaitOne(); + } + + #endregion + + #region ITestEngineRunner Members + + /// + /// Explore a loaded TestPackage and return information about + /// the tests found. + /// + /// The TestPackage to be explored + /// A TestEngineResult. + public TestEngineResult Explore(TestFilter filter) + { + if (_runner == null) + throw new InvalidOperationException("RemoteTestAgent: Explore called before Load"); + + return _runner.Explore(filter); + } + + public TestEngineResult Load() + { + //System.Diagnostics.Debug.Assert(false, "Attach debugger if desired"); + + _runner = Services.TestRunnerFactory.MakeTestRunner(_package); + return _runner.Load(); + } + + public void Unload() + { + if (_runner != null) + _runner.Unload(); + } + + public TestEngineResult Reload() + { + if (_runner == null) + throw new InvalidOperationException("RemoteTestAgent: Reload called before Load"); + + return _runner.Reload(); + } + + /// + /// Count the test cases that would be run under + /// the specified filter. + /// + /// A TestFilter + /// The count of test cases + public int CountTestCases(TestFilter filter) + { + if (_runner == null) + throw new InvalidOperationException("RemoteTestAgent: CountTestCases called before Load"); + + return _runner.CountTestCases(filter); + } + + /// + /// Run the tests in the loaded TestPackage and return a test result. The tests + /// are run synchronously and the listener interface is notified as it progresses. + /// + /// An ITestEventHandler to receive events + /// A TestFilter used to select tests + /// A TestEngineResult giving the result of the test execution + public TestEngineResult Run(ITestEventListener listener, TestFilter filter) + { + if (_runner == null) + throw new InvalidOperationException("RemoteTestAgent: Run called before Load"); + + return _runner.Run(listener, filter); + } + + /// + /// Start a run of the tests in the loaded TestPackage. The tests are run + /// asynchronously and the listener interface is notified as it progresses. + /// + /// An ITestEventHandler to receive events + /// A TestFilter used to select tests + public void StartRun(ITestEventListener listener, TestFilter filter) + { + if (_runner == null) + throw new InvalidOperationException("RemoteTestAgent: StartRun called before Load"); + + _runner.StartRun(listener, filter); + } + + /// + /// Cancel the ongoing test run. If no test is running, the call is ignored. + /// + /// If true, cancel any ongoing test threads, otherwise wait for them to complete. + public void StopRun(bool force) + { + if (_runner != null) + _runner.StopRun(force); + } + + #endregion + } +} diff --git a/src/nunit.engine/Agents/TestAgent.cs b/src/nunit.engine/Agents/TestAgent.cs new file mode 100644 index 00000000..65e959cc --- /dev/null +++ b/src/nunit.engine/Agents/TestAgent.cs @@ -0,0 +1,130 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; + +namespace NUnit.Engine.Agents +{ + /// + /// Abstract base for all types of TestAgents. + /// A TestAgent provides services of locating, + /// loading and running tests in a particular + /// context such as an AppDomain or Process. + /// + public abstract class TestAgent : MarshalByRefObject, ITestAgent, IDisposable + { + #region Private Fields + + private ITestAgency agency; + private Guid agentId; + private ServiceContext services; + + #endregion + + #region Constructors + + /// + /// Consructs a TestAgent + /// + /// + /// + public TestAgent( Guid agentId, ITestAgency agency, ServiceContext services ) + { + this.agency = agency; + this.agentId = agentId; + this.services = services; + } + + #endregion + + #region Protected Properties + + /// + /// The ServiceContext under which the agent is running + /// + protected ServiceContext Services + { + get { return services; } + } + + #endregion + + #region ITestAgent members + + /// + /// Gets a reference to the TestAgency with which this agent + /// is asssociated. Returns null if the agent is not + /// connected to an agency. + /// + public ITestAgency Agency + { + get { return agency; } + } + + /// + /// Gets a Guid that uniquely identifies this agent. + /// + public Guid Id + { + get { return agentId; } + } + + /// + /// Starts the agent, performing any required initialization + /// + /// True if successful, otherwise false + public abstract bool Start(); + + /// + /// Stops the agent, releasing any resources + /// + public abstract void Stop(); + + /// + /// Creates a test runner + /// + public abstract ITestEngineRunner CreateRunner(TestPackage package); + + #endregion + + #region IDisposable Members + /// + /// Dispose is overridden to stop the agent + /// + public void Dispose() + { + this.Stop(); + } + #endregion + + #region InitializeLifeTimeService + /// + /// Overridden to cause object to live indefinitely + /// + public override object InitializeLifetimeService() + { + return null; + } + #endregion + } +} diff --git a/src/nunit.engine/AssemblyInfo.cs b/src/nunit.engine/AssemblyInfo.cs new file mode 100644 index 00000000..e6376370 --- /dev/null +++ b/src/nunit.engine/AssemblyInfo.cs @@ -0,0 +1,17 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NUnit Engine")] +[assembly: AssemblyDescription("Provides a common interface for loading, exploring and running NUnit tests")] +[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("5796938b-03c9-4b75-8b43-89a8adc4acd0")] diff --git a/src/nunit.engine/CallbackHandler.cs b/src/nunit.engine/CallbackHandler.cs new file mode 100644 index 00000000..ad99d412 --- /dev/null +++ b/src/nunit.engine/CallbackHandler.cs @@ -0,0 +1,61 @@ +// *********************************************************************** +// Copyright (c) 2010 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Web.UI; + +namespace NUnit.Engine +{ + public class CallbackHandler : MarshalByRefObject, ICallbackEventHandler + { + public string Result { get; private set; } + + public virtual void ReportProgress(string report) + { + } + + #region MarshalByRefObject Overrides + + public override object InitializeLifetimeService() + { + return null; + } + + #endregion + + #region ICallbackEventHandler Members + + public string GetCallbackResult() + { + throw new NotImplementedException(); + } + + public void RaiseCallbackEvent(string eventArgument) + { + Result = eventArgument; + ReportProgress(eventArgument); + } + + #endregion + } +} diff --git a/src/nunit.engine/IProject.cs b/src/nunit.engine/IProject.cs new file mode 100644 index 00000000..defd776b --- /dev/null +++ b/src/nunit.engine/IProject.cs @@ -0,0 +1,49 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +namespace NUnit.Engine +{ + public interface IProject + { + #region Properties + + /// + /// Gets the path to the file storing this project, if any. + /// If the project has not been saved, this is null. + /// + string ProjectPath { get; } + + /// + /// Gets the active configuration, as defined + /// by the particular project. + /// + IProjectConfig ActiveConfig { get; } + + /// + /// Gets a list of the configs for this project + /// + IProjectConfigList Configs { get; } + + #endregion + } +} diff --git a/src/nunit.engine/IProjectConfig.cs b/src/nunit.engine/IProjectConfig.cs new file mode 100644 index 00000000..da698c2d --- /dev/null +++ b/src/nunit.engine/IProjectConfig.cs @@ -0,0 +1,43 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; + +namespace NUnit.Engine +{ + public enum BinPathType + { + Auto, + Manual, + None + } + + public interface IProjectConfig + { + string Name { get; } + + System.Collections.Generic.IDictionary Settings { get; } + + string[] Assemblies { get; } + } +} diff --git a/src/nunit.engine/IProjectConfigList.cs b/src/nunit.engine/IProjectConfigList.cs new file mode 100644 index 00000000..4a4d5abb --- /dev/null +++ b/src/nunit.engine/IProjectConfigList.cs @@ -0,0 +1,43 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +namespace NUnit.Engine +{ + public interface IProjectConfigList + { + /// + /// Gets a count of the number of configs + /// + int Count { get; } + + ///// + ///// Gets the config at the specified index. + ///// + //IProjectConfig this[int index] { get; } + + /// + /// Gets the config with the specified name + /// + IProjectConfig this[string name] { get; } + } +} diff --git a/src/nunit.engine/IProjectLoader.cs b/src/nunit.engine/IProjectLoader.cs new file mode 100644 index 00000000..9775e1fb --- /dev/null +++ b/src/nunit.engine/IProjectLoader.cs @@ -0,0 +1,49 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; + +namespace NUnit.Engine +{ + /// + /// The IProjectLoader interface is implemented by any class + /// that knows how to load projects in a specific format. + /// + public interface IProjectLoader + { + /// + /// Returns true if the file indicated is one that this + /// converter knows how to load. + /// + /// + /// + bool IsProjectFile( string path ); + + /// + /// Loads an external project returning an IProject. + /// + /// + /// + IProject LoadProject( string path ); + } +} diff --git a/src/nunit.engine/IRuntimeFrameworkSelector.cs b/src/nunit.engine/IRuntimeFrameworkSelector.cs new file mode 100644 index 00000000..28f0e6c4 --- /dev/null +++ b/src/nunit.engine/IRuntimeFrameworkSelector.cs @@ -0,0 +1,40 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; + +namespace NUnit.Engine +{ + public interface IRuntimeFrameworkSelector + { + /// + /// Selects a target runtime framework for a TestPackage based on + /// the settings in the package and the assemblies themselves. + /// The package RuntimeFramework setting may be updated as a + /// result and the selected runtime is returned. + /// + /// A TestPackage + /// The selected RuntimeFramework + RuntimeFramework SelectRuntimeFramework(TestPackage package); + } +} diff --git a/src/nunit.engine/IService.cs b/src/nunit.engine/IService.cs new file mode 100644 index 00000000..d8bf9bcf --- /dev/null +++ b/src/nunit.engine/IService.cs @@ -0,0 +1,48 @@ +// *********************************************************************** +// Copyright (c) 2007 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; + +namespace NUnit.Engine +{ + /// + /// The IService interface is implemented by all Services. + /// + public interface IService + { + /// + /// The ServiceContext + /// + ServiceContext ServiceContext { get; set; } + + /// + /// Initialize the Service + /// + void InitializeService(); + + /// + /// Do any cleanup needed before terminating the service + /// + void UnloadService(); + } +} diff --git a/src/nunit.engine/ITestAgency.cs b/src/nunit.engine/ITestAgency.cs new file mode 100644 index 00000000..ad90ef4a --- /dev/null +++ b/src/nunit.engine/ITestAgency.cs @@ -0,0 +1,38 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +namespace NUnit.Engine +{ + /// + /// The ITestAgency interface is implemented by a TestAgency in + /// order to allow TestAgents to register with it. + /// + public interface ITestAgency + { + /// + /// Registers an agent with an agency + /// + /// + void Register(ITestAgent agent); + } +} diff --git a/src/nunit.engine/ITestAgent.cs b/src/nunit.engine/ITestAgent.cs new file mode 100644 index 00000000..433f8f60 --- /dev/null +++ b/src/nunit.engine/ITestAgent.cs @@ -0,0 +1,59 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; + +namespace NUnit.Engine +{ + /// + /// The ITestAgent interface is implemented by remote test agents. + /// + public interface ITestAgent + { + /// + /// Gets the agency with which this agent is associated. + /// + ITestAgency Agency { get; } + + /// + /// Gets a Guid that uniquely identifies this agent. + /// + Guid Id { get; } + + /// + /// Starts the agent, performing any required initialization + /// + /// True if successful, otherwise false + bool Start(); + + /// + /// Stops the agent, releasing any resources + /// + void Stop(); + + /// + /// Creates a test runner + /// + ITestEngineRunner CreateRunner(TestPackage package); + } +} diff --git a/src/nunit.engine/ITestEngineRunner.cs b/src/nunit.engine/ITestEngineRunner.cs new file mode 100644 index 00000000..143ed2f8 --- /dev/null +++ b/src/nunit.engine/ITestEngineRunner.cs @@ -0,0 +1,95 @@ +// *********************************************************************** +// Copyright (c) 2011-2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; + +namespace NUnit.Engine +{ + /// + /// Interface implemented by all internal test runners in + /// the engine, allowing them to pass back TestEngineResults + /// to any higher-level runner that calls them. + /// + public interface ITestEngineRunner : IDisposable + { + /// + /// Load a TestPackage for possible execution + /// + /// The TestPackage to be loaded + /// A TestEngineResult. + TestEngineResult Load(); + + /// + /// Unload any loaded TestPackage. If none is loaded, + /// the call is ignored. + /// + void Unload(); + + /// + /// Reload the loaded test package. + /// + /// A TestEngineResult. + /// If no package is loaded. + TestEngineResult Reload(); + + /// + /// Count the test cases that would be run under + /// the specified filter. + /// + /// A TestFilter + /// The count of test cases + int CountTestCases(TestFilter filter); + + /// + /// Run the tests in the loaded TestPackage and return a test result. The tests + /// are run synchronously and the listener interface is notified as it progresses. + /// + /// An ITestEventHandler to receive events + /// A TestFilter used to select tests + /// A TestEngineResult giving the result of the test execution + TestEngineResult Run(ITestEventListener listener, TestFilter filter); + + /// + /// Start a run of the tests in the loaded TestPackage. The tests are run + /// asynchronously and the listener interface is notified as it progresses. + /// + /// An ITestEventHandler to receive events + /// A TestFilter used to select tests + void StartRun(ITestEventListener listener, TestFilter filter); + + /// + /// Cancel the current test run. If no test is running, + /// the call is ignored. + /// + /// If true, force a stop by cancelling threads if necessary. + void StopRun(bool force); + + /// + /// Explore a loaded TestPackage and return information about + /// the tests found. + /// + /// The TestPackage to be explored + /// A TestEngineResult. + TestEngineResult Explore(TestFilter filter); + } +} diff --git a/src/nunit.engine/ITestRunnerFactory.cs b/src/nunit.engine/ITestRunnerFactory.cs new file mode 100644 index 00000000..4df4ec95 --- /dev/null +++ b/src/nunit.engine/ITestRunnerFactory.cs @@ -0,0 +1,50 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; + +namespace NUnit.Engine +{ + /// + /// A Test Runner factory can supply a suitable test runner for a given package + /// + public interface ITestRunnerFactory + { + /// + /// Return a suitable runner for the package provided as an argument + /// + /// The test package to be loaded by the runner + /// A TestRunner + ITestEngineRunner MakeTestRunner(TestPackage package); + + /// + /// Return true if the provided runner is suitable for reuse in loading + /// the test package provided. Otherwise, return false. Runners that + /// cannot be reused must always return false. + /// + /// The TestPackage to be loaded. + /// True if the runner may be reused for the provided package. + bool CanReuse(ITestEngineRunner runner, TestPackage package); + } +} diff --git a/src/nunit.engine/Internal/AssemblyHelper.cs b/src/nunit.engine/Internal/AssemblyHelper.cs new file mode 100644 index 00000000..f773afcb --- /dev/null +++ b/src/nunit.engine/Internal/AssemblyHelper.cs @@ -0,0 +1,62 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Reflection; + +namespace NUnit.Engine.Internal +{ + public static class AssemblyHelper + { + #region GetAssemblyPath + + public static string GetAssemblyPath(Type type) + { + return GetAssemblyPath(type.Assembly); + } + + public static string GetAssemblyPath(Assembly assembly) + { + string path = assembly.CodeBase; + Uri uri = new Uri(path); + + // If it wasn't loaded locally, use the Location + if (!uri.IsFile) + return assembly.Location; + + if (uri.IsUnc) + return path.Substring(Uri.UriSchemeFile.Length + 1); + + return uri.LocalPath; + } + + #endregion + + #region GetDirectoryName + public static string GetDirectoryName( Assembly assembly ) + { + return System.IO.Path.GetDirectoryName(GetAssemblyPath(assembly)); + } + #endregion + } +} diff --git a/src/nunit.engine/Internal/AssemblyReader.cs b/src/nunit.engine/Internal/AssemblyReader.cs new file mode 100644 index 00000000..60285ce5 --- /dev/null +++ b/src/nunit.engine/Internal/AssemblyReader.cs @@ -0,0 +1,204 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Reflection; +using System.Text; +using System.IO; + +namespace NUnit.Engine.Internal +{ + /// + /// AssemblyReader knows how to find various things in an assembly header + /// + public class AssemblyReader : IDisposable + { + private string assemblyPath; + private BinaryReader rdr; + private FileStream fs; + + UInt16 dos_magic = 0xffff; + UInt32 pe_signature = 0xffffffff; + UInt16 numberOfSections; + UInt16 optionalHeaderSize; + UInt16 peType; + UInt32 numDataDirectoryEntries; + + private uint peHeader = 0; + private uint fileHeader = 0; + private uint optionalHeader = 0; + private uint dataDirectory = 0; + private uint dataSections = 0; + + private struct DataSection + { + public uint virtualAddress; + public uint virtualSize; + public uint fileOffset; + }; + + private DataSection[] sections; + + public AssemblyReader( string assemblyPath ) + { + this.assemblyPath = assemblyPath; + CalcHeaderOffsets(); + } + + public AssemblyReader( Assembly assembly ) + { + this.assemblyPath = AssemblyHelper.GetAssemblyPath( assembly ); + CalcHeaderOffsets(); + } + + private void CalcHeaderOffsets() + { + this.fs = new FileStream( assemblyPath, FileMode.Open, FileAccess.Read ); + this.rdr = new BinaryReader( fs ); + dos_magic = rdr.ReadUInt16(); + if ( dos_magic == 0x5a4d ) + { + fs.Position = 0x3c; + peHeader = rdr.ReadUInt32(); + fileHeader = peHeader + 4; + optionalHeader = fileHeader + 20; + + fs.Position = optionalHeader; + peType = rdr.ReadUInt16(); + + dataDirectory = peType == 0x20b + ? optionalHeader + 112 + : optionalHeader + 96; + + fs.Position = dataDirectory - 4; + numDataDirectoryEntries = rdr.ReadUInt32(); + + fs.Position = peHeader; + pe_signature = rdr.ReadUInt32(); + rdr.ReadUInt16(); // machine + numberOfSections = rdr.ReadUInt16(); + fs.Position += 12; + optionalHeaderSize = rdr.ReadUInt16(); + dataSections = optionalHeader + optionalHeaderSize; + + sections = new DataSection[numberOfSections]; + fs.Position = dataSections; + for( int i = 0; i < numberOfSections; i++ ) + { + fs.Position += 8; + sections[i].virtualSize = rdr.ReadUInt32(); + sections[i].virtualAddress = rdr.ReadUInt32(); + uint rawDataSize = rdr.ReadUInt32(); + sections[i].fileOffset = rdr.ReadUInt32(); + if ( sections[i].virtualSize == 0 ) + sections[i].virtualSize = rawDataSize; + + fs.Position += 16; + } + } + } + + private uint DataDirectoryRva( int n ) + { + fs.Position = dataDirectory + n * 8; + return rdr.ReadUInt32(); + } + + private uint RvaToLfa( uint rva ) + { + for( int i = 0; i < numberOfSections; i++ ) + if ( rva >= sections[i].virtualAddress && rva < sections[i].virtualAddress + sections[i].virtualSize ) + return rva - sections[i].virtualAddress + sections[i].fileOffset; + + return 0; + } + + public string AssemblyPath + { + get { return assemblyPath; } + } + + public bool IsValidPeFile + { + get { return dos_magic == 0x5a4d && pe_signature == 0x00004550; } + } + + public bool IsDotNetFile + { + get { return IsValidPeFile && numDataDirectoryEntries > 14 && DataDirectoryRva(14) != 0; } + } + + public bool Is64BitImage + { + get { return peType == 0x20b; } + } + + public string ImageRuntimeVersion + { + get + { + string runtimeVersion = string.Empty; + + if (this.IsDotNetFile) + { + uint rva = DataDirectoryRva(14); + if (rva != 0) + { + fs.Position = RvaToLfa(rva) + 8; + uint metadata = rdr.ReadUInt32(); + fs.Position = RvaToLfa(metadata); + if (rdr.ReadUInt32() == 0x424a5342) + { + // Copy string representing runtime version + fs.Position += 12; + StringBuilder sb = new StringBuilder(); + char c; + while ((c = rdr.ReadChar()) != '\0') + sb.Append(c); + + if (sb[0] == 'v') // Last sanity check + runtimeVersion = sb.ToString(); + + // Could do fixups here for bad values in older files + // like 1.x86, 1.build, etc. But we are only using + // the major version anyway + } + } + } + + return runtimeVersion; + } + } + + public void Dispose() + { + if ( fs != null ) + fs.Close(); + if ( rdr != null ) + rdr.Close(); + + fs = null; + rdr = null; + } + } +} diff --git a/src/nunit.engine/Internal/DomainUsage.cs b/src/nunit.engine/Internal/DomainUsage.cs new file mode 100644 index 00000000..5baa3831 --- /dev/null +++ b/src/nunit.engine/Internal/DomainUsage.cs @@ -0,0 +1,52 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +namespace NUnit.Engine.Internal +{ + /// + /// Represents the manner in which test assemblies use + /// AppDomains to provide isolation + /// + public enum DomainUsage + { + /// + /// Use the default setting, depending on the runner + /// and the nature of the tests to be loaded. + /// + Default, + /// + /// Don't create a test domain - run in the primary AppDomain. + /// Note that this requires the tests to be available in the + /// NUnit appbase or probing path. + /// + None, + /// + /// Run tests in a single separate test domain + /// + Single, + /// + /// Run tests in a separate domain per assembly + /// + Multiple + } +} \ No newline at end of file diff --git a/src/nunit.engine/Internal/InternalTrace.cs b/src/nunit.engine/Internal/InternalTrace.cs new file mode 100644 index 00000000..c7ee6c09 --- /dev/null +++ b/src/nunit.engine/Internal/InternalTrace.cs @@ -0,0 +1,87 @@ +// *********************************************************************** +// Copyright (c) 2008-2013 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; + +namespace NUnit.Engine.Internal +{ + /// + /// InternalTrace provides facilities for tracing the execution + /// of the NUnit framework. Tests and classes under test may make use + /// of Console writes, System.Diagnostics.Trace or various loggers and + /// NUnit itself traps and processes each of them. For that reason, a + /// separate internal trace is needed. + /// + /// Note: + /// InternalTrace uses a global lock to allow multiple threads to write + /// trace messages. This can easily make it a bottleneck so it must be + /// used sparingly. Keep the trace Level as low as possible and only + /// insert InternalTrace writes where they are needed. + /// TODO: add some buffering and a separate writer thread as an option. + /// TODO: figure out a way to turn on trace in specific classes only. + /// + public static class InternalTrace + { + private static InternalTraceLevel traceLevel; + private static InternalTraceWriter traceWriter; + + /// + /// Gets a flag indicating whether the InternalTrace is initialized + /// + public static bool Initialized { get; private set; } + + /// + /// Initialize the internal trace facility using the name of the log + /// to be written to and the trace level. + /// + /// The log name + /// The trace level + public static void Initialize(string logName, InternalTraceLevel level) + { + if (!Initialized) + { + traceLevel = level; + + if (traceWriter == null && traceLevel > InternalTraceLevel.Off) + { + traceWriter = new InternalTraceWriter(logName); + traceWriter.WriteLine("InternalTrace: Initializing at level {0}", traceLevel); + } + + Initialized = true; + } + else + traceWriter.WriteLine("InternalTrace: Ignoring attempted re-initialization at level {0}", level); + } + + public static Logger GetLogger(string name) + { + return new Logger(name, traceLevel, traceWriter); + } + + public static Logger GetLogger( Type type ) + { + return GetLogger(type.FullName); + } + } +} diff --git a/src/nunit.engine/Internal/InternalTraceWriter.cs b/src/nunit.engine/Internal/InternalTraceWriter.cs new file mode 100644 index 00000000..086697ab --- /dev/null +++ b/src/nunit.engine/Internal/InternalTraceWriter.cs @@ -0,0 +1,86 @@ +// *********************************************************************** +// Copyright (c) 2008-2013 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Diagnostics; +using System.IO; +using System.Security.AccessControl; + +namespace NUnit.Engine.Internal +{ + /// + /// A trace listener that writes to a separate file per domain + /// and process using it. + /// + public class InternalTraceWriter : TextWriter + { + TextWriter writer; + + /// + /// Construct an InternalTraceWriter that writes to a file. + /// + /// Path to the file to use + public InternalTraceWriter(string logPath) + { + var streamWriter = new StreamWriter(new FileStream(logPath, FileMode.Append, FileAccess.Write, FileShare.Write)); + streamWriter.AutoFlush = true; + this.writer = streamWriter; + } + + public override System.Text.Encoding Encoding + { + get { return writer.Encoding; } + } + + public override void Write(char value) + { + writer.Write(value); + } + + public override void Write(string value) + { + base.Write(value); + } + + public override void WriteLine(string value) + { + writer.WriteLine(value); + } + + public override void Close() + { + if (writer != null) + { + writer.Flush(); + writer.Close(); + writer = null; + } + } + + public override void Flush() + { + if ( writer != null ) + writer.Flush(); + } + } +} diff --git a/src/nunit.engine/Internal/Logger.cs b/src/nunit.engine/Internal/Logger.cs new file mode 100644 index 00000000..8d954826 --- /dev/null +++ b/src/nunit.engine/Internal/Logger.cs @@ -0,0 +1,130 @@ +// *********************************************************************** +// Copyright (c) 2008-2013 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; + +namespace NUnit.Engine.Internal +{ + public class Logger : ILogger + { + private readonly static string TIME_FMT = "HH:mm:ss.fff"; + private readonly static string TRACE_FMT = "{0} {1,-5} [{2,2}] {3}: {4}"; + + private string name; + private string fullname; + private InternalTraceLevel maxLevel; + private TextWriter writer; + + public Logger(string name, InternalTraceLevel level, TextWriter writer) + { + this.maxLevel = level; + this.writer = writer; + this.fullname = this.name = name; + int index = fullname.LastIndexOf('.'); + if (index >= 0) + this.name = fullname.Substring(index + 1); + } + + #region Error + public void Error(string message) + { + Log(InternalTraceLevel.Error, message); + } + + public void Error(string message, params object[] args) + { + Log(InternalTraceLevel.Error, message, args); + } + + //public void Error(string message, Exception ex) + //{ + // if (service.Level >= InternalTraceLevel.Error) + // { + // service.Log(InternalTraceLevel.Error, message, name, ex); + // } + //} + #endregion + + #region Warning + public void Warning(string message) + { + Log(InternalTraceLevel.Warning, message); + } + + public void Warning(string message, params object[] args) + { + Log(InternalTraceLevel.Warning, message, args); + } + #endregion + + #region Info + public void Info(string message) + { + Log(InternalTraceLevel.Info, message); + } + + public void Info(string message, params object[] args) + { + Log(InternalTraceLevel.Info, message, args); + } + #endregion + + #region Debug + public void Debug(string message) + { + Log(InternalTraceLevel.Verbose, message); + } + + public void Debug(string message, params object[] args) + { + Log(InternalTraceLevel.Verbose, message, args); + } + #endregion + + #region Helper Methods + private void Log(InternalTraceLevel level, string message) + { + if (writer != null && this.maxLevel >= level) + WriteLog(level, message); + } + + private void Log(InternalTraceLevel level, string format, params object[] args) + { + if (this.maxLevel >= level) + WriteLog(level, string.Format( format, args ) ); + } + + private void WriteLog(InternalTraceLevel level, string message) + { + writer.WriteLine(TRACE_FMT, + DateTime.Now.ToString(TIME_FMT), + level == InternalTraceLevel.Verbose ? "Debug" : level.ToString(), + System.Threading.Thread.CurrentThread.ManagedThreadId, + name, + message); + } + + #endregion + } +} diff --git a/src/nunit.engine/Internal/NUnitConfiguration.cs b/src/nunit.engine/Internal/NUnitConfiguration.cs new file mode 100644 index 00000000..c85c825f --- /dev/null +++ b/src/nunit.engine/Internal/NUnitConfiguration.cs @@ -0,0 +1,239 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using System.Reflection; +using System.Configuration; +using System.Collections.Specialized; +using System.Threading; +using Microsoft.Win32; + +namespace NUnit.Engine.Internal +{ + /// + /// Provides static methods for accessing the NUnit config + /// file + /// + public static class NUnitConfiguration + { + #region Public Properties + + #region BuildConfiguration + public static string BuildConfiguration + { + get + { +#if DEBUG + return "Debug"; +#else + return "Release"; +#endif + } + } + #endregion + + #region NUnitLibDirectory + private static string nunitLibDirectory; + /// + /// Gets the path to the lib directory for the version and build + /// of NUnit currently executing. + /// + public static string NUnitLibDirectory + { + get + { + if (nunitLibDirectory == null) + { + nunitLibDirectory = + AssemblyHelper.GetDirectoryName(Assembly.GetExecutingAssembly()); + } + + return nunitLibDirectory; + } + } + #endregion + + #region NUnitBinDirectory + private static string nunitBinDirectory; + public static string NUnitBinDirectory + { + get + { + if (nunitBinDirectory == null) + { + nunitBinDirectory = NUnitLibDirectory; + if (Path.GetFileName(nunitBinDirectory).ToLower() == "lib") + nunitBinDirectory = Path.GetDirectoryName(nunitBinDirectory); + } + + return nunitBinDirectory; + } + } + #endregion + + #region AddinDirectory + private static string addinDirectory; + public static string AddinDirectory + { + get + { + if (addinDirectory == null) + { + addinDirectory = Path.Combine(NUnitBinDirectory, "addins"); + } + + return addinDirectory; + } + } + #endregion + + #region TestAgentExePath + //private static string testAgentExePath; + //private static string TestAgentExePath + //{ + // get + // { + // if (testAgentExePath == null) + // testAgentExePath = Path.Combine(NUnitBinDirectory, "nunit-agent.exe"); + + // return testAgentExePath; + // } + //} + #endregion + + #region MonoExePath + private static string monoExePath; + public static string MonoExePath + { + get + { + if (monoExePath == null) + { + string[] searchNames = IsWindows() + ? new string[] { "mono.bat", "mono.cmd", "mono.exe" } + : new string[] { "mono", "mono.exe" }; + + monoExePath = FindOneOnPath(searchNames); + + if (monoExePath == null && IsWindows()) + { + RegistryKey key = Registry.LocalMachine.OpenSubKey(@"Software\Novell\Mono"); + if (key != null) + { + string version = key.GetValue("DefaultCLR") as string; + if (version != null) + { + key = key.OpenSubKey(version); + if (key != null) + { + string installDir = key.GetValue("SdkInstallRoot") as string; + if (installDir != null) + monoExePath = Path.Combine(installDir, @"bin\mono.exe"); + } + } + } + } + + if (monoExePath == null) + return "mono"; + } + + return monoExePath; + } + } + + private static string FindOneOnPath(string[] names) + { + //foreach (string dir in Environment.GetEnvironmentVariable("path").Split(new char[] { Path.PathSeparator })) + // foreach (string name in names) + // { + // string fullPath = Path.Combine(dir, name); + // if (File.Exists(fullPath)) + // return name; + // } + + return null; + } + + private static bool IsWindows() + { + return Environment.OSVersion.Platform == PlatformID.Win32NT; + } + #endregion + + #region ApplicationDataDirectory + private static string applicationDirectory; + public static string ApplicationDirectory + { + get + { + if (applicationDirectory == null) + { + applicationDirectory = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "NUnit"); + } + + return applicationDirectory; + } + } + #endregion + + #region HelpUrl + public static string HelpUrl + { + get + { + string helpUrl = ConfigurationManager.AppSettings["helpUrl"]; + + if (helpUrl == null) + { + helpUrl = "http://nunit.org"; + string dir = Path.GetDirectoryName(NUnitBinDirectory); + if ( dir != null ) + { + dir = Path.GetDirectoryName(dir); + if ( dir != null ) + { + string localPath = Path.Combine(dir, @"doc/index.html"); + if (File.Exists(localPath)) + { + UriBuilder uri = new UriBuilder(); + uri.Scheme = "file"; + uri.Host = "localhost"; + uri.Path = localPath; + helpUrl = uri.ToString(); + } + } + } + } + + return helpUrl; + } + } + #endregion + + #endregion + } +} diff --git a/src/nunit.engine/Internal/NUnitProject.cs b/src/nunit.engine/Internal/NUnitProject.cs new file mode 100644 index 00000000..b9f8894b --- /dev/null +++ b/src/nunit.engine/Internal/NUnitProject.cs @@ -0,0 +1,177 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System.IO; +using System.Xml; + +namespace NUnit.Engine.Internal +{ + public class NUnitProject : IProject + { + #region Instance Fields + + /// + /// The XmlDocument representing the loaded doc. It + /// is generated from the text when the doc is loaded + /// unless an exception is thrown. It is modified as the + /// user makes changes. + /// + XmlDocument xmlDoc = new XmlDocument(); + + /// + /// Path to the file storing this project + /// + private string projectPath; + + /// + /// Our collection of configs + /// + private ProjectConfigList configs; + + #endregion + + #region IProject Members + + /// + /// Gets the path to the file storing this project, if any. + /// If the project has not been saved, this is null. + /// + public string ProjectPath + { + get { return projectPath; } + } + + /// + /// EffectiveBasePath uses the BasePath if present and otherwise + /// defaults to the directory part of the ProjectPath. + /// + public string EffectiveBasePath + { + get + { + string appbase = this.GetSetting("appbase"); + + if (this.ProjectPath == null) + return appbase; + + if (appbase == null) + return Path.GetDirectoryName(this.ProjectPath); + + return Path.Combine( + Path.GetDirectoryName(this.ProjectPath), + appbase); + } + } + + /// + /// Gets the active configuration, as defined + /// by the particular project. For an NUnit + /// project, we use the activeConfig setting + /// if present and otherwise return the first + /// config found. + /// + public IProjectConfig ActiveConfig + { + get + { + if (configs.Count == 0) + return null; + + string activeConfigName = GetSetting("activeconfig"); + + return activeConfigName != null + ? configs[activeConfigName] + : configs[0]; + } + } + + public IProjectConfigList Configs + { + get { return configs; } + } + + #endregion + + #region Other Public Methods + + public void Load(string filename) + { + this.projectPath = Path.GetFullPath(filename); + xmlDoc.Load(this.projectPath); + + configs = new ProjectConfigList(this); + } + + public void LoadXml(string xmlText) + { + xmlDoc.LoadXml(xmlText); + + configs = new ProjectConfigList(this); + } + + public string GetSetting(string name) + { + return SettingsNode != null + ? SettingsNode.GetAttribute(name) + : null; + } + + public string GetSetting(string name, string defaultValue) + { + string result = GetSetting(name); + return result == null + ? defaultValue + : result; + } + + #endregion + + #region Internal Properties + + /// + /// The top-level (NUnitProject) node + /// + internal XmlNode RootNode + { + get { return xmlDoc.FirstChild; } + } + + /// + /// The Settings node if present, otherwise null + /// + internal XmlNode SettingsNode + { + get { return RootNode.SelectSingleNode("Settings"); } + } + + /// + /// The collection of Config nodes - may be empty + /// + internal XmlNodeList ConfigNodes + { + get { return RootNode.SelectNodes("Config"); } + } + + #endregion + } +} diff --git a/src/nunit.engine/Internal/PathUtils.cs b/src/nunit.engine/Internal/PathUtils.cs new file mode 100644 index 00000000..8f986a68 --- /dev/null +++ b/src/nunit.engine/Internal/PathUtils.cs @@ -0,0 +1,234 @@ +// *********************************************************************** +// Copyright (c) 2002-2003 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using System.Text; +using System.Reflection; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace NUnit.Engine.Internal +{ + /// + /// Static methods for manipulating project paths, including both directories + /// and files. Some synonyms for System.Path methods are included as well. + /// + public class PathUtils + { + public const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010; + public const uint FILE_ATTRIBUTE_NORMAL = 0x00000080; + public const int MAX_PATH = 256; + + protected static char DirectorySeparatorChar = Path.DirectorySeparatorChar; + protected static char AltDirectorySeparatorChar = Path.AltDirectorySeparatorChar; + + #region Public methods + + /// + /// Returns a boolean indicating whether the specified path + /// is that of an assembly - that is a dll or exe file. + /// + /// Path to a file. + /// True if the file extension is dll or exe, otherwise false. + public static bool IsAssemblyFileType(string path) + { + string extension = Path.GetExtension(path).ToLower(); + return extension == ".dll" || extension == ".exe"; + } + + /// + /// Returns the relative path from a base directory to another + /// directory or file. + /// + public static string RelativePath( string from, string to ) + { + if (from == null) + throw new ArgumentNullException (from); + if (to == null) + throw new ArgumentNullException (to); + + string toPathRoot = Path.GetPathRoot(to); + if (toPathRoot == null || toPathRoot == string.Empty) + return to; + string fromPathRoot = Path.GetPathRoot(from); + + if (!PathsEqual(toPathRoot, fromPathRoot)) + return null; + + string fromNoRoot = from.Substring(fromPathRoot.Length); + string toNoRoot = to.Substring(toPathRoot.Length); + + string[] _from = SplitPath(fromNoRoot); + string[] _to = SplitPath(toNoRoot); + + StringBuilder sb = new StringBuilder (Math.Max (from.Length, to.Length)); + + int last_common, min = Math.Min (_from.Length, _to.Length); + for (last_common = 0; last_common < min; ++last_common) + { + if (!PathsEqual(_from[last_common], _to[last_common])) + break; + } + + if (last_common < _from.Length) + sb.Append (".."); + for (int i = last_common + 1; i < _from.Length; ++i) + { + sb.Append (PathUtils.DirectorySeparatorChar).Append (".."); + } + + if (sb.Length > 0) + sb.Append (PathUtils.DirectorySeparatorChar); + if (last_common < _to.Length) + sb.Append (_to [last_common]); + for (int i = last_common + 1; i < _to.Length; ++i) + { + sb.Append (PathUtils.DirectorySeparatorChar).Append (_to [i]); + } + + return sb.ToString (); + } + + /// + /// Return the canonical form of a path. + /// + public static string Canonicalize( string path ) + { + List parts = new List( + path.Split( DirectorySeparatorChar, AltDirectorySeparatorChar ) ); + + for( int index = 0; index < parts.Count; ) + { + string part = (string)parts[index]; + + switch( part ) + { + case ".": + parts.RemoveAt( index ); + break; + + case "..": + parts.RemoveAt( index ); + if ( index > 0 ) + parts.RemoveAt( --index ); + break; + default: + index++; + break; + } + } + + // Trailing separator removal + if (parts.Count > 1 && path.Length > 1 && (string)parts[parts.Count - 1] == "") + parts.RemoveAt(parts.Count - 1); + + return String.Join(DirectorySeparatorChar.ToString(), parts.ToArray()); + } + + /// + /// True if the two paths are the same or if the second is + /// directly or indirectly under the first. Note that paths + /// using different network shares or drive letters are + /// considered unrelated, even if they end up referencing + /// the same subtrees in the file system. + /// + public static bool SamePathOrUnder( string path1, string path2 ) + { + path1 = Canonicalize( path1 ); + path2 = Canonicalize( path2 ); + + int length1 = path1.Length; + int length2 = path2.Length; + + // if path1 is longer, then path2 can't be under it + if ( length1 > length2 ) + return false; + + // if lengths are the same, check for equality + if ( length1 == length2 ) + return string.Compare( path1, path2, IsWindows() ) == 0; + + // path 2 is longer than path 1: see if initial parts match + if ( string.Compare( path1, path2.Substring( 0, length1 ), IsWindows() ) != 0 ) + return false; + + // must match through or up to a directory separator boundary + return path2[length1-1] == DirectorySeparatorChar || + path2[length1] == DirectorySeparatorChar; + } + + /// + /// Combines all the arguments into a single path + /// + public static string Combine(string path1, params string[] morePaths) + { + string result = path1; + foreach (string path in morePaths) + result = Path.Combine(result, path); + return result; + } + + #endregion + + #region Helper Methods + + private static bool IsWindows() + { + return PathUtils.DirectorySeparatorChar == '\\'; + } + + private static string[] SplitPath(string path) + { + char[] separators = new char[] { PathUtils.DirectorySeparatorChar, PathUtils.AltDirectorySeparatorChar }; + + string[] trialSplit = path.Split(separators); + + int emptyEntries = 0; + foreach (string piece in trialSplit) + if (piece == string.Empty) + emptyEntries++; + + if (emptyEntries == 0) + return trialSplit; + + string[] finalSplit = new string[trialSplit.Length - emptyEntries]; + int index = 0; + foreach (string piece in trialSplit) + if (piece != string.Empty) + finalSplit[index++] = piece; + + return finalSplit; + } + + private static bool PathsEqual(string path1, string path2) + { + if (PathUtils.IsWindows()) + return path1.ToLower().Equals(path2.ToLower()); + else + return path1.Equals(path2); + } + + #endregion + } +} diff --git a/src/nunit.engine/Internal/ProcessModel.cs b/src/nunit.engine/Internal/ProcessModel.cs new file mode 100644 index 00000000..33601c28 --- /dev/null +++ b/src/nunit.engine/Internal/ProcessModel.cs @@ -0,0 +1,50 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +namespace NUnit.Engine.Internal +{ + /// + /// Represents the manner in which test assemblies are + /// distributed across processes. + /// + public enum ProcessModel + { + /// + /// Use the default setting, depending on the runner + /// and the nature of the tests to be loaded. + /// + Default, + /// + /// Run tests directly in the NUnit process + /// + Single, + /// + /// Run tests in a single separate process + /// + Separate, + /// + /// Run tests in a separate process per assembly + /// + Multiple + } +} diff --git a/src/nunit.engine/Internal/ProjectConfig.cs b/src/nunit.engine/Internal/ProjectConfig.cs new file mode 100644 index 00000000..366d9ebc --- /dev/null +++ b/src/nunit.engine/Internal/ProjectConfig.cs @@ -0,0 +1,155 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; + +namespace NUnit.Engine.Internal +{ + public class ProjectConfig : IProjectConfig + { + #region Instance Variables + + private NUnitProject project; + + /// + /// The XmlNode representing this config + /// + private XmlNode configNode; + + /// + /// List of the test assemblies in this config + /// + private List assemblies; + + #endregion + + #region Constructor + + public ProjectConfig(NUnitProject project, XmlNode configNode) + { + this.project = project; + this.configNode = configNode; + this.assemblies = new List(); + foreach (XmlNode node in configNode.SelectNodes("assembly")) + { + string assemblyPath = node.GetAttribute("path"); + if (this.EffectiveBasePath != null) + assemblyPath = Path.Combine(this.EffectiveBasePath, assemblyPath); + this.assemblies.Add(assemblyPath); + } + } + + #endregion + + #region Properties + + public string Name + { + get { return GetAttribute("name"); } + } + + /// + /// The actual base path used in loading the tests. Its + /// value depends on the appbase entry of the config element + /// as well as the project EffectiveBasePath. + /// + private string EffectiveBasePath + { + get + { + string basePath = GetAttribute("appbase"); + + if (basePath == null) + return project.EffectiveBasePath; + + if (project.EffectiveBasePath == null) + return basePath; + + return Path.Combine(project.EffectiveBasePath, basePath); + } + } + + /// + /// Return our AssemblyList + /// + public string[] Assemblies + { + get { return assemblies.ToArray(); } + } + + private IDictionary settings; + public IDictionarySettings + { + get + { + if (settings == null) + { + settings = new Dictionary(); + + if (EffectiveBasePath != project.ProjectPath) + settings[RunnerSettings.BasePath] = EffectiveBasePath; + + string configFile = GetAttribute("configfile"); + if (configFile != null) + settings[RunnerSettings.ConfigurationFile] = configFile; + + string binpath = GetAttribute("binpath"); + if (binpath != null) + settings[RunnerSettings.PrivateBinPath] = binpath; + + string binpathtype = GetAttribute("binpathtype"); + if (binpathtype != null && binpathtype.ToLower() == "auto") + settings[RunnerSettings.AutoBinPath] = true; + + string runtime = GetAttribute("runtimeFramework"); + if (runtime != null) + settings[RunnerSettings.RuntimeFramework] = runtime; + + string processModel = project.GetSetting("processModel"); + if (processModel != null) + settings[RunnerSettings.ProcessModel] = processModel; + + string domainUsage = project.GetSetting("domainUsage"); + if (domainUsage != null) + settings[RunnerSettings.DomainUsage] = domainUsage; + } + + return settings; + } + } + + #endregion + + #region Helper Methods + + private string GetAttribute(string name) + { + return configNode.GetAttribute(name); + } + + #endregion + } +} diff --git a/src/nunit.engine/Internal/ProjectConfigList.cs b/src/nunit.engine/Internal/ProjectConfigList.cs new file mode 100644 index 00000000..68d6083c --- /dev/null +++ b/src/nunit.engine/Internal/ProjectConfigList.cs @@ -0,0 +1,94 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System.Xml; + +namespace NUnit.Engine.Internal +{ + /// + /// Summary description for ProjectConfigList. + /// + public class ProjectConfigList : IProjectConfigList + { + private NUnitProject project; + private XmlNode projectNode; + + public ProjectConfigList(NUnitProject project) + { + this.project = project; + this.projectNode = project.RootNode; + } + + #region IProjectConfigList Members + + public int Count + { + get { return ConfigNodes.Count; } + } + + public IProjectConfig this[int index] + { + get { return new Internal.ProjectConfig(project, ConfigNodes[index]); } + } + + public IProjectConfig this[string name] + { + get + { + int index = IndexOf(name); + return index >= 0 ? this[index] : null; + } + } + + #endregion + + #region Private Properties + + private XmlNodeList ConfigNodes + { + get { return projectNode.SelectNodes("Config"); } + } + + private XmlNode SettingsNode + { + get { return projectNode.SelectSingleNode("Settings"); } + } + + #endregion + + #region Private Methods + + private int IndexOf(string name) + { + for (int index = 0; index < ConfigNodes.Count; index++) + { + if (ConfigNodes[index].GetAttribute("name") == name) + return index; + } + + return -1; + } + + #endregion + } +} diff --git a/src/nunit.engine/Internal/ResultHelper.cs b/src/nunit.engine/Internal/ResultHelper.cs new file mode 100644 index 00000000..fd3d2b94 --- /dev/null +++ b/src/nunit.engine/Internal/ResultHelper.cs @@ -0,0 +1,206 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using System.Text; +using System.Xml; + +namespace NUnit.Engine.Internal +{ + /// + /// ResultHelper provides static methods for working with + /// TestEngineResults to wrap, combiner and aggregate them + /// in various ways. + /// + public static class ResultHelper + { + private const string TEST_SUITE_ELEMENT = "test-suite"; + private const string PROJECT_SUITE_TYPE = "Project"; + + #region TestEngineResult extension methods + + /// + /// Aggregate the XmlNodes under a TestEngineResult into a single XmlNode. + /// + /// A new TestEngineResult with xml nodes for each assembly or project + /// A TestEngineResult with a single top-level element. + public static TestEngineResult Aggregate(this TestEngineResult result, string elementName, string suiteType, string name, string fullname) + { + return new TestEngineResult(Aggregate(elementName, suiteType, name, fullname, result.XmlNodes)); + } + + /// + /// Aggregate the XmlNodes under a TestEngineResult into a single XmlNode. + /// + /// A new TestEngineResult with xml nodes for each assembly or project + /// A TestEngineResult with a single top-level element. + public static TestEngineResult Aggregate(this TestEngineResult result, string elementName, string name, string fullname) + { + return new TestEngineResult(Aggregate(elementName, name, fullname, result.XmlNodes)); + } + + /// + /// Aggregate all the separate assembly results of a project as a single node. + /// + public static TestEngineResult MakePackageResult(this TestEngineResult result, string name, string fullName) + { + return Aggregate(result, TEST_SUITE_ELEMENT, PROJECT_SUITE_TYPE, name, fullName); + } + + #endregion + + #region Methods that operate on a list of TestEngineResults + + /// + /// Merges multiple test engine results into a single result. The + /// result element contains all the XML nodes found in the input. + /// + /// A list of TestEngineResults + /// A TestEngineResult merging all the imput results + /// Used by AbstractTestRunner MakePackageResult method. + public static TestEngineResult Merge(IList results) + { + TestEngineResult mergedResult = new TestEngineResult(); + + foreach (TestEngineResult result in results) + foreach (XmlNode node in result.XmlNodes) + mergedResult.Add(node); + + return mergedResult; + } + + #endregion + + #region XmlNode extension methods + + /// + /// Insert an environment element as a child of the node provided. + /// + /// + public static void InsertEnvironmentElement(this XmlNode resultNode) + { + XmlNode env = resultNode.OwnerDocument.CreateElement("environment"); + resultNode.InsertAfter(env, null); + env.AddAttribute("nunit-version", Assembly.GetExecutingAssembly().GetName().Version.ToString()); + env.AddAttribute("clr-version", Environment.Version.ToString()); + env.AddAttribute("os-version", Environment.OSVersion.ToString()); + env.AddAttribute("platform", Environment.OSVersion.Platform.ToString()); + env.AddAttribute("cwd", Environment.CurrentDirectory); + env.AddAttribute("machine-name", Environment.MachineName); + env.AddAttribute("user", Environment.UserName); + env.AddAttribute("user-domain", Environment.UserDomainName); + env.AddAttribute("culture", System.Globalization.CultureInfo.CurrentCulture.ToString()); + env.AddAttribute("uiculture", System.Globalization.CultureInfo.CurrentUICulture.ToString()); + } + + #endregion + + #region Methods that operate on a list of XmlNodes + + public static XmlNode Aggregate(string elementName, string name, string fullname, IList resultNodes) + { + return Aggregate(elementName, null, name, fullname, resultNodes); + } + + public static XmlNode Aggregate(string elementName, string testType, string name, string fullname, IList resultNodes) + { + XmlNode combinedNode = XmlHelper.CreateTopLevelElement(elementName); + if (testType != null) + combinedNode.AddAttribute("type", testType); + combinedNode.AddAttribute("id", "2"); // TODO: Should not be hard-coded + if (name != null && name != string.Empty) + combinedNode.AddAttribute("name", name); + if (fullname != null && fullname != string.Empty) + combinedNode.AddAttribute("fullname", fullname); + + string status = "Inconclusive"; + //double totalDuration = 0.0d; + int testcasecount = 0; + int total = 0; + int passed = 0; + int failed = 0; + int inconclusive = 0; + int skipped = 0; + int asserts = 0; + + bool isTestRunResult = false; + + foreach (XmlNode node in resultNodes) + { + testcasecount += node.GetAttribute("testcasecount", 0); + + XmlAttribute resultAttribute = node.Attributes["result"]; + if (resultAttribute != null) + { + isTestRunResult = true; + + switch (resultAttribute.Value) + { + case "Skipped": + if (status == "Inconclusive") + status = "Skipped"; + break; + case "Passed": + if (status != "Failed") + status = "Passed"; + break; + case "Failed": + status = "Failed"; + break; + } + + total += node.GetAttribute("total", 0); + passed += node.GetAttribute("passed", 0); + failed += node.GetAttribute("failed", 0); + inconclusive += node.GetAttribute("inconclusive", 0); + skipped += node.GetAttribute("skipped", 0); + asserts += node.GetAttribute("asserts", 0); + } + + XmlNode import = combinedNode.OwnerDocument.ImportNode(node, true); + combinedNode.AppendChild(import); + } + + combinedNode.AddAttribute("testcasecount", testcasecount.ToString()); + + if (isTestRunResult) + { + combinedNode.AddAttribute("result", status); + //combinedNode.AddAttribute("duration", totalDuration.ToString("0.000000", NumberFormatInfo.InvariantInfo)); + combinedNode.AddAttribute("total", total.ToString()); + combinedNode.AddAttribute("passed", passed.ToString()); + combinedNode.AddAttribute("failed", failed.ToString()); + combinedNode.AddAttribute("inconclusive", inconclusive.ToString()); + combinedNode.AddAttribute("skipped", skipped.ToString()); + combinedNode.AddAttribute("asserts", asserts.ToString()); + } + + return combinedNode; + } + + #endregion + } +} diff --git a/src/nunit.engine/Internal/ServerBase.cs b/src/nunit.engine/Internal/ServerBase.cs new file mode 100644 index 00000000..ce035383 --- /dev/null +++ b/src/nunit.engine/Internal/ServerBase.cs @@ -0,0 +1,135 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Threading; +using System.Runtime.Remoting; +using System.Runtime.Remoting.Services; +using System.Runtime.Remoting.Channels; +using System.Runtime.Remoting.Channels.Tcp; + +namespace NUnit.Engine.Internal +{ + /// + /// Summary description for ServerBase. + /// + public abstract class ServerBase : MarshalByRefObject, IDisposable + { + protected string uri; + protected int port; + + private TcpChannel channel; + private bool isMarshalled; + + private object theLock = new object(); + + protected ServerBase() + { + } + + /// + /// Constructor used to provide + /// + /// + /// + protected ServerBase(string uri, int port) + { + this.uri = uri; + this.port = port; + } + + public string ServerUrl + { + get { return string.Format("tcp://127.0.0.1:{0}/{1}", port, uri); } + } + + public virtual void Start() + { + if (uri != null && uri != string.Empty) + { + lock (theLock) + { + this.channel = ServerUtilities.GetTcpChannel(uri + "Channel", port, 100); + + RemotingServices.Marshal(this, uri); + this.isMarshalled = true; + } + + if (this.port == 0) + { + ChannelDataStore store = this.channel.ChannelData as ChannelDataStore; + if (store != null) + { + string channelUri = store.ChannelUris[0]; + this.port = int.Parse(channelUri.Substring(channelUri.LastIndexOf(':') + 1)); + } + } + } + } + + [System.Runtime.Remoting.Messaging.OneWay] + public virtual void Stop() + { + lock( theLock ) + { + if ( this.isMarshalled ) + { + RemotingServices.Disconnect( this ); + this.isMarshalled = false; + } + + if ( this.channel != null ) + { + ChannelServices.UnregisterChannel( this.channel ); + this.channel = null; + } + + Monitor.PulseAll( theLock ); + } + } + + public void WaitForStop() + { + lock( theLock ) + { + Monitor.Wait( theLock ); + } + } + + #region IDisposable Members + + public void Dispose() + { + this.Stop(); + } + + #endregion + + #region InitializeLifetimeService + public override object InitializeLifetimeService() + { + return null; + } + #endregion + } +} diff --git a/src/nunit.engine/Internal/ServerUtilities.cs b/src/nunit.engine/Internal/ServerUtilities.cs new file mode 100644 index 00000000..353a8dfd --- /dev/null +++ b/src/nunit.engine/Internal/ServerUtilities.cs @@ -0,0 +1,139 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections; +using System.Runtime.Remoting; +using System.Runtime.Remoting.Channels; +using System.Runtime.Remoting.Channels.Tcp; +using System.Reflection; + +namespace NUnit.Engine.Internal +{ + /// + /// Summary description for RemotingUtilities. + /// + public static class ServerUtilities + { + static Logger log = InternalTrace.GetLogger(typeof(ServerUtilities)); + + /// + /// Create a TcpChannel with a given name, on a given port. + /// + /// + /// + /// + private static TcpChannel CreateTcpChannel( string name, int port, int limit ) + { + Hashtable props = new Hashtable(); + props.Add( "port", port ); + props.Add( "name", name ); + props.Add( "bindTo", "127.0.0.1" ); + + BinaryServerFormatterSinkProvider serverProvider = + new BinaryServerFormatterSinkProvider(); + + // NOTE: TypeFilterLevel and "clientConnectionLimit" property don't exist in .NET 1.0. + Type typeFilterLevelType = typeof(object).Assembly.GetType("System.Runtime.Serialization.Formatters.TypeFilterLevel"); + if (typeFilterLevelType != null) + { + PropertyInfo typeFilterLevelProperty = serverProvider.GetType().GetProperty("TypeFilterLevel"); + object typeFilterLevel = Enum.Parse(typeFilterLevelType, "Full"); + typeFilterLevelProperty.SetValue(serverProvider, typeFilterLevel, null); + +// props.Add("clientConnectionLimit", limit); + } + + BinaryClientFormatterSinkProvider clientProvider = + new BinaryClientFormatterSinkProvider(); + + return new TcpChannel( props, clientProvider, serverProvider ); + } + + public static TcpChannel GetTcpChannel() + { + return GetTcpChannel( "", 0, 2 ); + } + + /// + /// Get a channel by name, casting it to a TcpChannel. + /// Otherwise, create, register and return a TcpChannel with + /// that name, on the port provided as the second argument. + /// + /// The channel name + /// The port to use if the channel must be created + /// A TcpChannel or null + public static TcpChannel GetTcpChannel( string name, int port ) + { + return GetTcpChannel( name, port, 2 ); + } + + /// + /// Get a channel by name, casting it to a TcpChannel. + /// Otherwise, create, register and return a TcpChannel with + /// that name, on the port provided as the second argument. + /// + /// The channel name + /// The port to use if the channel must be created + /// The client connection limit or negative for the default + /// A TcpChannel or null + public static TcpChannel GetTcpChannel( string name, int port, int limit ) + { + TcpChannel channel = ChannelServices.GetChannel( name ) as TcpChannel; + + if ( channel == null ) + { + // NOTE: Retries are normally only needed when rapidly creating + // and destroying channels, as in running the NUnit tests. + int retries = 10; + while( --retries > 0 ) + try + { + channel = CreateTcpChannel( name, port, limit ); + ChannelServices.RegisterChannel( channel, false ); + break; + } + catch( Exception e ) + { + log.Error("Failed to create/register channel", e); + System.Threading.Thread.Sleep(300); + } + } + + return channel; + } + + public static void SafeReleaseChannel( IChannel channel ) + { + if( channel != null ) + try + { + ChannelServices.UnregisterChannel( channel ); + } + catch( RemotingException ) + { + // Channel was not registered - ignore + } + } + } +} diff --git a/src/nunit.engine/Internal/SettingsGroup.cs b/src/nunit.engine/Internal/SettingsGroup.cs new file mode 100644 index 00000000..7567a86f --- /dev/null +++ b/src/nunit.engine/Internal/SettingsGroup.cs @@ -0,0 +1,166 @@ +// *********************************************************************** +// Copyright (c) 2013 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Xml; + +namespace NUnit.Engine.Internal +{ + /// + /// SettingsGroup is the base class representing a group + /// of user or system settings. + /// + public class SettingsGroup : ISettings, IDisposable + { + static Logger log = InternalTrace.GetLogger("SettingsGroup"); + + protected Dictionary _settings = new Dictionary(); + + public event SettingsEventHandler Changed; + + #region ISettings Members + + /// + /// Load the value of one of the group's settings + /// + /// Name of setting to load + /// Value of the setting or null + public object GetSetting(string settingName) + { + return _settings.ContainsKey(settingName) + ? _settings[settingName] + : null; + } + + /// + /// Load the value of one of the group's settings or return a default value + /// + /// Name of setting to load + /// Value to return if the seeting is not present + /// Value of the setting or the default + public T GetSetting(string settingName, T defaultValue) + { + object result = GetSetting(settingName); + + if (result == null) + return defaultValue; + + if (result is T) + return (T)result; + + try + { + TypeConverter converter = TypeDescriptor.GetConverter(typeof(T)); + if (converter == null) + return defaultValue; + + return (T)converter.ConvertFrom(result); + } + catch(Exception ex) + { + + log.Error("Unable to convert setting {0} to {1}", settingName, typeof(T).Name); + log.Error(ex.Message); + return defaultValue; + } + } + + /// + /// Remove a setting from the group + /// + /// Name of the setting to remove + public void RemoveSetting(string settingName) + { + _settings.Remove(settingName); + + if (Changed != null) + Changed(this, new SettingsEventArgs(settingName)); + } + + /// + /// Remove a group of settings + /// + /// + public void RemoveGroup(string groupName) + { + List keysToRemove = new List(); + + string prefix = groupName; + if (!prefix.EndsWith(".")) + prefix = prefix + "."; + + foreach (string key in _settings.Keys) + if (key.StartsWith(prefix)) + keysToRemove.Add(key); + + foreach (string key in keysToRemove) + _settings.Remove(key); + } + + /// + /// Save the value of one of the group's settings + /// + /// Name of the setting to save + /// Value to be saved + public void SaveSetting(string settingName, object settingValue) + { + object oldValue = GetSetting(settingName); + + // Avoid signaling "changes" when there is not really a change + if (oldValue != null) + { + if (oldValue is string && settingValue is string && (string)oldValue == (string)settingValue || + oldValue is int && settingValue is int && (int)oldValue == (int)settingValue || + oldValue is bool && settingValue is bool && (bool)oldValue == (bool)settingValue || + oldValue is Enum && settingValue is Enum && oldValue.Equals(settingValue)) + return; + } + + _settings[settingName] = settingValue; + + if (Changed != null) + Changed(this, new SettingsEventArgs(settingName)); + } + + #endregion + + #region IDisposable Members + /// + /// Dispose of this group by disposing of it's _settings implementation + /// + public void Dispose() + { + if (_settings != null) + { + //_settings.Dispose(); + _settings = null; + } + } + #endregion + } +} diff --git a/src/nunit.engine/Internal/SettingsStore.cs b/src/nunit.engine/Internal/SettingsStore.cs new file mode 100644 index 00000000..354a6ffc --- /dev/null +++ b/src/nunit.engine/Internal/SettingsStore.cs @@ -0,0 +1,135 @@ +// *********************************************************************** +// Copyright (c) 2013 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Xml; + +namespace NUnit.Engine.Internal +{ + /// + /// SettingsStore extends SettingsGroup to provide for + /// loading and saving the settings from an XML file. + /// + public class SettingsStore : SettingsGroup + { + private string _settingsFile; + private bool _writeable; + + #region Constructors + + /// + /// Construct a SettingsStore without a backing file - used for testing. + /// + public SettingsStore() { } + + /// + /// Construct a SettingsStore with a file name and indicate whether it is writeable + /// + /// + /// + public SettingsStore(string settingsFile, bool writeable) + { + _settingsFile = Path.GetFullPath(settingsFile); + _writeable = writeable; + } + + #endregion + + #region Public Methods + + public void LoadSettings() + { + FileInfo info = new FileInfo(_settingsFile); + if (!info.Exists || info.Length == 0) + return; + + try + { + XmlDocument doc = new XmlDocument(); + doc.Load(_settingsFile); + + foreach (XmlElement element in doc.DocumentElement["Settings"].ChildNodes) + { + if (element.Name != "Setting") + throw new ApplicationException("Unknown element in settings file: " + element.Name); + + if (!element.HasAttribute("name")) + throw new ApplicationException("Setting must have 'name' attribute"); + + if (!element.HasAttribute("value")) + throw new ApplicationException("Setting must have 'value' attribute"); + + SaveSetting(element.GetAttribute("name"), element.GetAttribute("value")); + } + } + catch (Exception ex) + { + throw new ApplicationException("Error loading settings file", ex); + } + } + + public void SaveSettings() + { + if (_writeable) + { + string dirPath = Path.GetDirectoryName(_settingsFile); + if (!Directory.Exists(dirPath)) + Directory.CreateDirectory(dirPath); + + XmlTextWriter writer = new XmlTextWriter(_settingsFile, System.Text.Encoding.UTF8); + writer.Formatting = Formatting.Indented; + + writer.WriteProcessingInstruction("xml", "version=\"1.0\""); + writer.WriteStartElement("NUnitSettings"); + writer.WriteStartElement("Settings"); + + List keys = new List(_settings.Keys); + keys.Sort(); + + foreach (string name in keys) + { + object val = GetSetting(name); + if (val != null) + { + writer.WriteStartElement("Setting"); + writer.WriteAttributeString("name", name); + writer.WriteAttributeString("value", + TypeDescriptor.GetConverter(val).ConvertToInvariantString(val)); + writer.WriteEndElement(); + } + } + + writer.WriteEndElement(); + writer.WriteEndElement(); + writer.Close(); + } + } + + #endregion + } +} diff --git a/src/nunit.engine/Internal/XmlHelper.cs b/src/nunit.engine/Internal/XmlHelper.cs new file mode 100644 index 00000000..d26f34e4 --- /dev/null +++ b/src/nunit.engine/Internal/XmlHelper.cs @@ -0,0 +1,134 @@ +// *********************************************************************** +// Copyright (c) 2011-2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System.Collections.Generic; +using System.Xml; + +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] + public sealed class ExtensionAttribute : Attribute { } +} + +namespace NUnit.Engine.Internal +{ + /// + /// XmlHelper provides static methods for basic XML operations. + /// + /// + /// This helper class is used in various NUnit modules. + /// Unused methods are currently commented out in those + /// modules that don't make use of them. + /// + public static class XmlHelper + { + /// + /// Creates a new top level element node. + /// + /// The element name. + /// A new XmlNode + public static XmlNode CreateTopLevelElement(string name) + { + XmlDocument doc = new XmlDocument(); + doc.LoadXml( "<" + name + "/>" ); + return doc.FirstChild; + } + + public static XmlNode CreateXmlNode(string xml) + { + XmlDocument doc = new XmlDocument(); + doc.LoadXml(xml); + return doc.FirstChild; + } + + /// + /// Adds an attribute with a specified name and value to an existing XmlNode. + /// + /// The node to which the attribute should be added. + /// The name of the attribute. + /// The value of the attribute. + public static void AddAttribute(this XmlNode node, string name, string value) + { + XmlAttribute attr = node.OwnerDocument.CreateAttribute(name); + attr.Value = value; + node.Attributes.Append(attr); + } + + /// + /// Adds a new element as a child of an existing XmlNode and returns it. + /// + /// The node to which the element should be added. + /// The element name. + /// The newly created child element + public static XmlNode AddElement(this XmlNode node, string name) + { + XmlNode childNode = node.OwnerDocument.CreateElement(name); + node.AppendChild(childNode); + return childNode; + } + + /// + /// Adds the a new element as a child of an existing node and returns it. + /// A CDataSection is added to the new element using the data provided. + /// + /// The node to which the element should be added. + /// The element name. + /// The data for the CDataSection. + /// + public static XmlNode AddElementWithCDataSection(this XmlNode node, string name, string data) + { + XmlNode childNode = node.AddElement(name); + childNode.AppendChild(node.OwnerDocument.CreateCDataSection(data)); + return childNode; + } + + #region Safe Attribute Access + + public static string GetAttribute(this XmlNode result, string name) + { + XmlAttribute attr = result.Attributes[name]; + + return attr == null ? null : attr.Value; + } + + public static int GetAttribute(this XmlNode result, string name, int defaultValue) + { + XmlAttribute attr = result.Attributes[name]; + + return attr == null + ? defaultValue + : int.Parse(attr.Value, System.Globalization.CultureInfo.InvariantCulture); + } + + public static double GetAttribute(this XmlNode result, string name, double defaultValue) + { + XmlAttribute attr = result.Attributes[name]; + + return attr == null + ? defaultValue + : double.Parse(attr.Value, System.Globalization.CultureInfo.InvariantCulture); + } + + #endregion + } +} diff --git a/src/nunit.engine/NUnitFrameworkDriver.cs b/src/nunit.engine/NUnitFrameworkDriver.cs new file mode 100644 index 00000000..668aa8e3 --- /dev/null +++ b/src/nunit.engine/NUnitFrameworkDriver.cs @@ -0,0 +1,157 @@ +// *********************************************************************** +// Copyright (c) 2009-2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Diagnostics; +using System.IO; +using System.Collections.Generic; +using System.Web.UI; +using System.Xml; +using NUnit.Engine.Internal; + +namespace NUnit.Engine +{ + /// + /// NUnitFrameworkDriver is used by the test-runner to load and run + /// tests using the NUnit framework assembly. + /// + public class BaseNUnitFrameworkDriver : IFrameworkDriver + { + private static readonly string CONTROLLER_TYPE = "NUnit.Framework.Api.FrameworkController"; + private static readonly string LOAD_ACTION = CONTROLLER_TYPE + "+LoadTestsAction"; + private static readonly string EXPLORE_ACTION = CONTROLLER_TYPE + "+ExploreTestsAction"; + private static readonly string COUNT_ACTION = CONTROLLER_TYPE + "+CountTestsAction"; + private static readonly string RUN_ACTION = CONTROLLER_TYPE + "+RunTestsAction"; + private static readonly string STOP_RUN_ACTION = CONTROLLER_TYPE + "+StopRunAction"; + + static ILogger log = InternalTrace.GetLogger("NUnitFrameworkDriver"); + + AppDomain _testDomain; + string _testAssemblyPath; + string _frameworkAssemblyName; + + object _frameworkController; + + public BaseNUnitFrameworkDriver(AppDomain testDomain, string frameworkAssemblyName, string testAssemblyPath, IDictionary settings) + { + _testDomain = testDomain; + _testAssemblyPath = testAssemblyPath; + _frameworkAssemblyName = frameworkAssemblyName; + _frameworkController = CreateObject(CONTROLLER_TYPE, testAssemblyPath, (System.Collections.IDictionary)settings); + } + + /// + /// Loads the tests in an assembly. + /// + /// An Xml string representing the loaded test + public string Load() + { + CallbackHandler handler = new CallbackHandler(); + + log.Info("Loading {0} - see separate log file", Path.GetFileName(_testAssemblyPath)); + CreateObject(LOAD_ACTION, _frameworkController, handler); + + return handler.Result; + } + + public int CountTestCases(TestFilter filter) + { + CallbackHandler handler = new CallbackHandler(); + + CreateObject(COUNT_ACTION, _frameworkController, filter.Text, handler); + + return int.Parse(handler.Result); + } + + /// + /// Executes the tests in an assembly. + /// + /// An ITestEventHandler that receives progress notices + /// A filter that controls which tests are executed + /// An Xml string representing the result + public string Run(ITestEventListener listener, TestFilter filter) + { + CallbackHandler handler = new RunTestsCallbackHandler(listener); + + log.Info("Running {0} - see separate log file", Path.GetFileName(_testAssemblyPath)); + CreateObject(RUN_ACTION, _frameworkController, filter.Text, handler); + + return handler.Result; + } + + /// + /// Cancel the ongoing test run. If no test is running, the call is ignored. + /// + /// If true, cancel any ongoing test threads, otherwise wait for them to complete. + public void StopRun(bool force) + { + CreateObject(STOP_RUN_ACTION, _frameworkController, force); + } + + /// + /// Returns information about the tests in an assembly. + /// + /// A filter indicating which tests to include + /// An Xml string representing the tests + public string Explore(TestFilter filter) + { + CallbackHandler handler = new CallbackHandler(); + + log.Info("Exploring {0} - see separate log file", Path.GetFileName(_testAssemblyPath)); + CreateObject(EXPLORE_ACTION, _frameworkController, filter.Text, handler); + + return handler.Result; + } + + #region Helper Methods + + private object CreateObject(string typeName, params object[] args) + { + return _testDomain.CreateInstanceAndUnwrap( + _frameworkAssemblyName, typeName, false, 0, +#if !NET_4_0 + null, args, null, null, null ); +#else + null, args, null, null ); +#endif + } + + #endregion + } + + public class NUnitFrameworkDriver : BaseNUnitFrameworkDriver + { + public const string FrameworkName = "nunit.framework"; + + public NUnitFrameworkDriver(AppDomain testDomain, string testAssemblyPath, IDictionary settings) + : base(testDomain, FrameworkName, testAssemblyPath, settings) { } + } + + public class NUnitLiteFrameworkDriver : BaseNUnitFrameworkDriver + { + public const string FrameworkName = "nunitlite"; + + public NUnitLiteFrameworkDriver(AppDomain testDomain, string testAssemblyPath, IDictionary settings) + : base(testDomain, FrameworkName, testAssemblyPath, settings) { } + } +} diff --git a/src/nunit.engine/RunTestsCallbackHandler.cs b/src/nunit.engine/RunTestsCallbackHandler.cs new file mode 100644 index 00000000..50837134 --- /dev/null +++ b/src/nunit.engine/RunTestsCallbackHandler.cs @@ -0,0 +1,55 @@ +// *********************************************************************** +// Copyright (c) 2010-2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Diagnostics; +using System.IO; +using System.Xml; + +namespace NUnit.Engine +{ + public class RunTestsCallbackHandler : CallbackHandler + { + private ITestEventListener listener; + + public RunTestsCallbackHandler(ITestEventListener listener) + { + // TODO: Move this substitution into the framework? + this.listener = listener ?? new NullListener(); + } + + public override void ReportProgress(string state) + { + listener.OnTestEvent(state); + } + + #region Nested NullListener class + class NullListener : ITestEventListener + { + public void OnTestEvent(string report) + { + } + } + #endregion + } +} diff --git a/src/nunit.engine/RunnerSettings.cs b/src/nunit.engine/RunnerSettings.cs new file mode 100644 index 00000000..2e3dca8b --- /dev/null +++ b/src/nunit.engine/RunnerSettings.cs @@ -0,0 +1,73 @@ +// *********************************************************************** +// Copyright (c) 2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +namespace NUnit.Engine +{ + /// + /// RunnerSettings is a static class containing constant values that + /// are used as keys in a TestPackage and are acted on by the engine. + /// + public static class RunnerSettings + { + /// + /// The config to use in loading a project + /// + public const string ActiveConfig = "ActiveConfig"; + + /// + /// If true, the engine should determine the private bin + /// path by examining the paths to all the tests. + /// + public const string AutoBinPath = "AutoBinPath"; + + /// + /// The ApplicationBase to use in loading the tests. + /// + public const string BasePath = "BasePath"; + + /// + /// The config file to use in running the tests + /// + public const string ConfigurationFile = "ConfigurationFile"; + + /// + /// Indicates how to load tests across AppDomains + /// + public const string DomainUsage = "DomainUsage"; + + /// + /// The private binpath used to locate assemblies + /// + public const string PrivateBinPath = "PrivateBinPath"; + + /// + /// Indicates how to allocate assemblies to processes + /// + public const string ProcessModel = "ProcessModel"; + + /// + /// Indicates the desired runtime to use for the tests. + /// + public const string RuntimeFramework = "RuntimeFramework"; + } +} diff --git a/src/nunit.engine/Runners/AbstractTestRunner.cs b/src/nunit.engine/Runners/AbstractTestRunner.cs new file mode 100644 index 00000000..5b9a1c10 --- /dev/null +++ b/src/nunit.engine/Runners/AbstractTestRunner.cs @@ -0,0 +1,266 @@ +// *********************************************************************** +// Copyright (c) 2011-2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Xml; + +namespace NUnit.Engine.Runners +{ + using Internal; + + /// + /// AbstractTestRunner is the base class for all runners + /// within the NUnit Engine. It implements the ITestRunner + /// interface, which is used by clients of the engine and + /// uses a Template pattern with abstract methods overridden + /// by the derived runners. + /// + public abstract class AbstractTestRunner : ITestEngineRunner + { + protected const string TEST_RUN_ELEMENT = "test-run"; + + public AbstractTestRunner(ServiceContext services, TestPackage package) + { + this.Services = services; + this.TestPackage = package; + } + + #region Properties + + /// + /// Our Service Context + /// + protected ServiceContext Services { get; private set; } + + /// + /// The TestPackage for which this is the runner + /// + protected TestPackage TestPackage { get; set; } + + /// + /// The result of the last call to LoadPackage + /// + protected TestEngineResult LoadResult { get; set; } + + /// + /// Gets an indicator of whether the package has been loaded. + /// + protected bool IsPackageLoaded + { + get { return LoadResult != null; } + } + + #endregion + + #region Abstract and Virtual Template Methods + + /// + /// Loads the TestPackage for exploration or execution. + /// + /// A TestEngineResult. + protected abstract TestEngineResult LoadPackage(); + + /// + /// Reload the currently loaded test package. Overridden + /// in derived classes to take any additional action. + /// + /// A TestEngineResult. + protected virtual TestEngineResult ReloadPackage() + { + return LoadPackage(); + } + + /// + /// Unload any loaded TestPackage. Overridden in + /// derived classes to take any necessary action. + /// + public virtual void UnloadPackage() + { + } + + /// + /// Explores a previously loaded TestPackage and returns information + /// about the tests found. + /// + /// The TestFilter to be used to select tests + /// A TestEngineResult. + protected abstract TestEngineResult ExploreTests(TestFilter filter); + + /// + /// Count the test cases that would be run under the specified filter. + /// + /// A TestFilter + /// The count of test cases. + protected abstract int CountTests(TestFilter filter); + + /// + /// Run the tests in the loaded TestPackage. + /// + /// An ITestEventHandler to receive events + /// A TestFilter used to select tests + /// A TestEngineResult giving the result of the test execution + protected abstract TestEngineResult RunTests(ITestEventListener listener, TestFilter filter); + + /// + /// Start a run of the tests in the loaded TestPackage, returning immediately. + /// The tests are run asynchronously and the listener interface is notified + /// as it progresses. + /// + /// An ITestEventHandler to receive events + /// A TestFilter used to select tests + protected virtual void RunTestsAsynchronously(ITestEventListener listener, TestFilter filter) + { + using (var worker = new BackgroundWorker()) + { + worker.DoWork += (s, ea) => RunTests(listener, filter); + worker.RunWorkerAsync(); + } + } + + /// + /// Cancel the ongoing test run. If no test is running, the call is ignored. + /// + /// If true, cancel any ongoing test threads, otherwise wait for them to complete. + public abstract void StopRun(bool force); + + #endregion + + #region ITestEngineRunner Members + + /// + /// Explores the TestPackage and returns information about + /// the tests found. Loads the package if not done previously. + /// + /// The TestFilter to be used to select tests + /// A TestEngineResult. + public TestEngineResult Explore(TestFilter filter) + { + EnsurePackageIsLoaded(); + + return ExploreTests(filter); + } + + /// + /// Loads the TestPackage for exploration or execution, saving the result. + /// + /// A TestEngineResult. + public TestEngineResult Load() + { + return LoadResult = LoadPackage(); + } + + /// + /// Reload the currently loaded test package, saving the result. + /// + /// A TestEngineResult. + /// If no package has been loaded + public TestEngineResult Reload() + { + if (this.TestPackage == null) + throw new InvalidOperationException("MasterTestRunner: Reload called before Load"); + + return LoadResult = ReloadPackage(); + } + + /// + /// Unload any loaded TestPackage. + /// + public void Unload() + { + UnloadPackage(); + LoadResult = null; + } + + /// + /// Count the test cases that would be run under the specified + /// filter, loading the TestPackage if it is not already loaded. + /// + /// A TestFilter + /// The count of test cases. + public int CountTestCases(TestFilter filter) + { + EnsurePackageIsLoaded(); + + return CountTests(filter); + } + + /// + /// Run the tests in the TestPackage, loading the package + /// if this has not already been done. + /// + /// An ITestEventHandler to receive events + /// A TestFilter used to select tests + /// A TestEngineResult giving the result of the test execution + public TestEngineResult Run(ITestEventListener listener, TestFilter filter) + { + EnsurePackageIsLoaded(); + + return RunTests(listener, filter); + } + + /// + /// Start a run of the tests in the TestPackage. The tests are run + /// asynchronously and the listener interface is notified as it progresses. + /// Loads the TestPackage if not already loaded. + /// + /// An ITestEventHandler to receive events + /// A TestFilter used to select tests + public void StartRun(ITestEventListener listener, TestFilter filter) + { + EnsurePackageIsLoaded(); + + RunTestsAsynchronously(listener, filter); + } + + #endregion + + #region IDisposable Members + + public virtual void Dispose() + { + this.Unload(); + } + + #endregion + + #region Helper Methods + + protected bool IsProjectPackage(TestPackage package) + { + return package != null + && package.FullName != null + && package.FullName != string.Empty + && Services.ProjectService.IsProjectFile(package.FullName); + } + + private void EnsurePackageIsLoaded() + { + if (!IsPackageLoaded) + LoadResult = LoadPackage(); + } + + #endregion + } +} diff --git a/src/nunit.engine/Runners/AggregatingTestRunner.cs b/src/nunit.engine/Runners/AggregatingTestRunner.cs new file mode 100644 index 00000000..d4758b4c --- /dev/null +++ b/src/nunit.engine/Runners/AggregatingTestRunner.cs @@ -0,0 +1,163 @@ +// *********************************************************************** +// Copyright (c) 2011-2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml; +using NUnit.Engine.Internal; + +namespace NUnit.Engine.Runners +{ + /// + /// AggregatingTestRunner runs tests using multiple + /// subordinate runners and combines the results. + /// + public class AggregatingTestRunner : AbstractTestRunner + { + // The runners created by the derived class will (at least at the time + // of writing this comment) be either TestDomainRunners or ProcessRunners. + private List _runners = new List(); + + public AggregatingTestRunner(ServiceContext services, TestPackage package) : base(services, package) { } + + #region AbstractTestRunner Overrides + + /// + /// Explore a TestPackage and return information about + /// the tests found. + /// + /// The TestPackage to be explored + /// A TestEngineResult. + protected override TestEngineResult ExploreTests(TestFilter filter) + { + List results = new List(); + + foreach (ITestEngineRunner runner in _runners) + results.Add(runner.Explore(filter)); + + TestEngineResult result = ResultHelper.Merge(results); + + return IsProjectPackage(this.TestPackage) + ? result.MakePackageResult(TestPackage.Name, TestPackage.FullName) + : result; + } + + /// + /// Load a TestPackage for possible execution + /// + /// The TestPackage to be loaded + /// A TestEngineResult. + protected override TestEngineResult LoadPackage() + { + List packages = new List(); + + foreach (string testFile in TestPackage.TestFiles) + { + TestPackage subPackage = new TestPackage(testFile); + if (Services.ProjectService.IsProjectFile(testFile)) + Services.ProjectService.ExpandProjectPackage(subPackage); + foreach (string key in TestPackage.Settings.Keys) + subPackage.Settings[key] = TestPackage.Settings[key]; + packages.Add(subPackage); + } + + List results = new List(); + + foreach (TestPackage subPackage in packages) + { + var runner = CreateRunner(subPackage); + _runners.Add(runner); + results.Add(runner.Load()); + } + + return ResultHelper.Merge(results); + } + + /// + /// Unload any loaded TestPackages and clear the + /// list of runners. + /// + public override void UnloadPackage() + { + foreach (ITestEngineRunner runner in _runners) + runner.Unload(); + + _runners.Clear(); + } + + /// + /// Count the test cases that would be run under + /// the specified filter. + /// + /// A TestFilter + /// The count of test cases + protected override int CountTests(TestFilter filter) + { + int count = 0; + + foreach (ITestEngineRunner runner in _runners) + count += runner.CountTestCases(filter); + + return count; + } + + /// + /// Run the tests in a loaded TestPackage + /// + /// A TestFilter used to select tests + /// + /// A TestEngineResult giving the result of the test execution. + /// + protected override TestEngineResult RunTests(ITestEventListener listener, TestFilter filter) + { + List results = new List(); + + foreach (ITestEngineRunner runner in _runners) + results.Add(runner.Run(listener, filter)); + + TestEngineResult result = ResultHelper.Merge(results); + + return IsProjectPackage(this.TestPackage) + ? result.MakePackageResult(TestPackage.Name, TestPackage.FullName) + : result; + } + + /// + /// Cancel the ongoing test run. If no test is running, the call is ignored. + /// + /// If true, cancel any ongoing test threads, otherwise wait for them to complete. + public override void StopRun(bool force) + { + foreach (var runner in _runners) + runner.StopRun(force); + } + + #endregion + + protected virtual ITestEngineRunner CreateRunner(TestPackage package) + { + return Services.TestRunnerFactory.MakeTestRunner(package); + } + } +} diff --git a/src/nunit.engine/Runners/DirectTestRunner.cs b/src/nunit.engine/Runners/DirectTestRunner.cs new file mode 100644 index 00000000..d80296c9 --- /dev/null +++ b/src/nunit.engine/Runners/DirectTestRunner.cs @@ -0,0 +1,138 @@ +// *********************************************************************** +// Copyright (c) 2011-2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml; +using NUnit.Engine.Internal; + +namespace NUnit.Engine.Runners +{ + /// + /// DirectTestRunner is the abstract base for runners + /// that deal directly with a framework driver. + /// + public abstract class DirectTestRunner : AbstractTestRunner + { + private List _drivers = new List(); + + public DirectTestRunner(ServiceContext services, TestPackage package) : base(services, package) { } + + #region Properties + + protected AppDomain TestDomain { get; set; } + + #endregion + + #region AbstractTestRunner Overrides + + /// + /// Explore a TestPackage and return information about + /// the tests found. + /// + /// The TestPackage to be explored + /// A TestEngineResult. + protected override TestEngineResult ExploreTests(TestFilter filter) + { + TestEngineResult result = new TestEngineResult(); + + foreach (IFrameworkDriver driver in _drivers) + result.Add(driver.Explore(filter)); + + return IsProjectPackage(this.TestPackage) + ? result.MakePackageResult(TestPackage.Name, TestPackage.FullName) + : result; + } + + /// + /// Load a TestPackage for exploration or execution + /// + /// The TestPackage to be loaded + /// A TestEngineResult. + protected override TestEngineResult LoadPackage() + { + TestEngineResult result = new TestEngineResult(); + + foreach (string testFile in TestPackage.TestFiles) + { + IFrameworkDriver driver = Services.DriverFactory.GetDriver(TestDomain, testFile, TestPackage.Settings); + result.Add(driver.Load()); + _drivers.Add(driver); + } + + return IsProjectPackage(TestPackage) + ? result.MakePackageResult(TestPackage.Name, TestPackage.FullName) + : result; + } + + /// + /// Count the test cases that would be run under + /// the specified filter. + /// + /// A TestFilter + /// The count of test cases + protected override int CountTests(TestFilter filter) + { + int count = 0; + + foreach (IFrameworkDriver driver in _drivers) + count += driver.CountTestCases(filter); + + return count; + } + + /// + /// Run the tests in a loaded TestPackage + /// + /// A TestFilter used to select tests + /// + /// A TestEngineResult giving the result of the test execution. The + /// top-level node of the result is <direct-runner> and wraps + /// all the <test-assembly> elements returned by the drivers. + /// + protected override TestEngineResult RunTests(ITestEventListener listener, TestFilter filter) + { + TestEngineResult result = new TestEngineResult(); + + foreach (IFrameworkDriver driver in _drivers) + result.Add(driver.Run(listener, filter)); + + return IsProjectPackage(this.TestPackage) + ? result.MakePackageResult(TestPackage.Name, TestPackage.FullName) + : result; + } + + /// + /// Cancel the ongoing test run. If no test is running, the call is ignored. + /// + /// If true, cancel any ongoing test threads, otherwise wait for them to complete. + public override void StopRun(bool force) + { + foreach(var driver in _drivers) + driver.StopRun(force); + } + + #endregion + } +} diff --git a/src/nunit.engine/Runners/LocalTestRunner.cs b/src/nunit.engine/Runners/LocalTestRunner.cs new file mode 100644 index 00000000..19203a81 --- /dev/null +++ b/src/nunit.engine/Runners/LocalTestRunner.cs @@ -0,0 +1,38 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; + +namespace NUnit.Engine.Runners +{ + /// + /// LocalTestRunner runs tests in the current AppDomain. + /// + public class LocalTestRunner : DirectTestRunner + { + public LocalTestRunner(ServiceContext services, TestPackage package) : base(services, package) + { + this.TestDomain = AppDomain.CurrentDomain; + } + } +} diff --git a/src/nunit.engine/Runners/MasterTestRunner.cs b/src/nunit.engine/Runners/MasterTestRunner.cs new file mode 100644 index 00000000..f3cadfad --- /dev/null +++ b/src/nunit.engine/Runners/MasterTestRunner.cs @@ -0,0 +1,261 @@ +// *********************************************************************** +// Copyright (c) 2011-2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Reflection; +using System.Xml; +using NUnit.Engine.Internal; + +namespace NUnit.Engine.Runners +{ + public class MasterTestRunner : AbstractTestRunner, ITestRunner + { + private ITestEngineRunner _realRunner; + + // Count of assemblies and projects passed in package + private int _assemblyCount; + private int _projectCount; + + public MasterTestRunner(ServiceContext services, TestPackage package) : base(services, package) { } + + public bool IsTestRunning { get; private set; } + + #region AbstractTestRunner Overrides + + /// + /// Explore a loaded TestPackage and return information about + /// the tests found. + /// + /// The TestPackage to be explored + /// A TestEngineResult. + protected override TestEngineResult ExploreTests(TestFilter filter) + { + return _realRunner.Explore(filter).Aggregate(TEST_RUN_ELEMENT, TestPackage.Name, TestPackage.FullName); + } + + /// + /// Load a TestPackage for possible execution + /// + /// The TestPackage to be loaded + /// A TestEngineResult. + protected override TestEngineResult LoadPackage() + { + PerformPackageSetup(TestPackage); + return _realRunner.Load().Aggregate(TEST_RUN_ELEMENT, TestPackage.Name, TestPackage.FullName); + } + + /// + /// Unload any loaded TestPackage. + /// + public override void UnloadPackage() + { + if (_realRunner != null) + _realRunner.Unload(); + } + + /// + /// Count the test cases that would be run under + /// the specified filter. + /// + /// A TestFilter + /// The count of test cases + protected override int CountTests(TestFilter filter) + { + return _realRunner.CountTestCases(filter); + } + + /// + /// Run the tests in the loaded TestPackage and return a test result. The tests + /// are run synchronously and the listener interface is notified as it progresses. + /// + /// An ITestEventHandler to receive events + /// A TestFilter used to select tests + /// A TestEngineResult giving the result of the test execution + protected override TestEngineResult RunTests(ITestEventListener listener, TestFilter filter) + { + IsTestRunning = true; + + if (listener != null) + listener.OnTestEvent(string.Format("", CountTestCases(filter))); + + DateTime startTime = DateTime.UtcNow; + long startTicks = Stopwatch.GetTimestamp(); + + TestEngineResult result = _realRunner.Run(listener, filter).Aggregate("test-run", TestPackage.Name, TestPackage.FullName); + + result.Xml.InsertEnvironmentElement(); + + double duration = (double)(Stopwatch.GetTimestamp() - startTicks) / Stopwatch.Frequency; + result.Xml.AddAttribute("start-time", XmlConvert.ToString(startTime, "u")); + result.Xml.AddAttribute("end-time", XmlConvert.ToString(DateTime.UtcNow, "u")); + result.Xml.AddAttribute("duration", duration.ToString("0.000000", NumberFormatInfo.InvariantInfo)); + + IsTestRunning = false; + + if (listener != null) + listener.OnTestEvent(result.Xml.OuterXml); + + return result; + } + + /// + /// Cancel the ongoing test run. If no test is running, the call is ignored. + /// + /// If true, cancel any ongoing test threads, otherwise wait for them to complete. + public override void StopRun(bool force) + { + _realRunner.StopRun(force); + } + + #endregion + + #region ITestRunner Explicit Implementation + + // NOTE: Only those methods which differ from those in + // ITestEngineRunner have an explicit implementation. + // Methods that are the same for both interfaces + // use the class methods. + + /// + /// Load a TestPackage for possible execution. The + /// explicit implemenation returns an ITestEngineResult + /// for consumption by clients. + /// + /// The TestPackage to be loaded + /// An XmlNode representing the loaded assembly. + XmlNode ITestRunner.Load() + { + return this.Load().Xml; + } + + /// + /// Reload the currently loaded test jpackage. + /// + /// An XmlNode representing the loaded package + /// If no package has been loaded + XmlNode ITestRunner.Reload() + { + return this.Reload().Xml; + } + + /// + /// Run the tests in a loaded TestPackage. The explicit + /// implementation returns an ITestEngineResult for use + /// by external clients. + /// + /// An ITestEventHandler to receive events + /// A TestFilter used to select tests + /// An XmlNode giving the result of the test execution + XmlNode ITestRunner.Run(ITestEventListener listener, TestFilter filter) + { + return this.Run(listener, filter).Xml; + } + + /// + /// + /// + /// + ITestRun ITestRunner.RunAsync(ITestEventListener listener, TestFilter filter) + { + var testRun = new TestRun(this); + testRun.Start(listener, filter); + return testRun; + } + + /// + /// Explore a loaded TestPackage and return information about + /// the tests found. + /// + /// The TestPackage to be explored + /// An XmlNode representing the tests found. + XmlNode ITestRunner.Explore(TestFilter filter) + { + return this.Explore(filter).Xml; + } + + #endregion + + #region IDisposable Members + + /// + /// Dispose of this object. + /// + public override void Dispose() + { + if (_realRunner != null) + _realRunner.Dispose(); + } + + #endregion + + #region HelperMethods + + private void PerformPackageSetup(TestPackage package) + { + this.TestPackage = package; + + // Expand projects, updating the count of projects and assemblies + ExpandProjects(); + + // If there is more than one project or a mix of assemblies and + // projects, AggregatingTestRunner will call MakeTestRunner for + // each project or assembly. + _realRunner = _projectCount > 1 || _projectCount > 0 && _assemblyCount > 0 + ? new AggregatingTestRunner(Services, package) + : Services.TestRunnerFactory.MakeTestRunner(package); + } + + private void ExpandProjects() + { + if (TestPackage.TestFiles.Length > 0) + { + foreach (string testFile in TestPackage.TestFiles) + { + TestPackage subPackage = new TestPackage(testFile); + if (Services.ProjectService.IsProjectFile(testFile)) + { + Services.ProjectService.ExpandProjectPackage(subPackage); + _projectCount++; + } + else + _assemblyCount++; + } + } + else + { + if (Services.ProjectService.IsProjectFile(TestPackage.FullName)) + { + Services.ProjectService.ExpandProjectPackage(TestPackage); + _projectCount++; + } + else + _assemblyCount++; + } + } + + #endregion + } +} diff --git a/src/nunit.engine/Runners/MultipleTestDomainRunner.cs b/src/nunit.engine/Runners/MultipleTestDomainRunner.cs new file mode 100644 index 00000000..c7efc827 --- /dev/null +++ b/src/nunit.engine/Runners/MultipleTestDomainRunner.cs @@ -0,0 +1,48 @@ +// *********************************************************************** +// Copyright (c) 2011-2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtainingn +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml; +using NUnit.Engine.Internal; + +namespace NUnit.Engine.Runners +{ + /// + /// MultipleTestDomainRunner runs tests using separate + /// AppDomains for each assembly. + /// + public class MultipleTestDomainRunner : AggregatingTestRunner + { + public MultipleTestDomainRunner(ServiceContext services, TestPackage package) : base(services, package) { } + + #region AggregatingTestRunner Overrides + + protected override ITestEngineRunner CreateRunner(TestPackage package) + { + return new TestDomainRunner(this.Services, package); + } + #endregion + } +} diff --git a/src/nunit.engine/Runners/MultipleTestProcessRunner.cs b/src/nunit.engine/Runners/MultipleTestProcessRunner.cs new file mode 100644 index 00000000..f62445c6 --- /dev/null +++ b/src/nunit.engine/Runners/MultipleTestProcessRunner.cs @@ -0,0 +1,48 @@ +// *********************************************************************** +// Copyright (c) 2011-2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtainingn +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml; +using NUnit.Engine.Internal; + +namespace NUnit.Engine.Runners +{ + /// + /// MultipleTestDomainRunner runs tests using separate + /// AppDomains for each assembly. + /// + public class MultipleTestProcessRunner : AggregatingTestRunner + { + public MultipleTestProcessRunner(ServiceContext services, TestPackage package) : base(services, package) { } + + #region AggregatingTestRunner Overrides + + protected override ITestEngineRunner CreateRunner(TestPackage package) + { + return new ProcessRunner(this.Services, package); + } + #endregion + } +} diff --git a/src/nunit.engine/Runners/ProcessRunner.cs b/src/nunit.engine/Runners/ProcessRunner.cs new file mode 100644 index 00000000..6a798cd7 --- /dev/null +++ b/src/nunit.engine/Runners/ProcessRunner.cs @@ -0,0 +1,184 @@ +// *********************************************************************** +// Copyright (c) 2011-2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.Remoting; +using System.Runtime.Remoting.Proxies; +using System.Runtime.Remoting.Services; +using System.Runtime.Remoting.Channels; +using System.Runtime.Remoting.Channels.Tcp; +using System.Xml; +using NUnit.Engine.Internal; + +namespace NUnit.Engine.Runners +{ + /// + /// Summary description for ProcessRunner. + /// + public class ProcessRunner : AbstractTestRunner + { + static Logger log = InternalTrace.GetLogger(typeof(ProcessRunner)); + + private ITestAgent _agent; + private ITestEngineRunner _remoteRunner; + + public ProcessRunner(ServiceContext services, TestPackage package) : base(services, package) { } + + #region Properties + + public RuntimeFramework RuntimeFramework { get; private set; } + + #endregion + + #region AbstractTestRunner Overrides + + /// + /// Explore a TestPackage and return information about + /// the tests found. + /// + /// The TestPackage to be explored + /// A TestEngineResult. + protected override TestEngineResult ExploreTests(TestFilter filter) + { + TestEngineResult result = this._remoteRunner.Explore(filter); + return result as TestEngineResult; // TODO: Remove need for this cast + } + + /// + /// Load a TestPackage for possible execution + /// + /// The TestPackage to be loaded + /// A TestEngineResult. + protected override TestEngineResult LoadPackage() + { + log.Info("Loading " + TestPackage.Name); + Unload(); + + string frameworkSetting = TestPackage.GetSetting(RunnerSettings.RuntimeFramework, ""); + this.RuntimeFramework = frameworkSetting != "" + ? RuntimeFramework.Parse(frameworkSetting) + : RuntimeFramework.CurrentFramework; + + bool enableDebug = TestPackage.GetSetting("AgentDebug", false); + bool verbose = TestPackage.GetSetting("Verbose", false); + string agentArgs = string.Empty; + if (enableDebug) agentArgs += " --pause"; + if (verbose) agentArgs += " --verbose"; + + try + { + CreateAgentAndRunner(enableDebug, agentArgs); + + return _remoteRunner.Load(); + } + catch(Exception) + { + // TODO: Check if this is really needed + // Clean up if the load failed + Unload(); + throw; + } + } + + /// + /// Unload any loaded TestPackage and clear + /// the reference to the remote runner. + /// + public override void UnloadPackage() + { + if (_remoteRunner != null) + { + log.Info("Unloading remote runner"); + _remoteRunner.Unload(); + _remoteRunner = null; + } + } + + /// + /// Count the test cases that would be run under + /// the specified filter. + /// + /// A TestFilter + /// The count of test cases + protected override int CountTests(TestFilter filter) + { + return _remoteRunner.CountTestCases(filter); + } + + /// + /// Run the tests in a loaded TestPackage + /// + /// A TestFilter used to select tests + /// A TestResult giving the result of the test execution + protected override TestEngineResult RunTests(ITestEventListener listener, TestFilter filter) + { + return (TestEngineResult)_remoteRunner.Run(listener, filter); + } + + /// + /// Cancel the ongoing test run. If no test is running, the call is ignored. + /// + /// If true, cancel any ongoing test threads, otherwise wait for them to complete. + public override void StopRun(bool force) + { + _remoteRunner.StopRun(force); + } + + public override void Dispose() + { + if (_agent != null) + { + log.Info("Stopping remote agent"); + _agent.Stop(); + _agent = null; + } + } + + #endregion + + #region Helper Methods + + private void CreateAgentAndRunner(bool enableDebug, string agentArgs) + { + if (_agent == null) + { + _agent = Services.TestAgency.GetAgent( + RuntimeFramework, + 30000, + enableDebug, + agentArgs); + + if (_agent == null) + throw new Exception("Unable to acquire remote process agent"); + } + + if (_remoteRunner == null) + _remoteRunner = _agent.CreateRunner(TestPackage); + } + + #endregion + } +} diff --git a/src/nunit.engine/Runners/TestDomainRunner.cs b/src/nunit.engine/Runners/TestDomainRunner.cs new file mode 100644 index 00000000..44ed9c0b --- /dev/null +++ b/src/nunit.engine/Runners/TestDomainRunner.cs @@ -0,0 +1,59 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using NUnit.Engine.Services; + +namespace NUnit.Engine.Runners +{ + /// + /// TestDomainRunner loads and runs tests in a separate + /// domain whose lifetime it controls. + /// + public class TestDomainRunner : DirectTestRunner + { + public TestDomainRunner(ServiceContext services, TestPackage package) : base(services, package) { } + + #region DirectTestRunner Overrides + + protected override TestEngineResult LoadPackage() + { + this.TestDomain = Services.DomainManager.CreateDomain(TestPackage); + + return base.LoadPackage(); + } + + /// + /// Unload any loaded TestPackage as well as the AppDomain. + /// + public override void UnloadPackage() + { + if (this.TestDomain != null) + { + Services.DomainManager.Unload(this.TestDomain); + this.TestDomain = null; + } + } + + #endregion + } +} diff --git a/src/nunit.engine/RuntimeFramework.cs b/src/nunit.engine/RuntimeFramework.cs new file mode 100644 index 00000000..54626a3a --- /dev/null +++ b/src/nunit.engine/RuntimeFramework.cs @@ -0,0 +1,618 @@ +// *********************************************************************** +// Copyright (c) 2007 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using Microsoft.Win32; + +namespace NUnit.Engine +{ + /// + /// Enumeration identifying a common language + /// runtime implementation. + /// + public enum RuntimeType + { + /// Any supported runtime framework + Any, + /// Microsoft .NET Framework + Net, + /// Microsoft .NET Compact Framework + NetCF, + /// Microsoft Shared Source CLI + SSCLI, + /// Mono + Mono, + /// Silverlight + Silverlight, + /// MonoTouch + MonoTouch + } + + /// + /// RuntimeFramework represents a particular version + /// of a common language runtime implementation. + /// + [Serializable] + public sealed class RuntimeFramework + { + #region Static and Instance Fields + + /// + /// DefaultVersion is an empty Version, used to indicate that + /// NUnit should select the CLR version to use for the test. + /// + public static readonly Version DefaultVersion = new Version(0, 0); + + private static RuntimeFramework currentFramework; + private static RuntimeFramework[] availableFrameworks; + + #endregion + + #region Constructor + + /// + /// Construct from a runtime type and version. If the version has + /// two parts, it is taken as a framework version. If it has three + /// or more, it is taken as a CLR version. In either case, the other + /// version is deduced based on the runtime type and provided version. + /// + /// The runtime type of the framework + /// The version of the framework + public RuntimeFramework(RuntimeType runtime, Version version) + { + this.Runtime = runtime; + + if (version.Build < 0) + InitFromFrameworkVersion(version); + else + InitFromClrVersion(version); + + this.DisplayName = GetDefaultDisplayName(runtime, version); + } + + private void InitFromFrameworkVersion(Version version) + { + this.FrameworkVersion = this.ClrVersion = version; + + if (version.Major > 0) // 0 means any version + switch (Runtime) + { + case RuntimeType.Net: + case RuntimeType.Mono: + case RuntimeType.Any: + switch (version.Major) + { + case 1: + switch (version.Minor) + { + case 0: + this.ClrVersion = Runtime == RuntimeType.Mono + ? new Version(1, 1, 4322) + : new Version(1, 0, 3705); + break; + case 1: + if (Runtime == RuntimeType.Mono) + this.FrameworkVersion = new Version(1, 0); + this.ClrVersion = new Version(1, 1, 4322); + break; + default: + ThrowInvalidFrameworkVersion(version); + break; + } + break; + case 2: + case 3: + this.ClrVersion = new Version(2, 0, 50727); + break; + case 4: + this.ClrVersion = new Version(4, 0, 30319); + break; + default: + ThrowInvalidFrameworkVersion(version); + break; + } + break; + + case RuntimeType.Silverlight: + this.ClrVersion = version.Major >= 4 + ? new Version(4, 0, 60310) + : new Version(2, 0, 50727); + break; + } + } + + private static void ThrowInvalidFrameworkVersion(Version version) + { + throw new ArgumentException("Unknown framework version " + version.ToString(), "version"); + } + + private void InitFromClrVersion(Version version) + { + this.FrameworkVersion = new Version(version.Major, version.Minor); + this.ClrVersion = version; + if (Runtime == RuntimeType.Mono && version.Major == 1) + this.FrameworkVersion = new Version(1, 0); + } + + #endregion + + #region Properties + /// + /// Static method to return a RuntimeFramework object + /// for the framework that is currently in use. + /// + public static RuntimeFramework CurrentFramework + { + get + { + if (currentFramework == null) + { + Type monoRuntimeType = Type.GetType("Mono.Runtime", false); + bool isMono = monoRuntimeType != null; + + RuntimeType runtime = isMono + ? RuntimeType.Mono + : Environment.OSVersion.Platform == PlatformID.WinCE + ? RuntimeType.NetCF + : RuntimeType.Net; + + int major = Environment.Version.Major; + int minor = Environment.Version.Minor; + + if (isMono) + { + switch (major) + { + case 1: + minor = 0; + break; + case 2: + major = 3; + minor = 5; + break; + } + } + else /* It's windows */ + if (major == 2) + { + RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\.NETFramework"); + if (key != null) + { + string installRoot = key.GetValue("InstallRoot") as string; + if (installRoot != null) + { + if (Directory.Exists(Path.Combine(installRoot, "v3.5"))) + { + major = 3; + minor = 5; + } + else if (Directory.Exists(Path.Combine(installRoot, "v3.0"))) + { + major = 3; + minor = 0; + } + } + } + } + else if (major == 4 && Type.GetType("System.Reflection.AssemblyMetadataAttribute") != null) + { + minor = 5; + } + + currentFramework = new RuntimeFramework(runtime, new Version(major, minor)); + currentFramework.ClrVersion = Environment.Version; + + if (isMono) + { + MethodInfo getDisplayNameMethod = monoRuntimeType.GetMethod( + "GetDisplayName", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.ExactBinding); + if (getDisplayNameMethod != null) + currentFramework.DisplayName = (string)getDisplayNameMethod.Invoke(null, new object[0]); + } + } + + return currentFramework; + } + } + + /// + /// Gets an array of all available frameworks + /// + // TODO: Special handling for netcf + public static RuntimeFramework[] AvailableFrameworks + { + get + { + if (availableFrameworks == null) + { + List frameworks = new List(); + + AppendDotNetFrameworks(frameworks); + AppendDefaultMonoFramework(frameworks); + // NYI + //AppendMonoFrameworks(frameworks); + + availableFrameworks = frameworks.ToArray(); + } + + return availableFrameworks; + } + } + + /// + /// Returns true if the current RuntimeFramework is available. + /// In the current implementation, only Mono and Microsoft .NET + /// are supported. + /// + /// True if it's available, false if not + public bool IsAvailable + { + get + { + foreach (RuntimeFramework framework in AvailableFrameworks) + if (this.Supports(framework)) + return true; + + return false; + } + } + + /// + /// The type of this runtime framework + /// + public RuntimeType Runtime { get; private set; } + + /// + /// The framework version for this runtime framework + /// + public Version FrameworkVersion { get; private set; } + + /// + /// The CLR version for this runtime framework + /// + public Version ClrVersion { get; private set; } + + /// + /// Return true if any CLR version may be used in + /// matching this RuntimeFramework object. + /// + public bool AllowAnyVersion + { + get { return this.ClrVersion == DefaultVersion; } + } + + /// + /// Returns the Display name for this framework + /// + public string DisplayName { get; private set; } + + #endregion + + #region Public Methods + + /// + /// Parses a string representing a RuntimeFramework. + /// The string may be just a RuntimeType name or just + /// a Version or a hyphentated RuntimeType-Version or + /// a Version prefixed by 'v'. + /// + /// + /// + public static RuntimeFramework Parse(string s) + { + RuntimeType runtime = RuntimeType.Any; + Version version = DefaultVersion; + + string[] parts = s.Split(new char[] { '-' }); + if (parts.Length == 2) + { + runtime = (RuntimeType)System.Enum.Parse(typeof(RuntimeType), parts[0], true); + string vstring = parts[1]; + if (vstring != "") + version = new Version(vstring); + } + else if (char.ToLower(s[0]) == 'v') + { + version = new Version(s.Substring(1)); + } + else if (IsRuntimeTypeName(s)) + { + runtime = (RuntimeType)System.Enum.Parse(typeof(RuntimeType), s, true); + } + else + { + version = new Version(s); + } + + return new RuntimeFramework(runtime, version); + } + + /// + /// Returns the best available framework that matches a target framework. + /// If the target framework has a build number specified, then an exact + /// match is needed. Otherwise, the matching framework with the highest + /// build number is used. + /// + /// + /// + public static RuntimeFramework GetBestAvailableFramework(RuntimeFramework target) + { + RuntimeFramework result = target; + + if (target.ClrVersion.Build < 0) + { + foreach (RuntimeFramework framework in AvailableFrameworks) + if (framework.Supports(target) && + framework.ClrVersion.Build > result.ClrVersion.Build) + { + result = framework; + } + } + + return result; + } + + /// + /// Overridden to return the short name of the framework + /// + /// + public override string ToString() + { + if (this.AllowAnyVersion) + { + return Runtime.ToString().ToLower(); + } + else + { + string vstring = FrameworkVersion.ToString(); + if (Runtime == RuntimeType.Any) + return "v" + vstring; + else + return Runtime.ToString().ToLower() + "-" + vstring; + } + } + + /// + /// Returns true if the current framework matches the + /// one supplied as an argument. Two frameworks match + /// if their runtime types are the same or either one + /// is RuntimeType.Any and all specified version components + /// are equal. Negative (i.e. unspecified) version + /// components are ignored. + /// + /// The RuntimeFramework to be matched. + /// True on match, otherwise false + public bool Supports(RuntimeFramework target) + { + if (this.Runtime != RuntimeType.Any + && target.Runtime != RuntimeType.Any + && this.Runtime != target.Runtime) + return false; + + if (this.AllowAnyVersion || target.AllowAnyVersion) + return true; + + return VersionsMatch(this.ClrVersion, target.ClrVersion) + && this.FrameworkVersion.Major >= target.FrameworkVersion.Major + && this.FrameworkVersion.Minor >= target.FrameworkVersion.Minor; + } + + #endregion + + #region Helper Methods + + private static bool IsRuntimeTypeName(string name) + { + foreach (string item in Enum.GetNames(typeof(RuntimeType))) + if (item.ToLower() == name.ToLower()) + return true; + + return false; + } + + private static string GetDefaultDisplayName(RuntimeType runtime, Version version) + { + if (version == DefaultVersion) + return runtime.ToString(); + else if (runtime == RuntimeType.Any) + return "v" + version.ToString(); + else + return runtime.ToString() + " " + version.ToString(); + } + + private static bool VersionsMatch(Version v1, Version v2) + { + return v1.Major == v2.Major && + v1.Minor == v2.Minor && + (v1.Build < 0 || v2.Build < 0 || v1.Build == v2.Build) && + (v1.Revision < 0 || v2.Revision < 0 || v1.Revision == v2.Revision); + } + + private static void AppendMonoFrameworks(List frameworks) + { + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + AppendAllMonoFrameworks(frameworks); + else + AppendDefaultMonoFramework(frameworks); + } + + private static void AppendAllMonoFrameworks(List frameworks) + { + // TODO: Find multiple installed Mono versions under Linux + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + // Use registry to find alternate versions + RegistryKey key = Registry.LocalMachine.OpenSubKey(@"Software\Novell\Mono"); + if (key == null) return; + + foreach (string version in key.GetSubKeyNames()) + { + RegistryKey subKey = key.OpenSubKey(version); + string monoPrefix = subKey.GetValue("SdkInstallRoot") as string; + + AppendMonoFramework(frameworks, monoPrefix, version); + } + } + else + AppendDefaultMonoFramework(frameworks); + } + + // This method works for Windows and Linux but currently + // is only called under Linux. + private static void AppendDefaultMonoFramework(List frameworks) + { + string monoPrefix = null; + string version = null; + + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + RegistryKey key = Registry.LocalMachine.OpenSubKey(@"Software\Novell\Mono"); + if (key != null) + { + version = key.GetValue("DefaultCLR") as string; + if (version != null && version != "") + { + key = key.OpenSubKey(version); + if (key != null) + monoPrefix = key.GetValue("SdkInstallRoot") as string; + } + } + } + else // Assuming we're currently running Mono - change if more runtimes are added + { + string libMonoDir = Path.GetDirectoryName(typeof(object).Assembly.Location); + monoPrefix = Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(libMonoDir))); + } + + AppendMonoFramework(frameworks, monoPrefix, version); + } + + private static void AppendMonoFramework(List frameworks, string monoPrefix, string version) + { + if (monoPrefix != null) + { + string displayFmt = version != null + ? "Mono " + version + " - {0} Profile" + : "Mono {0} Profile"; + + if (File.Exists(Path.Combine(monoPrefix, "lib/mono/1.0/mscorlib.dll"))) + { + RuntimeFramework framework = new RuntimeFramework(RuntimeType.Mono, new Version(1, 1, 4322)); + framework.DisplayName = string.Format(displayFmt, "1.0"); + frameworks.Add(framework); + } + + if (File.Exists(Path.Combine(monoPrefix, "lib/mono/2.0/mscorlib.dll"))) + { + RuntimeFramework framework = new RuntimeFramework(RuntimeType.Mono, new Version(2, 0, 50727)); + framework.DisplayName = string.Format(displayFmt, "2.0"); + frameworks.Add(framework); + } + + if (File.Exists(Path.Combine(monoPrefix, "lib/mono/4.0/mscorlib.dll"))) + { + RuntimeFramework framework = new RuntimeFramework(RuntimeType.Mono, new Version(4, 0, 30319)); + framework.DisplayName = string.Format(displayFmt, "4.0"); + frameworks.Add(framework); + } + } + } + + private static void AppendDotNetFrameworks(List frameworks) + { + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + // Handle Version 1.0, using a different registry key + AppendExtremelyOldDotNetFrameworkVersions(frameworks); + + RegistryKey key = Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\NET Framework Setup\NDP"); + if (key != null) + { + foreach (string name in key.GetSubKeyNames()) + { + if (name.StartsWith("v")) + { + var versionKey = key.OpenSubKey(name); + + if (name == "v4") + // Version 4 and 4.5 + AppendDotNetFourFrameworkVersions(frameworks, versionKey); + else + // Versions 1.1 through 3.5 + AppendOlderDotNetFrameworkVersion(frameworks, versionKey, new Version(name.Substring(1))); + } + } + } + } + } + + private static void AppendExtremelyOldDotNetFrameworkVersions(List frameworks) + { + RegistryKey key = Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\.NETFramework\policy\v1.0"); + if (key != null) + foreach (string build in key.GetValueNames()) + frameworks.Add(new RuntimeFramework(RuntimeType.Net, new Version("1.0." + build))); + } + + private static void AppendOlderDotNetFrameworkVersion(List frameworks, RegistryKey versionKey, Version version) + { + if (CheckInstallDword(versionKey)) + frameworks.Add(new RuntimeFramework(RuntimeType.Net, version)); + } + + // Note: this method cannot be generalized past V4, because (a) it has + // specific code for detecting .NET 4.5 and (b) we don't know what + // microsoft will do in the future + private static void AppendDotNetFourFrameworkVersions(List frameworks, RegistryKey versionKey) + { + foreach (string profile in new string[] { "Full", "Client" }) + { + var profileKey = versionKey.OpenSubKey(profile); + if (CheckInstallDword(profileKey)) + { + var framework = new RuntimeFramework(RuntimeType.Net, new Version(4, 0)); + framework.DisplayName += " - " + profile; + frameworks.Add(framework); + + var release = (int)profileKey.GetValue("Release", 0); + if (release > 0) + { + framework = new RuntimeFramework(RuntimeType.Net, new Version(4, 5)); + framework.DisplayName += " - " + profile; + frameworks.Add(framework); + } + } + } + } + + private static bool CheckInstallDword(RegistryKey key) + { + return (int)key.GetValue("Install", 0) == 1; + } + + #endregion + } +} diff --git a/src/nunit.engine/ServiceContext.cs b/src/nunit.engine/ServiceContext.cs new file mode 100644 index 00000000..787c13fc --- /dev/null +++ b/src/nunit.engine/ServiceContext.cs @@ -0,0 +1,229 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using NUnit.Engine.Services; + +namespace NUnit.Engine +{ + /// + /// The ServiceContext is used by services, runners and + /// external clients to locate the services they need through + /// the IServiceLocator interface. + /// + /// For internal use by runners and other services, individual + /// properties are provided for common services as well. + /// + public class ServiceContext : IServiceLocator + { + #region Service Properties + + #region ServiceManager + + private ServiceManager serviceManager = new ServiceManager(); + public ServiceManager ServiceManager + { + get { return serviceManager; } + } + + #endregion + + #region DomainManager + + private DomainManager domainManager; + public DomainManager DomainManager + { + get + { + if (domainManager == null) + domainManager = (DomainManager)ServiceManager.GetService(typeof(DomainManager)); + + return domainManager; + } + } + + #endregion + + #region UserSettings + + private ISettings userSettings; + public ISettings UserSettings + { + get + { + if (userSettings == null) + userSettings = (ISettings)ServiceManager.GetService(typeof(ISettings)); + + // // Temporary fix needed to run TestDomain tests in test AppDomain + // // TODO: Figure out how to set up the test domain correctly + // if ( userSettings == null ) + // userSettings = new SettingsService(); + + return userSettings; + } + } + + #endregion + + #region RecentFilesService + private IRecentFiles recentFiles; + public IRecentFiles RecentFiles + { + get + { + if ( recentFiles == null ) + recentFiles = (IRecentFiles)ServiceManager.GetService( typeof( IRecentFiles ) ); + + return recentFiles; + } + } + #endregion + + #region RuntimeFrameworkSelector + + private IRuntimeFrameworkSelector selector; + public IRuntimeFrameworkSelector RuntimeFrameworkSelector + { + get + { + if (selector == null) + selector = (IRuntimeFrameworkSelector)ServiceManager.GetService(typeof(IRuntimeFrameworkSelector)); + + return selector; + } + } + + #endregion + + #region DriverFactory + + private IDriverFactory driverFactory; + public IDriverFactory DriverFactory + { + get + { + if (driverFactory == null) + driverFactory = (IDriverFactory)ServiceManager.GetService(typeof(IDriverFactory)); + + return driverFactory; + } + } + + #endregion + + #region TestRunnerFactory + + private ITestRunnerFactory testRunnerFactory; + public ITestRunnerFactory TestRunnerFactory + { + get + { + if (testRunnerFactory == null) + testRunnerFactory = (ITestRunnerFactory)ServiceManager.GetService(typeof(ITestRunnerFactory)); + + return testRunnerFactory; + } + } + + #endregion + + #region TestLoader + // private static TestLoader loader; + // public static TestLoader TestLoader + // { + // get + // { + // if ( loader == null ) + // loader = (TestLoader)ServiceManager.Services.GetService( typeof( TestLoader ) ); + + // return loader; + // } + // } + #endregion + + #region TestAgency + + private TestAgency agency; + public TestAgency TestAgency + { + get + { + if (agency == null) + agency = (TestAgency)ServiceManager.GetService(typeof(TestAgency)); + + // Temporary fix needed to run ProcessRunner tests in test AppDomain + // TODO: Figure out how to set up the test domain correctly + // if ( agency == null ) + // { + // agency = new TestAgency(); + // agency.Start(); + // } + + return agency; + } + } + + #endregion + + #region ProjectService + private ProjectService projectService; + public ProjectService ProjectService + { + get + { + if (projectService == null) + projectService = (ProjectService) + ServiceManager.GetService(typeof(ProjectService)); + + return projectService; + } + } + #endregion + + #endregion + + #region Add Method + + public void Add(IService service) + { + ServiceManager.AddService(service); + service.ServiceContext = this; + } + + #endregion + + #region IServiceLocator Explicit Implementation + + T IServiceLocator.GetService() + { + return ServiceManager.GetService(typeof(T)) as T; + } + + object IServiceLocator.GetService(Type serviceType) + { + return ServiceManager.GetService(serviceType); + } + + #endregion + } +} diff --git a/src/nunit.engine/Services/DefaultTestRunnerFactory.cs b/src/nunit.engine/Services/DefaultTestRunnerFactory.cs new file mode 100644 index 00000000..f5d07595 --- /dev/null +++ b/src/nunit.engine/Services/DefaultTestRunnerFactory.cs @@ -0,0 +1,102 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using NUnit.Engine.Internal; +using NUnit.Engine.Runners; + +namespace NUnit.Engine.Services +{ + /// + /// DefaultTestRunnerFactory handles creation of a suitable test + /// runner for a given package to be loaded and run either in a + /// separate process or within the same process. + /// + public class DefaultTestRunnerFactory : InProcessTestRunnerFactory, ITestRunnerFactory + { + /// + /// Returns a test runner based on the settings in a TestPackage. + /// Any setting that is "consumed" by the factory is removed, so + /// that downstream runners using the factory will not repeatedly + /// create the same type of runner. + /// + /// The TestPackage to be loaded and run + /// A TestRunner + public override ITestEngineRunner MakeTestRunner(TestPackage package) + { + ProcessModel processModel = GetTargetProcessModel(package); + + switch (processModel) + { + case ProcessModel.Multiple: + package.Settings.Remove("ProcessModel"); + return new MultipleTestProcessRunner(this.ServiceContext, package); + case ProcessModel.Separate: + package.Settings.Remove("ProcessModel"); + return new ProcessRunner(this.ServiceContext, package); + default: + return base.MakeTestRunner(package); + } + } + + public override bool CanReuse(ITestEngineRunner runner, TestPackage package) + { + RuntimeFramework currentFramework = RuntimeFramework.CurrentFramework; + RuntimeFramework targetFramework = ServiceContext.RuntimeFrameworkSelector.SelectRuntimeFramework(package); + + ProcessModel processModel = (ProcessModel)System.Enum.Parse( + typeof(ProcessModel), + package.GetSetting(RunnerSettings.ProcessModel, "Default")); + if (processModel == ProcessModel.Default) + if (!currentFramework.Supports(targetFramework)) + processModel = ProcessModel.Separate; + + switch (processModel) + { + case ProcessModel.Multiple: + return runner is MultipleTestProcessRunner; + case ProcessModel.Separate: + ProcessRunner processRunner = runner as ProcessRunner; + return processRunner != null && processRunner.RuntimeFramework == targetFramework; + default: + return base.CanReuse(runner, package); + } + } + + private ProcessModel GetTargetProcessModel(TestPackage package) + { + RuntimeFramework currentFramework = RuntimeFramework.CurrentFramework; + RuntimeFramework targetFramework = ServiceContext.RuntimeFrameworkSelector.SelectRuntimeFramework(package); + + ProcessModel processModel = (ProcessModel)System.Enum.Parse( + typeof(ProcessModel), + package.GetSetting(RunnerSettings.ProcessModel, "Default")); + + if (processModel == ProcessModel.Default) + if (!currentFramework.Supports(targetFramework)) + processModel = ProcessModel.Separate; + + return processModel; + } + } +} diff --git a/src/nunit.engine/Services/DomainManager.cs b/src/nunit.engine/Services/DomainManager.cs new file mode 100644 index 00000000..c4d70f19 --- /dev/null +++ b/src/nunit.engine/Services/DomainManager.cs @@ -0,0 +1,400 @@ +// *********************************************************************** +// Copyright (c) 2007 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Reflection; +using System.Diagnostics; +using System.Security; +using System.Security.Policy; +using System.Security.Principal; +using NUnit.Engine.Internal; + +namespace NUnit.Engine.Services +{ + /// + /// The DomainManager class handles the creation and unloading + /// of domains as needed and keeps track of all existing domains. + /// + public class DomainManager : IService + { + static Logger log = InternalTrace.GetLogger(typeof(DomainManager)); + + #region Properties + + private string shadowCopyPath; + private string ShadowCopyPath + { + get + { + if ( shadowCopyPath == null ) + { + shadowCopyPath = ServiceContext.UserSettings.GetSetting("Options.TestLoader.ShadowCopyPath", ""); + if (shadowCopyPath == "") + shadowCopyPath = PathUtils.Combine(NUnitConfiguration.ApplicationDirectory, "ShadowCopyCache"); + else + shadowCopyPath = Environment.ExpandEnvironmentVariables(shadowCopyPath); + } + + return shadowCopyPath; + } + } + + #endregion + + #region Create and Unload Domains + /// + /// Construct an application domain for running a test package + /// + /// The TestPackage to be run + public AppDomain CreateDomain( TestPackage package ) + { + AppDomainSetup setup = CreateAppDomainSetup(package); + + string domainName = "test-domain-" + package.Name; + // Setup the Evidence + Evidence evidence = new Evidence(AppDomain.CurrentDomain.Evidence); + if (evidence.Count == 0) + { + Zone zone = new Zone(SecurityZone.MyComputer); + evidence.AddHost(zone); + Assembly assembly = Assembly.GetExecutingAssembly(); + Url url = new Url(assembly.CodeBase); + evidence.AddHost(url); + Hash hash = new Hash(assembly); + evidence.AddHost(hash); + } + + log.Info("Creating AppDomain " + domainName); + + AppDomain runnerDomain; + + // TODO: Find an approach that works across all platforms + + //// TODO: Try to eliminate this test. Currently, running on + //// Linux with the permission set specified causes an + //// unexplained crash when unloading the domain. + //if (Environment.OSVersion.Platform == PlatformID.Win32NT) + //{ + // PermissionSet permissionSet = new PermissionSet( PermissionState.Unrestricted ); + // runnerDomain = AppDomain.CreateDomain(domainName, evidence, setup, permissionSet, null); + //} + //else + runnerDomain = AppDomain.CreateDomain(domainName, evidence, setup); + + // Set PrincipalPolicy for the domain if called for in the settings + if (ServiceContext.UserSettings.GetSetting("Options.TestLoader.SetPrincipalPolicy", false)) + runnerDomain.SetPrincipalPolicy((PrincipalPolicy)ServiceContext.UserSettings.GetSetting( + "Options.TestLoader.PrincipalPolicy", PrincipalPolicy.UnauthenticatedPrincipal)); + + //// HACK: Only pass down our AddinRegistry one level so that tests of NUnit + //// itself start without any addins defined. + //if ( !IsTestDomain( AppDomain.CurrentDomain ) ) + // runnerDomain.SetData("AddinRegistry", Services.AddinRegistry); + + //// Inject DomainInitializer into the remote domain - there are other + //// approaches, but this works for all CLR versions. + //DomainInitializer initializer = DomainInitializer.CreateInstance(runnerDomain); + + //// HACK: Under nunit-console, direct use of the enum fails + //int traceLevel = IsTestDomain(AppDomain.CurrentDomain) + // ? (int)InternalTraceLevel.Off : (int)InternalTrace.Level; + + //initializer.InitializeDomain(traceLevel); + + return runnerDomain; + } + + // Made separate and public for testing + public AppDomainSetup CreateAppDomainSetup(TestPackage package) + { + AppDomainSetup setup = new AppDomainSetup(); + + //For paralell tests, we need to use distinct application name + setup.ApplicationName = "Tests" + "_" + Environment.TickCount; + + FileInfo testFile = package.FullName != null && package.FullName != string.Empty + ? new FileInfo(package.FullName) + : null; + + string appBase = package.GetSetting(RunnerSettings.BasePath, string.Empty); + string configFile = package.GetSetting(RunnerSettings.ConfigurationFile, string.Empty); + string binPath = package.GetSetting(RunnerSettings.PrivateBinPath, string.Empty); + + if (testFile != null) + { + if (appBase == null || appBase == string.Empty) + appBase = testFile.DirectoryName; + + if (configFile == null || configFile == string.Empty) + //configFile = Services.ProjectService.CanLoadProject(testFile.Name) + // ? Path.GetFileNameWithoutExtension(testFile.Name) + ".config" + // : testFile.Name + ".config"; + configFile = testFile.Name + ".config"; + } + else if (appBase == null || appBase == string.Empty) + appBase = GetCommonAppBase(package.TestFiles); + + char lastChar = appBase[appBase.Length - 1]; + if (lastChar != Path.DirectorySeparatorChar && lastChar != Path.AltDirectorySeparatorChar) + appBase += Path.DirectorySeparatorChar; + + setup.ApplicationBase = appBase; + // TODO: Check whether Mono still needs full path to config file... + setup.ConfigurationFile = appBase != null && configFile != null + ? Path.Combine(appBase, configFile) + : configFile; + + if (package.GetSetting(RunnerSettings.AutoBinPath, binPath == string.Empty)) + binPath = GetPrivateBinPath(appBase, package.TestFiles); + + setup.PrivateBinPath = binPath; + + if (package.GetSetting("ShadowCopyFiles", true)) + { + setup.ShadowCopyFiles = "true"; + setup.ShadowCopyDirectories = appBase; + setup.CachePath = GetCachePath(); + } + else + setup.ShadowCopyFiles = "false"; + return setup; + } + + public void Unload(AppDomain domain) + { + new DomainUnloader(domain).Unload(); + } + + #endregion + + #region Nested DomainUnloader Class + class DomainUnloader + { + private Thread thread; + private AppDomain domain; + + public DomainUnloader(AppDomain domain) + { + this.domain = domain; + } + + public void Unload() + { + string domainName; + try + { + domainName = "UNKNOWN";//domain.FriendlyName; + } + catch (AppDomainUnloadedException) + { + return; + } + + log.Info("Unloading AppDomain " + domainName); + + thread = new Thread(new ThreadStart(UnloadOnThread)); + thread.Start(); + if (!thread.Join(30000)) + { + log.Error("Unable to unload AppDomain {0}, Unload thread timed out", domainName); + thread.Abort(); + } + } + + private void UnloadOnThread() + { + bool shadowCopy = false; + string cachePath = null; + string domainName = "UNKNOWN"; + + try + { + shadowCopy = domain.ShadowCopyFiles; + cachePath = domain.SetupInformation.CachePath; + domainName = domain.FriendlyName; + + AppDomain.Unload(domain); + } + catch (Exception ex) + { + // We assume that the tests did something bad and just leave + // the orphaned AppDomain "out there". + // TODO: Something useful. + log.Error("Unable to unload AppDomain " + domainName, ex); + } + finally + { + if (shadowCopy && cachePath != null) + DeleteCacheDir(new DirectoryInfo(cachePath)); + } + } + } + #endregion + + #region Helper Methods + /// + /// Get the location for caching and delete any old cache info + /// + private string GetCachePath() + { + int processId = Process.GetCurrentProcess().Id; + long ticks = DateTime.Now.Ticks; + string cachePath = Path.Combine( ShadowCopyPath, processId.ToString() + "_" + ticks.ToString() ); + + try + { + DirectoryInfo dir = new DirectoryInfo(cachePath); + if(dir.Exists) dir.Delete(true); + } + catch( Exception ex) + { + throw new ApplicationException( + string.Format( "Invalid cache path: {0}",cachePath ), + ex ); + } + + return cachePath; + } + + /// + /// Helper method to delete the cache dir. This method deals + /// with a bug that occurs when files are marked read-only + /// and deletes each file separately in order to give better + /// exception information when problems occur. + /// + /// TODO: This entire method is problematic. Should we be doing it? + /// + /// + private static void DeleteCacheDir( DirectoryInfo cacheDir ) + { + // Debug.WriteLine( "Modules:"); + // foreach( ProcessModule module in Process.GetCurrentProcess().Modules ) + // Debug.WriteLine( module.ModuleName ); + + + if(cacheDir.Exists) + { + foreach( DirectoryInfo dirInfo in cacheDir.GetDirectories() ) + DeleteCacheDir( dirInfo ); + + foreach( FileInfo fileInfo in cacheDir.GetFiles() ) + { + fileInfo.Attributes = FileAttributes.Normal; + try + { + fileInfo.Delete(); + } + catch( Exception ex ) + { + Debug.WriteLine( string.Format( + "Error deleting {0}, {1}", fileInfo.Name, ex.Message ) ); + } + } + + cacheDir.Attributes = FileAttributes.Normal; + + try + { + cacheDir.Delete(); + } + catch( Exception ex ) + { + Debug.WriteLine( string.Format( + "Error deleting {0}, {1}", cacheDir.Name, ex.Message ) ); + } + } + } + + private bool IsTestDomain(AppDomain domain) + { + return domain.FriendlyName.StartsWith( "test-domain-" ); + } + + public static string GetCommonAppBase(IList assemblies) + { + string commonBase = null; + + foreach (string assembly in assemblies) + { + string dir = Path.GetDirectoryName(Path.GetFullPath(assembly)); + if (commonBase == null) + commonBase = dir; + else while (!PathUtils.SamePathOrUnder(commonBase, dir) && commonBase != null) + commonBase = Path.GetDirectoryName(commonBase); + } + + return commonBase; + } + + public static string GetPrivateBinPath(string basePath, IList assemblies) + { + List dirList = new List(); + StringBuilder sb = new StringBuilder(200); + + foreach( string assembly in assemblies ) + { + string dir = PathUtils.RelativePath( + Path.GetFullPath(basePath), + Path.GetDirectoryName( Path.GetFullPath(assembly) ) ); + if ( dir != null && dir != string.Empty && dir != "." && !dirList.Contains( dir ) ) + { + dirList.Add( dir ); + if ( sb.Length > 0 ) + sb.Append( Path.PathSeparator ); + sb.Append( dir ); + } + } + + return sb.Length == 0 ? null : sb.ToString(); + } + + public void DeleteShadowCopyPath() + { + if ( Directory.Exists( ShadowCopyPath ) ) + Directory.Delete( ShadowCopyPath, true ); + } + #endregion + + #region IService Members + + private ServiceContext services; + public ServiceContext ServiceContext + { + get { return services; } + set { services = value; } + } + + public void UnloadService() + { + // TODO: Add DomainManager.UnloadService implementation + } + + public void InitializeService() { } + + #endregion + } +} diff --git a/src/nunit.engine/Services/DriverFactory.cs b/src/nunit.engine/Services/DriverFactory.cs new file mode 100644 index 00000000..d23d14c2 --- /dev/null +++ b/src/nunit.engine/Services/DriverFactory.cs @@ -0,0 +1,81 @@ +// *********************************************************************** +// Copyright (c) 2013 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace NUnit.Engine.Services +{ + public class DriverFactory : IDriverFactory, IService + { + #region IDriverFactory Members + + public IFrameworkDriver GetDriver(AppDomain domain, string assemblyPath, IDictionary settings) + { + // Throws if this isn't a managed assembly or if it was built + // with a later version of the same assembly. + AssemblyName assemblyName = AssemblyName.GetAssemblyName(assemblyPath); + + var testAssembly = Assembly.Load(assemblyName); + + foreach(var refAssembly in testAssembly.GetReferencedAssemblies()) + switch (refAssembly.Name) + { + case NUnitFrameworkDriver.FrameworkName: + return new NUnitFrameworkDriver(domain, assemblyPath, settings); + case NUnitLiteFrameworkDriver.FrameworkName: + return new NUnitLiteFrameworkDriver(domain, assemblyPath, settings); + } + + throw new NUnitEngineException("Unable to locate driver for " + assemblyPath); + } + + #endregion + + #region IService Members + + private ServiceContext services; + public ServiceContext ServiceContext + { + get { return services; } + set { services = value; } + } + + public void InitializeService() + { + } + + public void UnloadService() + { + } + + #endregion + } + + public interface IDriverFactory + { + IFrameworkDriver GetDriver(AppDomain domain, string assemblyPath, IDictionary settings); + } +} diff --git a/src/nunit.engine/Services/InProcessTestRunnerFactory.cs b/src/nunit.engine/Services/InProcessTestRunnerFactory.cs new file mode 100644 index 00000000..8bd04426 --- /dev/null +++ b/src/nunit.engine/Services/InProcessTestRunnerFactory.cs @@ -0,0 +1,93 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using NUnit.Engine.Internal; +using NUnit.Engine.Runners; + +namespace NUnit.Engine.Services +{ + /// + /// InProcessTestRunnerFactory handles creation of a suitable test + /// runner for a given package to be loaded and run within the + /// same process. + /// + public class InProcessTestRunnerFactory : ITestRunnerFactory, IService + { + #region ITestRunnerFactory Members + + /// + /// Returns a test runner based on the settings in a TestPackage. + /// Any setting that is "consumed" by the factory is removed, so + /// that downstream runners using the factory will not repeatedly + /// create the same type of runner. + /// + /// The TestPackage to be loaded and run + /// An ITestEngineRunner + public virtual ITestEngineRunner MakeTestRunner(TestPackage package) + { + DomainUsage domainUsage = (DomainUsage)System.Enum.Parse( + typeof(DomainUsage), + package.GetSetting(RunnerSettings.DomainUsage, "Default")); + + switch (domainUsage) + { + case DomainUsage.Multiple: + package.Settings.Remove("DomainUsage"); + return new MultipleTestDomainRunner(ServiceContext, package); + case DomainUsage.None: + return new LocalTestRunner(ServiceContext, package); + case DomainUsage.Single: + default: + return new TestDomainRunner(ServiceContext, package); + } + } + + public virtual bool CanReuse(ITestEngineRunner runner, TestPackage package) + { + return false; + } + + #endregion + + #region IService Members + + private ServiceContext services; + public ServiceContext ServiceContext + { + get { return services; } + set { services = value; } + } + + public void InitializeService() + { + } + + public void UnloadService() + { + } + + #endregion + } +} diff --git a/src/nunit.engine/Services/NUnitProjectLoader.cs b/src/nunit.engine/Services/NUnitProjectLoader.cs new file mode 100644 index 00000000..7807041d --- /dev/null +++ b/src/nunit.engine/Services/NUnitProjectLoader.cs @@ -0,0 +1,48 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using NUnit.Engine.Internal; + +namespace NUnit.Engine.Services +{ + public class NUnitProjectLoader : IProjectLoader + { + #region IProjectLoader Members + + public bool IsProjectFile(string path) + { + return Path.GetExtension(path) == ".nunit"; + } + + public IProject LoadProject(string path) + { + NUnitProject project = new NUnitProject(); + project.Load(path); + return project; + } + + #endregion + } +} diff --git a/src/nunit.engine/Services/ProjectService.cs b/src/nunit.engine/Services/ProjectService.cs new file mode 100644 index 00000000..5931e262 --- /dev/null +++ b/src/nunit.engine/Services/ProjectService.cs @@ -0,0 +1,229 @@ +// *********************************************************************** +// Copyright (c) 2008 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; + +namespace NUnit.Engine.Services +{ + /// + /// Summary description for ProjectService. + /// + public class ProjectService : IProjectLoader, IService + { + /// + /// Seed used to generate names for new projects + /// + //private int projectSeed = 0; + + /// + /// The extension used for test projects + /// + //private static readonly string nunitExtension = ".nunit"; + + /// + /// Array of all installed ProjectLoaders + /// + IProjectLoader[] loaders = new IProjectLoader[] + { + new NUnitProjectLoader(), + //new VisualStudioLoader() + }; + + #region Instance Methods + + public TestPackage MakeTestPackage(IProject project) + { + return MakeTestPackage(project, null); + } + + public TestPackage MakeTestPackage(IProject project, string configName) + { + TestPackage package = new TestPackage(project.ProjectPath); + + if (project.Configs.Count == 0) + return package; + + foreach (string assembly in project.ActiveConfig.Assemblies) + package.Add(assembly); + + return package; + } + + ///// + ///// Creates a project to wrap a list of assemblies + ///// + //public IProject WrapAssemblies( string[] assemblies ) + //{ + // // if only one assembly is passed in then the configuration file + // // should follow the name of the assembly. This will only happen + // // if the LoadAssembly method is called. Currently the console ui + // // does not differentiate between having one or multiple assemblies + // // passed in. + // if ( assemblies.Length == 1) + // return WrapAssembly(assemblies[0]); + + + // NUnitProject project = ServiceContext.ProjectService.EmptyProject(); + // ProjectConfig config = new ProjectConfig( "Default" ); + // foreach( string assembly in assemblies ) + // { + // string fullPath = Path.GetFullPath( assembly ); + + // if ( !File.Exists( fullPath ) ) + // throw new FileNotFoundException( string.Format( "Assembly not found: {0}", fullPath ) ); + + // config.Assemblies.Add( fullPath ); + // } + + // project.Configs.Add( config ); + + // // TODO: Deduce application base, and provide a + // // better value for loadpath and project path + // // analagous to how new projects are handled + // string basePath = Path.GetDirectoryName( Path.GetFullPath( assemblies[0] ) ); + // project.ProjectPath = Path.Combine( basePath, project.Name + ".nunit" ); + + // project.IsDirty = true; + + // return project; + //} + + ///// + ///// Creates a project to wrap an assembly + ///// + //public IProject WrapAssembly( string assemblyPath ) + //{ + // if ( !File.Exists( assemblyPath ) ) + // throw new FileNotFoundException( string.Format( "Assembly not found: {0}", assemblyPath ) ); + + // string fullPath = Path.GetFullPath( assemblyPath ); + + // NUnitProject project = new NUnitProject( fullPath ); + + // ProjectConfig config = new ProjectConfig( "Default" ); + // config.Assemblies.Add( fullPath ); + // project.Configs.Add( config ); + + // project.IsAssemblyWrapper = true; + // project.IsDirty = false; + + // return project; + //} + + //public string GenerateProjectName() + //{ + // return string.Format( "Project{0}", ++projectSeed ); + //} + + //public IProject EmptyProject() + //{ + // return new NUnitProject( GenerateProjectName() ); + //} + + //public IProject NewProject() + //{ + // NUnitProject project = EmptyProject(); + + // project.Configs.Add( "Debug" ); + // project.Configs.Add( "Release" ); + // project.IsDirty = false; + + // return project; + //} + + //public void SaveProject( IProject project ) + //{ + // project.Save(); + //} + #endregion + + #region IProjectLoader Members + + public bool IsProjectFile(string path) + { + foreach( IProjectLoader loader in loaders ) + if ( loader.IsProjectFile(path) ) + return true; + + return false; + } + + public IProject LoadProject(string path) + { + foreach( IProjectLoader loader in loaders ) + { + if ( loader.IsProjectFile( path ) ) + return loader.LoadProject( path ); + } + + return null; + } + + /// + /// Expands a TestPackages based on a known project format, + /// creating a subpackage for each assembly. The FilePath + /// of hte package must be checked to ensure that it is + /// a known project format before calling this method. + /// + /// The TestPackage to be expanded + public void ExpandProjectPackage(TestPackage package) + { + IProject project = LoadProject(package.FullName); + + string configName = package.GetSetting(RunnerSettings.ActiveConfig, string.Empty); // Need RunnerSetting + IProjectConfig config = configName != string.Empty + ? project.Configs[configName] + : project.ActiveConfig; + + foreach (string key in config.Settings.Keys) + package.Settings[key] = config.Settings[key]; + + foreach (string assembly in config.Assemblies) + package.Add(assembly); + } + + #endregion + + #region IService Members + + private ServiceContext services; + public ServiceContext ServiceContext + { + get { return services; } + set { services = value; } + } + + public void InitializeService() + { + // TODO: Add ProjectLoader.InitializeService implementation + } + + public void UnloadService() + { + // TODO: Add ProjectLoader.UnloadService implementation + } + + #endregion + } +} diff --git a/src/nunit.engine/Services/RecentFilesService.cs b/src/nunit.engine/Services/RecentFilesService.cs new file mode 100644 index 00000000..dbff9226 --- /dev/null +++ b/src/nunit.engine/Services/RecentFilesService.cs @@ -0,0 +1,150 @@ +// *********************************************************************** +// Copyright (c) 2007 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; + +namespace NUnit.Engine.Services +{ + /// + /// Summary description for RecentFilesService. + /// + public class RecentFilesService : IRecentFiles, IService + { + private IList _fileEntries = new List(); + + private const int MinSize = 0; + private const int MaxSize = 24; + private const int DefaultSize = 5; + + #region Properties + + public ServiceContext ServiceContext { get; set; } + + public int MaxFiles + { + get + { + int size = ServiceContext.UserSettings.GetSetting("Gui.RecentProjects.MaxFiles", DefaultSize ); + + if ( size < MinSize ) size = MinSize; + if ( size > MaxSize ) size = MaxSize; + + return size; + } + set + { + int oldSize = MaxFiles; + int newSize = value; + + if ( newSize < MinSize ) newSize = MinSize; + if ( newSize > MaxSize ) newSize = MaxSize; + + ServiceContext.UserSettings.SaveSetting( "Gui.RecentProjects.MaxFiles", newSize ); + if ( newSize < oldSize ) SaveEntriesToSettings(); + } + } + #endregion + + #region Public Methods + + public IList Entries { get { return _fileEntries; } } + + public void Remove( string fileName ) + { + _fileEntries.Remove(fileName); + } + + public void SetMostRecent( string filePath ) + { + _fileEntries.Remove(filePath); + + _fileEntries.Insert( 0, filePath ); + if( _fileEntries.Count > MaxFiles ) + _fileEntries.RemoveAt( MaxFiles ); + } + #endregion + + #region Helper Methods for saving and restoring the settings + + private void LoadEntriesFromSettings() + { + _fileEntries.Clear(); + + // TODO: Prefix should be provided by caller + AddEntriesForPrefix("Gui.RecentProjects"); + } + + private void AddEntriesForPrefix(string prefix) + { + for (int index = 1; index < MaxFiles; index++) + { + if (_fileEntries.Count >= MaxFiles) break; + + string fileSpec = ServiceContext.UserSettings.GetSetting(GetRecentFileKey(prefix, index)) as string; + if (fileSpec != null) _fileEntries.Add(fileSpec); + } + } + + private void SaveEntriesToSettings() + { + string prefix = "Gui.RecentProjects"; + ISettings settings = ServiceContext.UserSettings; + + while( _fileEntries.Count > MaxFiles ) + _fileEntries.RemoveAt( _fileEntries.Count - 1 ); + + for( int index = 0; index < MaxSize; index++ ) + { + string keyName = GetRecentFileKey( prefix, index + 1 ); + if ( index < _fileEntries.Count ) + settings.SaveSetting( keyName, _fileEntries[index] ); + else + settings.RemoveSetting( keyName ); + } + + // Remove legacy entries here + settings.RemoveGroup("RecentProjects"); + } + + private string GetRecentFileKey( string prefix, int index ) + { + return string.Format( "{0}.File{1}", prefix, index ); + } + #endregion + + #region IService Members + + public void UnloadService() + { + SaveEntriesToSettings(); + } + + public void InitializeService() + { + LoadEntriesFromSettings(); + } + + #endregion + } +} diff --git a/src/nunit.engine/Services/RuntimeFrameworkSelector.cs b/src/nunit.engine/Services/RuntimeFrameworkSelector.cs new file mode 100644 index 00000000..77cf340e --- /dev/null +++ b/src/nunit.engine/Services/RuntimeFrameworkSelector.cs @@ -0,0 +1,114 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using System.Reflection; +using NUnit.Engine.Internal; + +namespace NUnit.Engine.Services +{ + public class RuntimeFrameworkSelector : IRuntimeFrameworkSelector, IService + { + static Logger log = InternalTrace.GetLogger(typeof(RuntimeFrameworkSelector)); + + /// + /// Selects a target runtime framework for a TestPackage based on + /// the settings in the package and the assemblies themselves. + /// The package RuntimeFramework setting may be updated as a + /// result and the selected runtime is returned. + /// + /// A TestPackage + /// The selected RuntimeFramework + public RuntimeFramework SelectRuntimeFramework(TestPackage package) + { + RuntimeFramework currentFramework = RuntimeFramework.CurrentFramework; + string frameworkSetting = package.GetSetting(RunnerSettings.RuntimeFramework, ""); + RuntimeFramework requestedFramework = frameworkSetting.Length > 0 + ? RuntimeFramework.Parse(frameworkSetting) + : new RuntimeFramework(RuntimeType.Any, RuntimeFramework.DefaultVersion); + + log.Debug("Current framework is {0}", currentFramework); + if (requestedFramework == null) + log.Debug("No specific framework requested"); + else + log.Debug("Requested framework is {0}", requestedFramework); + + RuntimeType targetRuntime = requestedFramework.Runtime; + Version targetVersion = requestedFramework.FrameworkVersion; + + if (targetRuntime == RuntimeType.Any) + targetRuntime = currentFramework.Runtime; + + if (targetVersion == RuntimeFramework.DefaultVersion) + { + if (ServiceContext.UserSettings.GetSetting("Options.TestLoader.RuntimeSelectionEnabled", true)) + foreach (string assembly in package.TestFiles) + { + using (AssemblyReader reader = new AssemblyReader(assembly)) + { + Version v = new Version(reader.ImageRuntimeVersion.Substring(1)); + log.Debug("Assembly {0} uses version {1}", assembly, v); + if (v > targetVersion) targetVersion = v; + } + } + else + targetVersion = RuntimeFramework.CurrentFramework.ClrVersion; + + RuntimeFramework checkFramework = new RuntimeFramework(targetRuntime, targetVersion); + if (!checkFramework.IsAvailable || !ServiceContext.TestAgency.IsRuntimeVersionSupported(targetVersion)) + { + log.Debug("Preferred version {0} is not installed or this NUnit installation does not support it", targetVersion); + if (targetVersion < currentFramework.FrameworkVersion) + targetVersion = currentFramework.FrameworkVersion; + } + } + + RuntimeFramework targetFramework = new RuntimeFramework(targetRuntime, targetVersion); + package.Settings[RunnerSettings.RuntimeFramework] = targetFramework.ToString(); + + log.Debug("Test will use {0} framework", targetFramework); + + return targetFramework; + } + + #region IService Members + + private ServiceContext services; + public ServiceContext ServiceContext + { + get { return services; } + set { services = value; } + } + + public void InitializeService() + { + } + + public void UnloadService() + { + } + + #endregion + } +} diff --git a/src/nunit.engine/Services/ServiceManager.cs b/src/nunit.engine/Services/ServiceManager.cs new file mode 100644 index 00000000..2afb6a6a --- /dev/null +++ b/src/nunit.engine/Services/ServiceManager.cs @@ -0,0 +1,124 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using NUnit.Engine.Internal; + +namespace NUnit.Engine.Services +{ + /// + /// ServiceManager handles access to all services - global + /// facilities shared by all instances of TestEngine. + /// + public class ServiceManager + { + private List services = new List(); + private Dictionary serviceIndex = new Dictionary(); + + static Logger log = InternalTrace.GetLogger(typeof(ServiceManager)); + + public bool ServicesInitialized { get; private set; } + + #region Public Methods + + public IService GetService( Type serviceType ) + { + IService theService = null; + + if (serviceIndex.ContainsKey(serviceType)) + theService = (IService)serviceIndex[serviceType]; + else + foreach( IService service in services ) + { + // TODO: Does this work on Mono? + if( serviceType.IsInstanceOfType( service ) ) + { + serviceIndex[serviceType] = service; + theService = service; + break; + } + } + + if (theService == null) + log.Error(string.Format("Requested service {0} was not found", serviceType.FullName)); + else + log.Debug(string.Format("Request for service {0} satisfied by {1}", serviceType.Name, theService.GetType().Name)); + + return theService; + } + + public void AddService(IService service) + { + services.Add(service); + log.Debug("Added " + service.GetType().Name); + } + + public void InitializeServices() + { + foreach( IService service in services ) + { + log.Info( "Initializing " + service.GetType().Name ); + try + { + service.InitializeService(); + } + catch (Exception ex) + { + // TODO: Should we pass this exception through? + log.Error("Failed to initialize service", ex); + } + } + + this.ServicesInitialized = true; + } + + public void StopAllServices() + { + // Stop services in reverse of initialization order + // TODO: Deal with dependencies explicitly + int index = services.Count; + while (--index >= 0) + { + IService service = services[index] as IService; + log.Info("Stopping " + service.GetType().Name); + try + { + service.UnloadService(); + } + catch (Exception ex) + { + log.Error("Failure stopping service", ex); + } + } + } + + public void ClearServices() + { + log.Info("Clearing Service list"); + services.Clear(); + } + + #endregion + } +} diff --git a/src/nunit.engine/Services/SettingsService.cs b/src/nunit.engine/Services/SettingsService.cs new file mode 100644 index 00000000..0be13fae --- /dev/null +++ b/src/nunit.engine/Services/SettingsService.cs @@ -0,0 +1,57 @@ +// *********************************************************************** +// Copyright (c) 2013 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using NUnit.Engine.Internal; + +namespace NUnit.Engine.Services +{ + /// + /// Summary description for UserSettingsService. + /// + public class SettingsService : SettingsStore, IService + { + public SettingsService() { } + + public SettingsService(string settingsFile, bool writeable) + : base(settingsFile, writeable) { } + + #region IService Implementation + + public ServiceContext ServiceContext { get; set; } + + public void InitializeService() + { + LoadSettings(); + } + + public void UnloadService() + { + SaveSettings(); + + this.Dispose(); + } + #endregion + } +} diff --git a/src/nunit.engine/Services/TestAgency.cs b/src/nunit.engine/Services/TestAgency.cs new file mode 100644 index 00000000..40e89258 --- /dev/null +++ b/src/nunit.engine/Services/TestAgency.cs @@ -0,0 +1,460 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using System.Threading; +using System.Diagnostics; +using System.Collections.Generic; +using System.Reflection; +using NUnit.Engine.Internal; + +namespace NUnit.Engine.Services +{ + /// + /// Enumeration used to report AgentStatus + /// + public enum AgentStatus + { + Unknown, + Starting, + Ready, + Busy, + Stopping + } + + /// + /// The TestAgency class provides RemoteTestAgents + /// on request and tracks their status. Agents + /// are wrapped in an instance of the TestAgent + /// class. Multiple agent types are supported + /// but only one, ProcessAgent is implemented + /// at this time. + /// + public class TestAgency : ServerBase, ITestAgency, IService + { + static Logger log = InternalTrace.GetLogger(typeof(TestAgency)); + + #region Private Fields + private AgentDataBase agentData = new AgentDataBase(); + #endregion + + #region Constructors + public TestAgency() : this( "TestAgency", 0 ) { } + + public TestAgency( string uri, int port ) : base( uri, port ) { } + #endregion + + #region ServerBase Overrides + //public override void Stop() + //{ + // foreach( KeyValuePair pair in agentData ) + // { + // AgentRecord r = pair.Value; + + // if ( !r.Process.HasExited ) + // { + // if ( r.Agent != null ) + // { + // r.Agent.Stop(); + // r.Process.WaitForExit(10000); + // } + + // if ( !r.Process.HasExited ) + // r.Process.Kill(); + // } + // } + + // agentData.Clear(); + + // base.Stop (); + //} + #endregion + + #region Public Methods - Called by Agents + public void Register( ITestAgent agent ) + { + AgentRecord r = agentData[agent.Id]; + if ( r == null ) + throw new ArgumentException( + string.Format("Agent {0} is not in the agency database", agent.Id), + "agentId"); + r.Agent = agent; + } + + public void ReportStatus( Guid agentId, AgentStatus status ) + { + AgentRecord r = agentData[agentId]; + + if ( r == null ) + throw new ArgumentException( + string.Format("Agent {0} is not in the agency database", agentId), + "agentId" ); + + r.Status = status; + } + #endregion + + #region Public Methods - Called by Clients + + /// + /// Returns true if NUnit support for the runtime specified + /// is installed, independent of whether the runtime itself + /// is installed on the system. + /// + /// In the current implementation, only .NET 1.x requires + /// special handling, since all higher runtimes are + /// supported normally. + /// + /// The desired runtime version + /// True if NUnit support is installed + public bool IsRuntimeVersionSupported(Version version) + { + return GetNUnitBinDirectory(version) != null; + } + + public ITestAgent GetAgent() + { + return GetAgent( RuntimeFramework.CurrentFramework, Timeout.Infinite ); + } + + public ITestAgent GetAgent(int waitTime) + { + return GetAgent(RuntimeFramework.CurrentFramework, waitTime); + } + + public ITestAgent GetAgent(RuntimeFramework framework, int waitTime) + { + return GetAgent(framework, waitTime, false, string.Empty); + } + + public ITestAgent GetAgent(RuntimeFramework framework, int waitTime, bool enableDebug, string agentArgs) + { + log.Info("Getting agent for use under {0}", framework); + + if (!framework.IsAvailable) + throw new ArgumentException( + string.Format("The {0} framework is not available", framework), + "framework"); + + // TODO: Decide if we should reuse agents + //AgentRecord r = FindAvailableRemoteAgent(type); + //if ( r == null ) + // r = CreateRemoteAgent(type, framework, waitTime); + return CreateRemoteAgent(framework, waitTime, enableDebug, agentArgs); + } + + public void ReleaseAgent( ITestAgent agent ) + { + AgentRecord r = agentData[agent.Id]; + if (r == null) + log.Error(string.Format("Unable to release agent {0} - not in database", agent.Id)); + else + { + r.Status = AgentStatus.Ready; + log.Debug("Releasing agent " + agent.Id.ToString()); + } + } + + //public void DestroyAgent( ITestAgent agent ) + //{ + // AgentRecord r = agentData[agent.Id]; + // if ( r != null ) + // { + // if( !r.Process.HasExited ) + // r.Agent.Stop(); + // agentData[r.Id] = null; + // } + //} + #endregion + + #region Helper Methods + private Guid LaunchAgentProcess(RuntimeFramework targetRuntime, bool enableDebug, string agentArgs) + { + string agentExePath = GetTestAgentExePath(targetRuntime.ClrVersion); + + if (agentExePath == null) + throw new ArgumentException( + string.Format("NUnit components for version {0} of the CLR are not installed", + targetRuntime.ClrVersion.ToString()), "targetRuntime"); + + log.Debug("Using nunit-agent at " + agentExePath); + + Process p = new Process(); + p.StartInfo.UseShellExecute = false; + Guid agentId = Guid.NewGuid(); + string arglist = agentId.ToString() + " " + ServerUrl + " " + agentArgs; + + switch( targetRuntime.Runtime ) + { + case RuntimeType.Mono: + p.StartInfo.FileName = NUnitConfiguration.MonoExePath; + string monoOptions = "--runtime=v" + targetRuntime.ClrVersion.ToString(3); + if (enableDebug) monoOptions += " --debug"; + p.StartInfo.Arguments = string.Format("{0} \"{1}\" {2}", monoOptions, agentExePath, arglist); + break; + case RuntimeType.Net: + p.StartInfo.FileName = agentExePath; + + if (targetRuntime.ClrVersion.Build < 0) + targetRuntime = RuntimeFramework.GetBestAvailableFramework(targetRuntime); + + string envVar = "v" + targetRuntime.ClrVersion.ToString(3); + p.StartInfo.EnvironmentVariables["COMPLUS_Version"] = envVar; + + p.StartInfo.Arguments = arglist; + break; + default: + p.StartInfo.FileName = agentExePath; + p.StartInfo.Arguments = arglist; + break; + } + + //p.Exited += new EventHandler(OnProcessExit); + p.Start(); + log.Info("Launched Agent process {0} - see nunit-agent_{0}.log", p.Id); + log.Info("Command line: \"{0}\" {1}", p.StartInfo.FileName, p.StartInfo.Arguments); + + agentData.Add( new AgentRecord( agentId, p, null, AgentStatus.Starting ) ); + return agentId; + } + + //private void OnProcessExit(object sender, EventArgs e) + //{ + // Process p = sender as Process; + // if (p != null) + // agentData.Remove(p.Id); + //} + + //private AgentRecord FindAvailableAgent() + //{ + // foreach( AgentRecord r in agentData ) + // if ( r.Status == AgentStatus.Ready) + // { + // log.Debug( "Reusing agent {0}", r.Id ); + // r.Status = AgentStatus.Busy; + // return r; + // } + + // return null; + //} + + private ITestAgent CreateRemoteAgent(RuntimeFramework framework, int waitTime, bool enableDebug, string agentArgs) + { + Guid agentId = LaunchAgentProcess(framework, enableDebug, agentArgs); + + log.Debug( "Waiting for agent {0} to register", agentId.ToString("B") ); + + int pollTime = 200; + bool infinite = waitTime == Timeout.Infinite; + + while( infinite || waitTime > 0 ) + { + Thread.Sleep( pollTime ); + if ( !infinite ) waitTime -= pollTime; + ITestAgent agent = agentData[agentId].Agent; + if ( agent != null ) + { + log.Debug( "Returning new agent {0}", agentId.ToString("B") ); + return agent; + } + } + + return null; + } + + /// + /// Return the NUnit Bin Directory for a particular + /// runtime version, or null if it's not installed. + /// For normal installations, there are only 1.1 and + /// 2.0 directories. However, this method accomodates + /// 3.5 and 4.0 directories for the benefit of NUnit + /// developers using those runtimes. + /// + private static string GetNUnitBinDirectory(Version v) + { + // Get current bin directory + string dir = NUnitConfiguration.NUnitBinDirectory; + + // Return current directory if current and requested + // versions are both >= 2 or both 1 + if ((Environment.Version.Major >= 2) == (v.Major >= 2)) + return dir; + + // Check whether special support for version 1 is installed + if (v.Major == 1) + { + string altDir = Path.Combine(dir, "net-1.1"); + if (Directory.Exists(altDir)) + return altDir; + + // The following is only applicable to the dev environment, + // which uses parallel build directories. We try to substitute + // one version number for another in the path. + string[] search = new string[] { "2.0", "3.0", "3.5", "4.0" }; + string[] replace = v.Minor == 0 + ? new string[] { "1.0", "1.1" } + : new string[] { "1.1", "1.0" }; + + // Look for current value in path so it can be replaced + string current = null; + foreach (string s in search) + if (dir.IndexOf(s) >= 0) + { + current = s; + break; + } + + // Try the substitution + if (current != null) + { + foreach (string target in replace) + { + altDir = dir.Replace(current, target); + if (Directory.Exists(altDir)) + return altDir; + } + } + } + + return null; + } + + private static string GetTestAgentExePath(Version v) + { + string binDir = GetNUnitBinDirectory(v); + if (binDir == null) return null; + + Assembly a = System.Reflection.Assembly.GetEntryAssembly(); + string agentName = v.Major > 1 && a != null && a.GetName().ProcessorArchitecture == ProcessorArchitecture.X86 + ? "nunit-agent-x86.exe" + : "nunit-agent.exe"; + + string agentExePath = Path.Combine(binDir, agentName); + return File.Exists(agentExePath) ? agentExePath : null; + } + + #endregion + + #region IService Members + + private ServiceContext services; + public ServiceContext ServiceContext + { + get { return services; } + set { services = value; } + } + + public void UnloadService() + { + this.Stop(); + } + + public void InitializeService() + { + this.Start(); + } + + #endregion + + #region Nested Class - AgentRecord + private class AgentRecord + { + public Guid Id; + public Process Process; + public ITestAgent Agent; + public AgentStatus Status; + + public AgentRecord( Guid id, Process p, ITestAgent a, AgentStatus s ) + { + this.Id = id; + this.Process = p; + this.Agent = a; + this.Status = s; + } + + } + #endregion + + #region Nested Class - AgentDataBase + /// + /// A simple class that tracks data about this + /// agencies active and available agents + /// + private class AgentDataBase + { + private Dictionary agentData = new Dictionary(); + + public AgentRecord this[Guid id] + { + get { return (AgentRecord)agentData[id]; } + set + { + if ( value == null ) + agentData.Remove( id ); + else + agentData[id] = value; + } + } + + public AgentRecord this[ITestAgent agent] + { + get + { + foreach( KeyValuePair entry in agentData) + { + AgentRecord r = (AgentRecord)entry.Value; + if ( r.Agent == agent ) + return r; + } + + return null; + } + } + + public void Add( AgentRecord r ) + { + agentData[r.Id] = r; + } + + public void Remove(Guid agentId) + { + agentData.Remove(agentId); + } + + public void Clear() + { + agentData.Clear(); + } + + //#region IEnumerable Members + //public IEnumerator> GetEnumerator() + //{ + // return agentData.GetEnumerator(); + //} + //#endregion + } + + #endregion + } +} diff --git a/src/nunit.engine/TestEngine.cs b/src/nunit.engine/TestEngine.cs new file mode 100644 index 00000000..5c8baeaa --- /dev/null +++ b/src/nunit.engine/TestEngine.cs @@ -0,0 +1,132 @@ +// *********************************************************************** +// Copyright (c) 2011 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Diagnostics; +using System.IO; +using NUnit.Engine.Internal; +using NUnit.Engine.Services; + +namespace NUnit.Engine +{ + /// + /// The TestEngine provides services that allow a client + /// program to interact with NUnit in order to explore, + /// load and run tests. + /// + public class TestEngine : ITestEngine + { + public TestEngine() + { + Services = new ServiceContext(); + WorkDirectory = Environment.CurrentDirectory; + InternalTraceLevel = InternalTraceLevel.Default; + } + + #region Public Properties + + public ServiceContext Services { get; private set; } + + public string WorkDirectory { get; set; } + + public InternalTraceLevel InternalTraceLevel { get; set; } + + #endregion + + #region ITestEngine Members + + /// + /// Access the public IServiceLocator, first initializing + /// the services if that has not already been done. + /// + IServiceLocator ITestEngine.Services + { + get + { + if(!this.Services.ServiceManager.ServicesInitialized) + InitializeServices(); + + return this.Services; + } + } + + /// + /// Create and initialize the standard set of services + /// used in the Engine. This interface is not normally + /// called by user code. Programs linking only to + /// only to the nunit.engine.api assembly are given a + /// pre-initialized instance of TestEngine. Programs + /// that link directly to nunit.engine usually do so + /// in order to perform custom initialization. + /// + public void InitializeServices() + { + SettingsService settingsService = new SettingsService("NUnit30Settings.xml", true); + + if(InternalTraceLevel == InternalTraceLevel.Default) + InternalTraceLevel = (InternalTraceLevel)settingsService.GetSetting("Options.InternalTraceLevel", InternalTraceLevel.Off); + + if(InternalTraceLevel != InternalTraceLevel.Off) + { + var logName = string.Format("InternalTrace.{0}.log", Process.GetCurrentProcess().Id); + InternalTrace.Initialize(Path.Combine(WorkDirectory, logName), InternalTraceLevel); + } + + this.Services.Add(settingsService); + this.Services.Add(new RecentFilesService()); + this.Services.Add(new DomainManager()); + this.Services.Add(new ProjectService()); + this.Services.Add(new RuntimeFrameworkSelector()); + this.Services.Add(new DefaultTestRunnerFactory()); + this.Services.Add(new DriverFactory()); + this.Services.Add(new TestAgency()); + + this.Services.ServiceManager.InitializeServices(); + } + + /// + /// Returns a test runner for use by clients that need to load the + /// tests once and run them multiple times. If necessary, the + /// services are initialized first. + /// + /// An ITestRunner. + public ITestRunner GetRunner(TestPackage package) + { + if(!this.Services.ServiceManager.ServicesInitialized) + InitializeServices(); + + return new Runners.MasterTestRunner(this.Services, package); + } + + #endregion + + #region IDisposable Members + + public void Dispose() + { + Services.ServiceManager.StopAllServices(); + } + + #endregion + } +} diff --git a/src/nunit.engine/TestEngineResult.cs b/src/nunit.engine/TestEngineResult.cs new file mode 100644 index 00000000..b703d085 --- /dev/null +++ b/src/nunit.engine/TestEngineResult.cs @@ -0,0 +1,152 @@ +// *********************************************************************** +// Copyright (c) 2011-2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Xml; +using NUnit.Engine.Internal; + +namespace NUnit.Engine +{ + /// + /// Wrapper class for the xml-formatted results produced + /// by the test engine for most operations. The XML is + /// stored as a string in order to allow serialization + /// and actual XmlNodes are created on demand. + /// + /// In principal, there should only be one XmlNode in + /// a result. However, as work progresses, there may + /// temporarily be multiple nodes, which have not yet + /// been aggregated under a higher level suite. For + /// that reason, TestEngineResult maintains a list + /// of XmlNodes and another of the corresponding text. + /// + /// Static methods are provided for aggregating the + /// internal XmlNodes into a single node as well as + /// for combining multiple TestEngineResults into one. + /// + /// + [Serializable] + public class TestEngineResult + { + private List _xmlText = new List(); + + [NonSerialized] + private List _xmlNodes = new List(); + + #region Constructors + + /// + /// Construct a TestResult from an XmlNode + /// + /// An XmlNode representing the result + public TestEngineResult(XmlNode xml) + { + this._xmlNodes.Add(xml); + this._xmlText.Add(xml.OuterXml); + } + + /// + /// Construct a test from a string holding xml + /// + /// A string containing the xml result + public TestEngineResult(string xml) + { + this._xmlText.Add(xml); + } + + /// + /// Default constructor used when adding multiple results + /// + public TestEngineResult() + { + } + + #endregion + + #region Properties + + /// + /// Gets a flag indicating whether this is a single result + /// having only one XmlNode associated with it. + /// + public bool IsSingle + { + get { return _xmlText.Count == 1; } + } + + /// + /// Gets the xml representing a test result as an XmlNode + /// + public IList XmlNodes + { + get + { + // xmlNodes might be null after deserialization + if (_xmlNodes == null) + _xmlNodes = new List(); + + for (int i = _xmlNodes.Count; i < _xmlText.Count; i++) + { + XmlDocument doc = new XmlDocument(); + doc.LoadXml(_xmlText[i]); + _xmlNodes.Add(doc.FirstChild); + } + + return _xmlNodes; + } + } + + /// + /// Gets the XML representing a single test result. + /// + /// + /// If the result is empty or has multiple XML nodes. + /// + public XmlNode Xml + { + get + { + // TODO: Fix this in some way and also allow for a count of zero + if (!IsSingle) + throw new InvalidOperationException("May not use 'Xml' property on a result with multiple XmlNodes"); + + return XmlNodes[0]; + } + } + + public void Add(string xml) + { + this._xmlText.Add(xml); + } + + public void Add(XmlNode xml) + { + this._xmlText.Add(xml.OuterXml); + this._xmlNodes.Add(xml); + } + + #endregion + } +} diff --git a/src/nunit.engine/TestRun.cs b/src/nunit.engine/TestRun.cs new file mode 100644 index 00000000..51faf64b --- /dev/null +++ b/src/nunit.engine/TestRun.cs @@ -0,0 +1,88 @@ +// *********************************************************************** +// Copyright (c) 2014 Charlie Poole +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; +using System.Xml; + +namespace NUnit.Engine +{ + /// + /// The TestRun class encapsulates an ongoing test run. + /// + public class TestRun : ITestRun + { + private BackgroundWorker _worker; + private ITestEngineRunner _runner; + private TestEngineResult _result; + + /// + /// Construct a new TestRun + /// + /// The ITestEventListener to use for this run + /// The TestFilter to use for this run + public TestRun(ITestEngineRunner runner) + { + _runner = runner; + + _worker = new BackgroundWorker(); + _worker.WorkerSupportsCancellation = true; + _worker.WorkerReportsProgress = true; // ? + } + + public XmlNode Result + { + get + { + if (_result == null) + throw new InvalidOperationException("Cannot retrieve Result from an incomplete or cancelled TestRun."); + + return _result.Xml; + } + } + + /// + /// Start asynchronous execution of a test. + /// + /// The ITestEventListener to use for this run + /// The TestFilter to use for this run + public void Start(ITestEventListener listener, TestFilter filter) + { + _worker.DoWork += (s, ea) => + _result = _runner.Run(listener, filter); + + _worker.RunWorkerAsync(); + } + + /// + /// Stop the current test run. + /// + /// If true, force the stop by cancelling all threads. + public void Stop(bool force) + { + _worker.CancelAsync(); + } + } +} diff --git a/src/nunit.engine/nunit.engine.build b/src/nunit.engine/nunit.engine.build new file mode 100644 index 00000000..7fabe790 --- /dev/null +++ b/src/nunit.engine/nunit.engine.build @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/nunit.engine/nunit.engine.csproj b/src/nunit.engine/nunit.engine.csproj new file mode 100644 index 00000000..cb241bbc --- /dev/null +++ b/src/nunit.engine/nunit.engine.csproj @@ -0,0 +1,138 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {372A3447-D657-40FF-A089-77C19FEC30C8} + Library + Properties + NUnit.Engine + nunit.engine + v2.0 + 512 + + + 3.5 + + + + true + full + false + ..\..\bin\Debug\ + TRACE;DEBUG + prompt + 4 + + + pdbonly + true + ..\..\bin\Release\ + TRACE + prompt + 4 + + + false + + + + + + + + + + + + + + + + + CommonAssemblyInfo.cs + + + CommonVersionInfo.cs + + + + + + + + + + + + + + + + + + + + + Code + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {775FAD50-3623-4922-97C4-DFB29A8BE4C7} + nunit.engine.api + + + + + \ No newline at end of file