Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support VSTest parallel test execution setting #2385

Merged
merged 4 commits into from
Aug 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net462</TargetFrameworks>
<DefineConstants>$(DefineConstants);NO_DOTNETCORE_BOOTSTRAP</DefineConstants>
Expand All @@ -13,6 +13,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="AssemblyInfo.fs" />
<Compile Include="VisibleTo.fs" />
<Compile Include="VSTest.fs" />
</ItemGroup>
<ItemGroup>
Expand Down
64 changes: 41 additions & 23 deletions src/app/Fake.DotNet.Testing.VSTest/VSTest.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Fake.DotNet.Testing.VSTest
open Fake.Core
open Fake.Testing.Common
open System
open System.IO
open System.Text

/// [omit]
Expand All @@ -30,6 +31,8 @@ type VSTestParams =
SettingsPath : string
/// Names of the tests that should be run (optional).
Tests : seq<string>
/// Enables parallel test execution (optional).
Parallel : bool
/// Enables code coverage collection (optional).
EnableCodeCoverage : bool
/// Run the tests in an isolated process (optional).
Expand Down Expand Up @@ -69,6 +72,7 @@ type VSTestParams =
let private VSTestDefaults =
{ SettingsPath = null
Tests = []
Parallel = false
EnableCodeCoverage = false
InIsolation = true
UseVsixExtensions = false
Expand All @@ -82,7 +86,7 @@ let private VSTestDefaults =
ListLoggers = false
ListSettingsProviders = false
ToolPath =
match Process.tryFindFile vsTestPaths vsTestExe with
match ProcessUtils.tryFindFile vsTestPaths vsTestExe with
| Some path -> path
| None -> ""
WorkingDir = null
Expand All @@ -91,15 +95,16 @@ let private VSTestDefaults =
TestAdapterPath = null }

/// Builds the command line arguments from the given parameter record and the given assemblies.
let buildArgs (parameters : VSTestParams) (assembly: string) =
let buildArgs (parameters : VSTestParams) (assemblies: string seq) =
let testsToRun =
if not (Seq.isEmpty parameters.Tests) then
sprintf @"/Tests:%s" (parameters.Tests |> String.separated ",")
else null
new StringBuilder()
|> StringBuilder.appendIfTrue (assembly <> null) assembly
StringBuilder()
|> StringBuilder.appendFileNamesIfNotNull assemblies
|> StringBuilder.appendIfNotNull parameters.SettingsPath "/Settings:"
|> StringBuilder.appendIfTrue (testsToRun <> null) testsToRun
|> StringBuilder.appendIfTrue (not (isNull testsToRun)) testsToRun
|> StringBuilder.appendIfTrue parameters.Parallel "/Parallel"
|> StringBuilder.appendIfTrue parameters.EnableCodeCoverage "/EnableCodeCoverage"
|> StringBuilder.appendIfTrue parameters.InIsolation "/InIsolation"
|> StringBuilder.appendIfTrue parameters.UseVsixExtensions "/UseVsixExtensions:true"
Expand All @@ -115,6 +120,31 @@ let buildArgs (parameters : VSTestParams) (assembly: string) =
|> StringBuilder.appendIfNotNull parameters.TestAdapterPath "/TestAdapterPath:"
|> StringBuilder.toText

let internal createProcess createTempFile (setParams : VSTestParams -> VSTestParams) (assemblies : string[]) =
let parameters = VSTestDefaults |> setParams
if Array.isEmpty assemblies then failwith "VSTest: cannot run tests (the assembly list is empty)."
let tool = parameters.ToolPath
let generatedArgs = buildArgs parameters assemblies

let path = createTempFile()
let argLine = Args.toWindowsCommandLine [ (sprintf "@%s" path) ]
CreateProcess.fromRawCommandLine tool argLine
|> CreateProcess.withWorkingDirectory parameters.WorkingDir
|> CreateProcess.withTimeout parameters.TimeOut
|> CreateProcess.addOnSetup (fun () ->
File.WriteAllText(path, generatedArgs)
Trace.trace(sprintf "Saved args to '%s' with value: %s" path generatedArgs)
)
|> CreateProcess.addOnFinally (fun () ->
File.Delete path
)
|> CreateProcess.addOnExited (fun result exitCode ->
if exitCode > 0 && parameters.ErrorLevel <> ErrorLevel.DontFailBuild then
let message = sprintf "%sVSTest test run failed with exit code %i" Environment.NewLine exitCode
Trace.traceError message
failwith message
)

