From f73b02ccd2af5ea4becd53e5c6730001447cb646 Mon Sep 17 00:00:00 2001 From: Martin Gondermann Date: Tue, 9 Oct 2018 09:03:46 +0200 Subject: [PATCH 1/4] Update Fake.Tools.Pickles and added Unit tests --- .../Fake.Tools.Pickles.fsproj | 5 +- src/app/Fake.Tools.Pickles/Pickles.fs | 317 +++++++++++------- src/app/Fake.Tools.Pickles/VisibleTo.fs | 7 + .../Fake.Core.UnitTests.fsproj | 2 + .../Fake.Core.UnitTests/Fake.Tools.Pickles.fs | 73 ++++ 5 files changed, 276 insertions(+), 128 deletions(-) create mode 100644 src/app/Fake.Tools.Pickles/VisibleTo.fs create mode 100644 src/test/Fake.Core.UnitTests/Fake.Tools.Pickles.fs diff --git a/src/app/Fake.Tools.Pickles/Fake.Tools.Pickles.fsproj b/src/app/Fake.Tools.Pickles/Fake.Tools.Pickles.fsproj index 788dd9877ee..d50b8ee3a8b 100644 --- a/src/app/Fake.Tools.Pickles/Fake.Tools.Pickles.fsproj +++ b/src/app/Fake.Tools.Pickles/Fake.Tools.Pickles.fsproj @@ -5,14 +5,15 @@ Fake.Tools.Pickles Library - + $(DefineConstants);NETSTANDARD;USE_HTTPCLIENT - + $(DefineConstants);RELEASE + diff --git a/src/app/Fake.Tools.Pickles/Pickles.fs b/src/app/Fake.Tools.Pickles/Pickles.fs index e21c5596bb9..0f37e9fc03b 100644 --- a/src/app/Fake.Tools.Pickles/Pickles.fs +++ b/src/app/Fake.Tools.Pickles/Pickles.fs @@ -17,16 +17,16 @@ module Fake.Tools.Pickles open System -open System.Text +open System.IO + open Fake.Core open Fake.IO open Fake.IO.Globbing open Fake.IO.FileSystemOperators -open System.IO (* .\packages\Pickles.CommandLine\tools\pickles.exe --help -Pickles version 2.18.1.0 +Pickles version 2.19.0.0 -f, --feature-directory=VALUE directory to start scanning recursively for features @@ -54,60 +54,70 @@ Pickles version 2.18.1.0 whether to enable comments in the output --et, --excludeTags=VALUE exclude scenarios that match this tag -*) + --ht, --hideTags=VALUE Technical tags that shouldn't be displayed + (separated by ;)*) /// Option which allows to specify if failure of pickles should break the build. -type ErrorLevel = - /// This option instructs FAKE to break the build if pickles fails to execute - | Error - /// With this option set, no exception is thrown if pickles fails to execute - | DontFailBuild +type ErrorLevel = + /// This option instructs FAKE to break the build if pickles fails to execute + | Error + /// With this option set, no exception is thrown if pickles fails to execute + | DontFailBuild /// The format of the test results type TestResultsFormat = - | NUnit - | NUnit3 - | XUnit - | XUnit2 - | MSTest - | CucumberJSON - | SpecRun - | VSTest + | NUnit + | NUnit3 + | XUnit + | XUnit2 + | MSTest + | CucumberJSON + | SpecRun + | VSTest type DocumentationFormat = - | DHTML - | HTML - | Word - | JSON - | Excel + | DHTML + | HTML + | Word + | JSON + | Excel + | CucumberJSON /// The Pickles parameter type type PicklesParams = - { /// The path to the Pickles console tool: 'pickles.exe' - ToolPath : string - /// The directory to start scanning recursively for features - FeatureDirectory: string - /// The language of the feature files - FeatureFileLanguage: string option - /// The directory where output files will be placed - OutputDirectory: string - /// The format of the output documentation - OutputFileFormat: DocumentationFormat - /// the format of the linked test results - TestResultsFormat: TestResultsFormat - /// the paths to the linked test results files - LinkedTestResultFiles: string list - /// The name of the system under test - SystemUnderTestName: string option - /// The version of the system under test - SystemUnderTestVersion: string option - /// Maximum time to allow xUnit to run before being killed. - TimeOut : TimeSpan - /// Option which allows to specify if failure of pickles should break the build. - ErrorLevel : ErrorLevel - /// Option which allows to enable some experimental features - IncludeExperimentalFeatures : bool option - } + { /// The path to the Pickles console tool: 'pickles.exe' + ToolPath : string + /// The working directory + WorkingDir: string + /// The directory to start scanning recursively for features + FeatureDirectory: string + /// The language of the feature files + FeatureFileLanguage: string option + /// The directory where output files will be placed + OutputDirectory: string + /// The format of the output documentation + OutputFileFormat: DocumentationFormat + /// the format of the linked test results + TestResultsFormat: TestResultsFormat + /// the paths to the linked test results files + LinkedTestResultFiles: string list + /// The name of the system under test + SystemUnderTestName: string option + /// The version of the system under test + SystemUnderTestVersion: string option + /// Maximum time to allow xUnit to run before being killed. + TimeOut : TimeSpan + /// Option which allows to specify if failure of pickles should break the build. + ErrorLevel : ErrorLevel + /// Option which allows to enable some experimental features + IncludeExperimentalFeatures : bool option + /// As of version 2.6, Pickles includes Gherkin #-style comments. As of version 2.7, this inclusion is configurable. + EnableComments: bool option + /// exclude scenarios that match this tags + ExcludeTags: string list + /// Technical tags that shouldn't be displayed + HideTags: string list + } let private currentDirectory = Directory.GetCurrentDirectory() @@ -127,82 +137,141 @@ let private currentDirectory = Directory.GetCurrentDirectory() /// - `TimeOut` - 5 minutes /// - `ErrorLevel` - `Error` /// - `IncludeExperimentalFeatures` - `None` +/// - `EnableComments` - true +/// - `ExcludeTags` - [] +/// - `HideTags` - [] let private PicklesDefaults = - { - ToolPath = Tools.findToolInSubPath "pickles.exe" currentDirectory - FeatureDirectory = currentDirectory - FeatureFileLanguage = None - OutputDirectory = currentDirectory "Documentation" - OutputFileFormat = DHTML - TestResultsFormat = NUnit - LinkedTestResultFiles = [] - SystemUnderTestName = None - SystemUnderTestVersion = None - TimeOut = TimeSpan.FromMinutes 5. - ErrorLevel = Error - IncludeExperimentalFeatures = None - } + { + ToolPath = Tools.findToolInSubPath "pickles.exe" currentDirectory + WorkingDir = currentDirectory + FeatureDirectory = null + FeatureFileLanguage = None + OutputDirectory = null + OutputFileFormat = DHTML + TestResultsFormat = NUnit + LinkedTestResultFiles = [] + SystemUnderTestName = None + SystemUnderTestVersion = None + TimeOut = TimeSpan.FromMinutes 5. + ErrorLevel = Error + IncludeExperimentalFeatures = None + EnableComments = None + ExcludeTags = [] + HideTags = [] + } let private buildPicklesArgs parameters = - let outputFormat = match parameters.OutputFileFormat with - | DHTML -> "dhtml" - | HTML -> "html" - | Word -> "word" - | JSON -> "json" - | Excel -> "excel" - - let testResultFormat = match parameters.LinkedTestResultFiles with - | [] -> None - | _ -> match parameters.TestResultsFormat with - | NUnit -> Some "nunit" - | NUnit3 -> Some "nunit3" - | XUnit -> Some "xunit" - | XUnit2 -> Some "xunit2" - | MSTest -> Some "mstest" - | CucumberJSON -> Some "cucumberjson" - | SpecRun -> Some "specrun" - | VSTest -> Some "vstest" - - let linkedResultFiles = match parameters.LinkedTestResultFiles with - | [] -> None - | _ -> parameters.LinkedTestResultFiles - |> Seq.map (fun f -> sprintf "\"%s\"" f) - |> String.concat ";" - |> Some - let experimentalFeatures = match parameters.IncludeExperimentalFeatures with - | Some true -> Some "--exp" - | _ -> None + let experimentalFeatures = + seq { + match parameters.IncludeExperimentalFeatures with + | Some true -> yield "--exp" + | _ -> () + } - new StringBuilder() - |> StringBuilder.appendWithoutQuotes (sprintf " -f \"%s\"" parameters.FeatureDirectory) - |> StringBuilder.appendWithoutQuotes (sprintf " -o \"%s\"" parameters.OutputDirectory) - |> StringBuilder.appendIfSome parameters.SystemUnderTestName (sprintf " --sn %s") - |> StringBuilder.appendIfSome parameters.SystemUnderTestVersion (sprintf " --sv %s") - |> StringBuilder.appendIfSome parameters.FeatureFileLanguage (sprintf " -l %s") - |> StringBuilder.appendWithoutQuotes (sprintf " --df %s" outputFormat) - |> StringBuilder.appendIfSome testResultFormat (sprintf " --trfmt %s") - |> StringBuilder.appendIfSome linkedResultFiles (sprintf " --lr %s") - |> StringBuilder.appendIfSome experimentalFeatures (sprintf "%s") - |> StringBuilder.toText - -module internal ResultHandling = - let (|OK|Failure|) = function - | 0 -> OK - | x -> Failure x + let enableComments = + seq { + match parameters.EnableComments with + | Some true -> yield "--enableComments=true" + | Some false -> yield "--enableComments=false" + | _ -> () + } + + let yieldIfNotNullOrWhitespace paramName value = + seq { + if String.isNullOrWhiteSpace value + then () + else + yield sprintf "-%s" paramName + yield value + } + + let yieldIfSome paramName value = + seq { + match value with + | Some v -> + yield sprintf "--%s" paramName + yield v + | _ -> () + } + + let yieldTags paramName value = + seq { + match value with + | [] -> () + | tags -> + yield sprintf "--%s" paramName + yield tags |> String.concat ";" + } + + [ + yield! parameters.FeatureDirectory |> yieldIfNotNullOrWhitespace "f" + yield! parameters.OutputDirectory |> yieldIfNotNullOrWhitespace "o" + yield! parameters.SystemUnderTestName |> yieldIfSome "sn" + yield! parameters.SystemUnderTestVersion |> yieldIfSome "sv" + yield! parameters.FeatureFileLanguage |> yieldIfSome "l" + yield! match parameters.OutputFileFormat |> string |> String.toLower with + | "html" -> None + | v -> Some v + |> yieldIfSome "df" + yield! match parameters.LinkedTestResultFiles with + | [] -> None + | _ -> parameters.TestResultsFormat + |> string + |> String.toLower + |> Some + |> yieldIfSome "trfmt" + yield! match parameters.LinkedTestResultFiles with + | [] -> None + | _ -> parameters.LinkedTestResultFiles + |> String.concat ";" + |> Some + |> yieldIfSome "lr" + yield! experimentalFeatures + yield! enableComments + yield! parameters.ExcludeTags |> yieldTags "et" + yield! parameters.HideTags |> yieldTags "ht" + ] + |> Arguments.OfArgs + +module internal ResultHandling = + let (|OK|Failure|) = function + | 0 -> OK + | x -> Failure x - let buildErrorMessage = function - | OK -> None - | Failure errorCode -> - Some (sprintf "Pickles reported an error (Error code %d)" errorCode) + let buildErrorMessage = function + | OK -> None + | Failure errorCode -> + Some (sprintf "Pickles reported an error (Error code %d)" errorCode) - let failBuildWithMessage = function - | DontFailBuild -> Trace.traceImportant - | _ -> failwith + let failBuildWithMessage = function + | DontFailBuild -> Trace.traceImportant + | _ -> failwith - let failBuildIfPicklesReportedError errorLevel = - buildErrorMessage - >> Option.iter (failBuildWithMessage errorLevel) + let failBuildIfPicklesReportedError errorLevel = + buildErrorMessage + >> Option.iter (failBuildWithMessage errorLevel) + +/// Builds the report generator command line arguments and process from the given parameters and reports +/// [omit] +let internal createProcess setParams = + let parameters = setParams PicklesDefaults + let args = buildPicklesArgs parameters + let tool = parameters.ToolPath + + CreateProcess.fromCommand (RawCommand(tool, args)) + |> CreateProcess.withFramework + |> CreateProcess.withWorkingDirectory parameters.WorkingDir + |> CreateProcess.withTimeout parameters.TimeOut + |> CreateProcess.addOnExited + (fun data exitCode -> + ResultHandling.failBuildIfPicklesReportedError parameters.ErrorLevel exitCode + data) + |> fun command -> + Trace.trace command.CommandLine + command + + /// Runs pickles living documentation generator via the given tool /// Will fail if the pickles command line tool terminates with a non zero exit code. /// @@ -212,15 +281,11 @@ module internal ResultHandling = /// ## Parameters /// - `setParams` - Function used to manipulate the default `PicklesParams` value let convert setParams = - use __ = Trace.traceTask "Pickles" "" - let parameters = setParams PicklesDefaults - let makeProcessStartInfo info = - { info with FileName = parameters.ToolPath - WorkingDirectory = "." - Arguments = parameters |> buildPicklesArgs } - |> Process.withFramework - - let result = Process.execSimple makeProcessStartInfo parameters.TimeOut + use __ = Trace.traceTask "Pickles" "Generating documentations" + + let result = + createProcess setParams + |> Proc.run + |> ignore - ResultHandling.failBuildIfPicklesReportedError parameters.ErrorLevel result - __.MarkSuccess() + __.MarkSuccess() diff --git a/src/app/Fake.Tools.Pickles/VisibleTo.fs b/src/app/Fake.Tools.Pickles/VisibleTo.fs new file mode 100644 index 00000000000..9a497f59ddc --- /dev/null +++ b/src/app/Fake.Tools.Pickles/VisibleTo.fs @@ -0,0 +1,7 @@ + +namespace System +open System.Runtime.CompilerServices + +[] +[] +do () \ No newline at end of file diff --git a/src/test/Fake.Core.UnitTests/Fake.Core.UnitTests.fsproj b/src/test/Fake.Core.UnitTests/Fake.Core.UnitTests.fsproj index 9a4b46e464b..e488eddcf18 100644 --- a/src/test/Fake.Core.UnitTests/Fake.Core.UnitTests.fsproj +++ b/src/test/Fake.Core.UnitTests/Fake.Core.UnitTests.fsproj @@ -9,6 +9,7 @@ + @@ -33,6 +34,7 @@ + diff --git a/src/test/Fake.Core.UnitTests/Fake.Tools.Pickles.fs b/src/test/Fake.Core.UnitTests/Fake.Tools.Pickles.fs new file mode 100644 index 00000000000..7351c25b4e8 --- /dev/null +++ b/src/test/Fake.Core.UnitTests/Fake.Tools.Pickles.fs @@ -0,0 +1,73 @@ +module Fake.Tools.Pickles.Tests + +open System.IO +open Fake.Core +open Fake.Testing +open Fake.Tools +open Expecto + +let runCreateProcess setParams = + let cp = + Pickles.createProcess + (fun param -> + { setParams param with + ToolPath = Path.Combine("pickles", "pickles.exe") } ) + + let file, args = + match cp.Command with + | RawCommand(file, args) -> file, args + | _ -> failwithf "expected RawCommand" + |> ArgumentHelper.checkIfMono + + let expectedPath = Path.Combine("pickles", "pickles.exe") + Expect.equal file expectedPath "Expected pickles.exe" + + expectedPath, (RawCommand(file,args)).CommandLine + + +[] +let tests = + testList "Fake.Tools.Pickles.Tests" [ + testCase "Test that new argument generation works with minimal parameters" <| fun _ -> + let expectedPath, commandLine = + runCreateProcess id + + Expect.equal + commandLine + (sprintf "%s --df dhtml" expectedPath) + "expected proper command line" + + testCase "Test that new argument generation works with all parameters" <| fun _ -> + let expectedPath, commandLine = + runCreateProcess + (fun param -> + { param with + FeatureDirectory = "features" + OutputDirectory = "output" + SystemUnderTestName = Some "sut" + SystemUnderTestVersion = Some "sutver" + FeatureFileLanguage = Some "de" + OutputFileFormat = Pickles.DocumentationFormat.DHTML + LinkedTestResultFiles = [ "TestResult1.xml"; "TestResult2.xml" ] + TestResultsFormat = Pickles.TestResultsFormat.XUnit2 + IncludeExperimentalFeatures = Some true + EnableComments = Some false + ExcludeTags = [ "et1"; "et2" ] + HideTags = [ "ht1"; "ht2" ] }) + + Expect.equal + commandLine + (sprintf "%s -f features -o output --sn sut --sv sutver --l de --df dhtml --trfmt xunit2 --lr TestResult1.xml;TestResult2.xml --exp --enableComments=false --et et1;et2 --ht ht1;ht2" expectedPath) "expected proper command line" + + testCase "Test that output file format is ommitted if it is HTML" <| fun _ -> + let expectedPath, commandLine = + runCreateProcess + (fun param -> + { param with + OutputFileFormat = Pickles.DocumentationFormat.HTML }) + + Expect.equal + commandLine + (sprintf "%s " expectedPath) + "expected proper command line" + ] From ae86e1f7a1294357d1b2f1cb9bf9eb6882c987d1 Mon Sep 17 00:00:00 2001 From: Martin Gondermann Date: Fri, 12 Oct 2018 14:53:48 +0200 Subject: [PATCH 2/4] Fix Tests --- src/test/Fake.Core.UnitTests/Fake.DotNet.Testing.SpecFlow.fs | 2 +- src/test/Fake.Core.UnitTests/Fake.Testing.ReportGenerator.fs | 3 ++- src/test/Fake.Core.UnitTests/Fake.Tools.Pickles.fs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/test/Fake.Core.UnitTests/Fake.DotNet.Testing.SpecFlow.fs b/src/test/Fake.Core.UnitTests/Fake.DotNet.Testing.SpecFlow.fs index 7463d9f2738..3024aaff402 100644 --- a/src/test/Fake.Core.UnitTests/Fake.DotNet.Testing.SpecFlow.fs +++ b/src/test/Fake.Core.UnitTests/Fake.DotNet.Testing.SpecFlow.fs @@ -21,7 +21,7 @@ let runCreateProcess setParams = let expectedPath = Path.Combine("specflow", "specflow.exe") Expect.equal file expectedPath "Expected specflow.exe" - expectedPath, (Command.RawCommand(file, Arguments.OfArgs args)).CommandLine + expectedPath, (RawCommand(file, args)).CommandLine [] let tests = diff --git a/src/test/Fake.Core.UnitTests/Fake.Testing.ReportGenerator.fs b/src/test/Fake.Core.UnitTests/Fake.Testing.ReportGenerator.fs index 7153af435e7..a81c8237f9f 100644 --- a/src/test/Fake.Core.UnitTests/Fake.Testing.ReportGenerator.fs +++ b/src/test/Fake.Core.UnitTests/Fake.Testing.ReportGenerator.fs @@ -21,7 +21,8 @@ let runCreateProcess setParams = let expectedPath = Path.Combine("reportgenerator", "ReportGenerator.exe") Expect.equal file expectedPath "Expected ReportGenerator.exe" - expectedPath, (Command.RawCommand(file, Arguments.OfArgs args)).CommandLine + + expectedPath, (RawCommand(file, args)).CommandLine [] let tests = diff --git a/src/test/Fake.Core.UnitTests/Fake.Tools.Pickles.fs b/src/test/Fake.Core.UnitTests/Fake.Tools.Pickles.fs index 7351c25b4e8..ac86b324d2c 100644 --- a/src/test/Fake.Core.UnitTests/Fake.Tools.Pickles.fs +++ b/src/test/Fake.Core.UnitTests/Fake.Tools.Pickles.fs @@ -22,7 +22,7 @@ let runCreateProcess setParams = let expectedPath = Path.Combine("pickles", "pickles.exe") Expect.equal file expectedPath "Expected pickles.exe" - expectedPath, (RawCommand(file,args)).CommandLine + expectedPath, (RawCommand(file, args)).CommandLine [] From 3f11bde0906b9c657a3fa99d78c8ae1ece19376c Mon Sep 17 00:00:00 2001 From: Martin Gondermann Date: Fri, 12 Oct 2018 15:36:26 +0200 Subject: [PATCH 3/4] Fix ArgumentHelper.checkIfMono --- .../Fake.Testing.ArgumentHelper.fs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/test/Fake.Core.UnitTests/Fake.Testing.ArgumentHelper.fs b/src/test/Fake.Core.UnitTests/Fake.Testing.ArgumentHelper.fs index 6b0288f1149..1744f2da1bf 100644 --- a/src/test/Fake.Core.UnitTests/Fake.Testing.ArgumentHelper.fs +++ b/src/test/Fake.Core.UnitTests/Fake.Testing.ArgumentHelper.fs @@ -6,9 +6,17 @@ open Expecto let checkIfMono (file, args) = match Environment.isWindows, Process.monoPath with | false, Some s when file = s -> - Expect.isGreaterThanOrEqual args.Args.Length 3 "Expected mono arguments" - Expect.equal args.Args.[0] "--debug" "Expected first flag to be '--debug'" - args.Args.[1], Arguments.OfArgs args.Args.[2..] + match args.Args with + | a when a.Length = 2 -> + Expect.equal a.[0] "--debug" "Expected first flag to be '--debug'" + a.[1], Arguments.OfArgs [] + | a when a.Length > 2 -> + Expect.equal a.[0] "--debug" "Expected first flag to be '--debug'" + a.[1], Arguments.OfArgs a.[2..] + | a -> + Expect.isGreaterThanOrEqual a.Length 2 "Expected mono arguments" + file, args + | true, _ -> file, args | _ -> Trace.traceFAKE "Mono was not found in test!" From f55d67c7ade035ff62c5ed784149ebef77b5cd10 Mon Sep 17 00:00:00 2001 From: Martin Gondermann Date: Fri, 12 Oct 2018 15:49:38 +0200 Subject: [PATCH 4/4] Made checkIfMono a bit more elegant --- .../Fake.Testing.ArgumentHelper.fs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/test/Fake.Core.UnitTests/Fake.Testing.ArgumentHelper.fs b/src/test/Fake.Core.UnitTests/Fake.Testing.ArgumentHelper.fs index 1744f2da1bf..51a653c956d 100644 --- a/src/test/Fake.Core.UnitTests/Fake.Testing.ArgumentHelper.fs +++ b/src/test/Fake.Core.UnitTests/Fake.Testing.ArgumentHelper.fs @@ -6,13 +6,10 @@ open Expecto let checkIfMono (file, args) = match Environment.isWindows, Process.monoPath with | false, Some s when file = s -> - match args.Args with - | a when a.Length = 2 -> - Expect.equal a.[0] "--debug" "Expected first flag to be '--debug'" - a.[1], Arguments.OfArgs [] - | a when a.Length > 2 -> - Expect.equal a.[0] "--debug" "Expected first flag to be '--debug'" - a.[1], Arguments.OfArgs a.[2..] + match args.Args |> Array.toList with + | debugFlag :: file :: rest -> + Expect.equal debugFlag "--debug" "Expected first flag to be '--debug'" + file, Arguments.OfArgs rest | a -> Expect.isGreaterThanOrEqual a.Length 2 "Expected mono arguments" file, args