From 973a0f12a062124c59fbb03cd579acb01f5d6633 Mon Sep 17 00:00:00 2001 From: Filip Staffa Date: Tue, 5 Jan 2016 23:27:45 +0100 Subject: [PATCH] added NUnit3 support --- src/app/FakeLib/DotCover.fs | 31 +++ src/app/FakeLib/FakeLib.fsproj | 1 + src/app/FakeLib/UnitTest/NUnit/Common.fs | 4 +- src/app/FakeLib/UnitTest/NUnit/NUnit3.fs | 280 +++++++++++++++++++++++ 4 files changed, 314 insertions(+), 2 deletions(-) create mode 100644 src/app/FakeLib/UnitTest/NUnit/NUnit3.fs diff --git a/src/app/FakeLib/DotCover.fs b/src/app/FakeLib/DotCover.fs index d22bfe4a03f..5a94ae9874a 100644 --- a/src/app/FakeLib/DotCover.fs +++ b/src/app/FakeLib/DotCover.fs @@ -6,6 +6,7 @@ open System open System.IO open System.Text open Fake.Testing.XUnit2 +open Fake.Testing.NUnit3 open Fake.MSTest type DotCoverReportType = @@ -194,6 +195,36 @@ let DotCoverNUnit (setDotCoverParams: DotCoverParams -> DotCoverParams) (setNUni traceEndTask "DotCoverNUnit" details +/// Runs the dotCover "cover" command against the NUnit test runner. +/// ## Parameters +/// +/// - `setDotCoverParams` - Function used to overwrite the dotCover report default parameters. +/// - `setNUnitParams` - Function used to overwrite the NUnit default parameters. +/// +/// ## Sample +/// +/// !! (buildDir @@ buildMode @@ "/*.Unit.Tests.dll") +/// |> DotCoverNUnit +/// (fun dotCoverOptions -> { dotCoverOptions with +/// Output = artifactsDir @@ "NUnitDotCoverSnapshot.dcvr" }) +/// (fun nUnitOptions -> { nUnitOptions with +/// DisableShadowCopy = true }) +let DotCoverNUnit3 (setDotCoverParams: DotCoverParams -> DotCoverParams) (setNUnitParams: NUnit3Params -> NUnit3Params) (assemblies: string seq) = + let assemblies = assemblies |> Seq.toArray + let details = assemblies |> separated ", " + traceStartTask "DotCoverNUnit" details + + let parameters = NUnit3Defaults |> setNUnitParams + let args = buildNUnit3Args parameters assemblies + + DotCover (fun p -> + {p with + TargetExecutable = parameters.ToolPath + TargetArguments = args + } |> setDotCoverParams) + + traceEndTask "DotCoverNUnit" details + /// Runs the dotCover "cover" command against the XUnit2 test runner. /// ## Parameters /// diff --git a/src/app/FakeLib/FakeLib.fsproj b/src/app/FakeLib/FakeLib.fsproj index 62ad2572b02..22aae55fba6 100644 --- a/src/app/FakeLib/FakeLib.fsproj +++ b/src/app/FakeLib/FakeLib.fsproj @@ -68,6 +68,7 @@ + diff --git a/src/app/FakeLib/UnitTest/NUnit/Common.fs b/src/app/FakeLib/UnitTest/NUnit/Common.fs index da6eba0b277..2186d602b35 100644 --- a/src/app/FakeLib/UnitTest/NUnit/Common.fs +++ b/src/app/FakeLib/UnitTest/NUnit/Common.fs @@ -104,11 +104,11 @@ type NUnitParams = /// ## Defaults /// - `IncludeCategory` - `""` /// - `ExcludeCategory` - `""` -/// - `ToolPath` - `""` +/// - `ToolPath` - The `nunit-console.exe` path if it exists in a subdirectory of the current directory. /// - `ToolName` - `"nunit-console.exe"` /// - `DontTestInNewThread`- `false` /// - `StopOnError` - `false` -/// - `OutputFile` - The `nunit-console.exe` path if it exists in a subdirectory of the current directory. +/// - `OutputFile` - `"TestResult.xml"` /// - `Out` - `""` /// - `ErrorOutputFile` - `""` /// - `WorkingDir` - `""` diff --git a/src/app/FakeLib/UnitTest/NUnit/NUnit3.fs b/src/app/FakeLib/UnitTest/NUnit/NUnit3.fs new file mode 100644 index 00000000000..2792db8054e --- /dev/null +++ b/src/app/FakeLib/UnitTest/NUnit/NUnit3.fs @@ -0,0 +1,280 @@ +[] +module Fake.Testing.NUnit3 + +open System +open System.Text +open System.IO +open Fake + +/// Process model for NUnit 3 to use. +type NUnit3ProcessModel = + | DefaultProcessModel + | SingleProcessModel + | SeparateProcessModel + | MultipleProcessModel with + member x.ParamString = + match x with + | DefaultProcessModel -> "" + | SingleProcessModel -> "Single" + | SeparateProcessModel -> "Separate" + | MultipleProcessModel -> "Multiple" +/// The --domain option controls of the creation of AppDomains for running tests. See [NUnit-Console Command Line Options](http://www.nunit.org/index.php?p=consoleCommandLine&r=2.6.4) +type NUnit3DomainModel = + /// The default is to use multiple domains if multiple assemblies are listed on the command line. Otherwise a single domain is used. + | DefaultDomainModel + /// No domain is created - the tests are run in the primary domain. This normally requires copying the NUnit assemblies into the same directory as your tests. + | NoDomainModel + /// A test domain is created - this is how NUnit worked prior to version 2.4 + | SingleDomainModel + /// A separate test domain is created for each assembly + | MultipleDomainModel with + member x.ParamString = + match x with + | DefaultDomainModel -> "" + | NoDomainModel -> "None" + | SingleDomainModel -> "Single" + | MultipleDomainModel -> "Multiple" + +/// The --framework option in running NUnit 3. There are three kinds - VXY, which means either .NET framework or Mono, NetXY (use .NET framework with given version) +/// and MonoXY (Mono framework with given version). You can use Net or Mono to let NUnit select the version. +/// You can pass any value using Other. +type NUnit3Runtime = + /// Uses the runtime under which the assembly was built. + | Default + | V20 + | V30 + | V35 + | V40 + | V45 + /// NUnit should use .NET framework but can select it's version + | Net + | Net20 + | Net30 + | Net35 + | Net40 + | Net45 + /// NUnit should use Mono framework but can select it's version + | Mono + | Mono20 + | Mono30 + | Mono35 + | Mono40 + /// NUnit should use runtime specified by this value + | Other of string with + member x.ParamString = + match x with + | Default -> "" + | V20 -> "v2.0" + | V30 -> "v3.0" + | V35 -> "v3.5" + | V40 -> "v4.0" + | V45 -> "v4.5" + | Net -> "net" + | Net20 -> "net-2.0" + | Net30 -> "net-3.0" + | Net35 -> "net-3.5" + | Net40 -> "net-4.0" + | Net45 -> "net-4.5" + | Mono20 -> "mono-2.0" + | Mono30 -> "mono-3.0" + | Mono35 -> "mono-3.5" + | Mono40 -> "mono-4.0" + | Other(name) -> name + +/// Option which allows to specify if a NUnit error should break the build. +type NUnit3ErrorLevel = TestRunnerErrorLevel + +/// The NUnit 3 Console Parameters type. FAKE will use [NUnit3Defaults](fake-testing-nunit3.html) for values not provided. +/// +/// For reference, see: [NUnit3 command line options](https://github.com/nunit/nunit/wiki/Console-Command-Line) +type NUnit3Params = + { /// The path to the NUnit3 console runner: `nunit3-console.exe` + ToolPath : string + + /// The name (or path) of a file containing a list of tests to run or explore, one per line. + Testlist : string + + /// An expression indicating which tests to run. It may specify test names, classes, methods, + /// catgories or properties comparing them to actual values with the operators ==, !=, =~ and !~. + /// See [NUnit documentation](https://github.com/nunit/nunit/wiki/Test-Selection-Language) for a full description of the syntax. + Where : string + + /// Name of a project configuration to load (e.g.: Debug) + Config : string + + /// Controls how NUnit loads tests in processes. See [NUnit3ProcessModel](fake-testing-nunit3-nunit3processmodel.html) + ProcessModel : NUnit3ProcessModel + + /// Number of agents that may be allowed to run simultaneously assuming you are not running inprocess. + /// If not specified, all agent processes run tests at the same time, whatever the number of assemblies. + /// This setting is used to control running your assemblies in parallel. + Agents : int option + + /// Controls how NUnit loads tests in processes. See: [NUnit3ProcessModel](fake-testing-nunit3-nunit3domainmodel.html). + Domain : NUnit3DomainModel + + /// Allows you to specify the version of the runtime to be used in executing tests. + /// Default value is runtime under which the assembly was built. See: [NUnit3Runtime](fake-testing-nunit3-nunit3runtime.html). + Framework : NUnit3Runtime + + /// Run tests in a 32-bit process on 64-bit systems. + Force32bit : bool + + /// Dispose each test runner after it has finished running its tests + DisposeRunners : bool + + /// The default timeout to be used for test cases. If any test exceeds the timeout value, it is cancelled and reported as an error. + TimeOut : TimeSpan + + /// Set the random seed used to generate test cases + Seed : int + + /// Specify the NUMBER of worker threads to be used in running tests. + /// This setting is used to control running your tests in parallel and is used in conjunction with the Parallelizable Attribute. + /// If not specified, workers defaults to the number of processors on the machine, or 2, whichever is greater. + Workers : int option + + /// Causes execution of the test run to terminate immediately on the first test failure or error. + StopOnError : bool + + /// Path of the directory to use for output files. + WorkingDir : string + + /// File path to contain text output from the tests. + OutputDir : string + + /// File path to contain error output from the tests. + ErrorDir : string + + /// Output specs for saving the test results. Default value is TestResult.xml + /// Passing empty list does not save any result (--noresult option in nunit) + /// For more information, see: [NUnit3 command line options](https://github.com/nunit/nunit/wiki/Console-Command-Line) + ResultSpecs : string list + + /// Tells .NET to copy loaded assemblies to the shadowcopy directory. + ShadowCopy : bool + + /// Turns on use of TeamCity service messages. + TeamCity : bool + + /// Default: [TestRunnerErrorLevel](fake-unittestcommon-testrunnererrorlevel.html).Error + ErrorLevel : NUnit3ErrorLevel + } + +/// The [NUnit3Params](fake-testing-nunit3-nunit3params.html) default parameters. +/// +/// - `ToolPath` - The `nunit-console.exe` path if it exists in a subdirectory of the current directory. +/// - `Testlist` - `""` +/// - `Where` - `""` +/// - `Config` - `""` +/// - `ProcessModel` - `DefaultProcessModel` +/// - `Agents` - `None` +/// - `Domain` - `DefaultDomainModel` +/// - `Framework` - `""` +/// - `Force32bit` - `false` +/// - `DisposeRunners` - `false` +/// - `Timeout` - `2147483647 milliseconds` +/// - `Seed` - `-1` (negative seed is ignored by NUnit and is not sent to it) +/// - `Workers` - `None` +/// - `StopOnError` - `false` +/// - `WorkingDir` - `""` +/// - `OutputDir` - `""` +/// - `ErrorDir` - `""` +/// - `ResultSpecs` - `"TestResult.xml"` +/// - `ShadowCopy` - `false` +/// - `TeamCity` - `false` +/// - `ErrorLevel` - `Error` +/// ## Defaults +let NUnit3Defaults = + { + ToolPath = findToolInSubPath "nunit3-console.exe" (currentDirectory @@ "tools" @@ "Nunit") + Testlist = "" + Where = "" + Config = "" + ProcessModel = DefaultProcessModel + Agents = None + Domain = DefaultDomainModel + Framework = Default + Force32bit = false + DisposeRunners = false + TimeOut = TimeSpan.FromMilliseconds((float)Int32.MaxValue) + Seed = -1 + Workers = None + StopOnError = false + WorkingDir = "" + OutputDir = "" + ErrorDir = "" + ResultSpecs = [currentDirectory @@ "TestResult.xml"] + ShadowCopy = false + TeamCity = false + ErrorLevel = Error + } + +/// Tries to detect the working directory as specified in the parameters or via TeamCity settings +/// [omit] +let getWorkingDir parameters = + Seq.find isNotNullOrEmpty [ parameters.WorkingDir + environVar ("teamcity.build.workingDir") + "." ] + |> Path.GetFullPath + +let buildNUnit3Args parameters assemblies = + let appendResultString results sb = + match results, sb with + | [], sb -> append "--noresult" sb + | x, sb when x = NUnit3Defaults.ResultSpecs -> sb + | results, sb -> (sb, results) ||> Seq.fold (fun builder str -> append (sprintf "--result=%s" str) builder) + + new StringBuilder() + |> append "--noheader" + |> appendIfNotNullOrEmpty parameters.Testlist "--testlist=" + |> appendIfNotNullOrEmpty parameters.Where "--where=" + |> appendIfNotNullOrEmpty parameters.Config "--config=" + |> appendIfNotNullOrEmpty parameters.ProcessModel.ParamString "--process=" + |> appendIfSome parameters.Agents (sprintf "--agents=%i") + |> appendIfNotNullOrEmpty parameters.Domain.ParamString "--domain=" + |> appendIfNotNullOrEmpty parameters.Framework.ParamString "--framework=" + |> appendIfTrue parameters.Force32bit "--x86" + |> appendIfTrue parameters.DisposeRunners "--dispose-runners" + |> appendIfTrue (parameters.TimeOut <> NUnit3Defaults.TimeOut) (sprintf "--timeout=%i" (int parameters.TimeOut.TotalMilliseconds)) + |> appendIfTrue (parameters.Seed >= 0) (sprintf "--seed=%i" parameters.Seed) + |> appendIfSome parameters.Workers (sprintf "--workers=%i") + |> appendIfTrue parameters.StopOnError "--stoponerror" + |> appendIfNotNullOrEmpty parameters.WorkingDir "--work=" + |> appendIfNotNullOrEmpty parameters.OutputDir "--output=" + |> appendIfNotNullOrEmpty parameters.ErrorDir "--err=" + |> appendResultString parameters.ResultSpecs + |> appendIfTrue parameters.ShadowCopy "--shadowcopy" + |> appendIfTrue parameters.TeamCity "--teamcity" + |> appendFileNamesIfNotNull assemblies + |> toText + +let NUnit3 (setParams : NUnit3Params -> NUnit3Params) (assemblies : string seq) = + let details = assemblies |> separated ", " + traceStartTask "NUnit" details + let parameters = NUnit3Defaults |> setParams + let assemblies = assemblies |> Seq.toArray + if Array.isEmpty assemblies then failwith "NUnit: cannot run tests (the assembly list is empty)." + let tool = parameters.ToolPath + let args = buildNUnit3Args parameters assemblies + trace (tool + " " + args) + let result = + ExecProcess (fun info -> + info.FileName <- tool + info.WorkingDirectory <- getWorkingDir parameters + info.Arguments <- args) parameters.TimeOut + let errorDescription error = + match error with + | OK -> "OK" + | TestsFailed -> sprintf "NUnit test failed (%d)." error + | FatalError x -> sprintf "NUnit test failed. Process finished with exit code %s (%d)." x error + + match parameters.ErrorLevel with + | DontFailBuild -> + match result with + | OK | TestsFailed -> traceEndTask "NUnit" details + | _ -> raise (FailedTestsException(errorDescription result)) + | Error | FailOnFirstError -> + match result with + | OK -> traceEndTask "NUnit" details + | _ -> raise (FailedTestsException(errorDescription result))