/// Runs the VSTest command line tool (VSTest.Console.exe) on a group of assemblies.
/// ## Parameters
///
Expand All @@ -128,22 +158,10 @@ let buildArgs (parameters : VSTestParams) (assembly: string) =
/// |> VSTest.run (fun p -> { p with SettingsPath = "Local.RunSettings" })
/// )
let run (setParams : VSTestParams -> VSTestParams) (assemblies : string seq) =
let details = assemblies |> String.separated ", "
use __ = Trace.traceTask "VSTest" details
let parameters = VSTestDefaults |> setParams
if String.IsNullOrEmpty parameters.ToolPath then failwith "VSTest: No tool path specified, or it could not be found automatically."
let assemblies = assemblies |> Seq.toArray
if Array.isEmpty assemblies then failwith "VSTest: cannot run tests (the assembly list is empty)."
let failIfError assembly exitCode =
if exitCode > 0 && parameters.ErrorLevel <> ErrorLevel.DontFailBuild then
let message = sprintf "%sVSTest test run failed for %s" Environment.NewLine assembly
Trace.traceError message
failwith message
for assembly in assemblies do
let args = buildArgs parameters assembly
Process.execSimple (fun info ->
{ info with
FileName = parameters.ToolPath
WorkingDirectory = parameters.WorkingDir
Arguments = args }) parameters.TimeOut
|> failIfError assembly
let details = assemblies |> String.separated ", "
use disposable = Trace.traceTask "VSTest" details
createProcess Path.GetTempFileName setParams assemblies
|> Proc.run

disposable.MarkSuccess()
6 changes: 6 additions & 0 deletions src/app/Fake.DotNet.Testing.VSTest/VisibleTo.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

namespace System
open System.Runtime.CompilerServices

[<assembly: InternalsVisibleTo("Fake.Core.UnitTests")>]
do ()
2 changes: 2 additions & 0 deletions src/test/Fake.Core.UnitTests/Fake.Core.UnitTests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<ProjectReference Include="..\..\app\Fake.DotNet.Fsi\Fake.DotNet.Fsi.fsproj" />
<ProjectReference Include="..\..\app\Fake.DotNet.ILMerge\Fake.DotNet.ILMerge.fsproj" />
<ProjectReference Include="..\..\app\Fake.DotNet.FxCop\Fake.DotNet.FxCop.fsproj" />
<ProjectReference Include="..\..\app\Fake.DotNet.Testing.VSTest\Fake.DotNet.Testing.VSTest.fsproj" />
<ProjectReference Include="..\..\app\Fake.IO.Zip\Fake.IO.Zip.fsproj" />
<ProjectReference Include="..\..\app\Fake.IO.FileSystem\Fake.IO.FileSystem.fsproj" />
<ProjectReference Include="..\..\app\Fake.Tools.Git\Fake.Tools.Git.fsproj" />
Expand Down Expand Up @@ -41,6 +42,7 @@
<Compile Include="Fake.DotNet.MSBuild.fs" />
<Compile Include="Fake.DotNet.NuGet.fs" />
<Compile Include="Fake.DotNet.Testing.NUnit.fs" />
<Compile Include="Fake.DotNet.Testing.VSTest.fs" />
<Compile Include="Fake.DotNet.Testing.SpecFlow.fs" />
<Compile Include="Fake.DotNet.Fsi.fs" />
<Compile Include="Fake.DotNet.Xdt.fs" />
Expand Down
61 changes: 61 additions & 0 deletions src/test/Fake.Core.UnitTests/Fake.DotNet.Testing.VSTest.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
module Fake.DotNet.Testing.VSTestTests

open System.IO
open Fake.Core
open Fake.DotNet
open Fake.DotNet.Testing
open Fake.Testing
open Expecto

[<Tests>]
let tests =
testList "Fake.DotNet.Testing.VSTest.Tests" [
testCase "Test that we write and delete arguments file" <| fun _ ->
let cp =
VSTest.createProcess Path.GetTempFileName (fun param ->
{ param with
ToolPath = "vstest.exe"
}) [| "assembly.dll" |]
let file, args =
match cp.Command with
| RawCommand(file, args) -> file, args
| _ -> failwithf "expected RawCommand"
|> ArgumentHelper.checkIfMono
Expect.equal file "vstest.exe" "Expected vstest.exe"
Expect.equal (args |> Arguments.toArray).Length 1 "expected a single argument"
let arg = (args |> Arguments.toArray).[0]
Expect.stringStarts arg "@" "Expected arg to start with @"
let argFile = arg.Substring(1)

( use _state = cp.Hook.PrepareState()
let contents = File.ReadAllText argFile
let args = Args.fromWindowsCommandLine contents
Expect.sequenceEqual args ["assembly.dll"; "/InIsolation"] "Expected arg file to be correct"
)
Expect.isFalse (File.Exists argFile) "File should be deleted"

testCase "Test that we can set Parallel setting" <| fun _ ->
let cp =
VSTest.createProcess Path.GetTempFileName (fun param ->
{ param with
ToolPath = "vstest.console.exe"
Parallel = true
}) [| "assembly1.dll"; "assembly2.dll" |]
let file, args =
match cp.Command with
| RawCommand(file, args) -> file, args
| _ -> failwithf "expected RawCommand"
|> ArgumentHelper.checkIfMono
Expect.equal file "vstest.console.exe" "Expected vstest.console.exe"
Expect.equal (args |> Arguments.toArray).Length 1 "expected a single argument"
let arg = (args |> Arguments.toArray).[0]
Expect.stringStarts arg "@" "Expected arg to start with @"
let argFile = arg.Substring(1)

( use _state = cp.Hook.PrepareState()
let contents = File.ReadAllText argFile
let args = Args.fromWindowsCommandLine contents
Expect.sequenceEqual args ["assembly1.dll"; "assembly2.dll"; "/Parallel"; "/InIsolation"] "Expected arg file to be correct"
)
Expect.isFalse (File.Exists argFile) "File should be deleted"
]