From fb69e5870ee6d04e64858d281d41a7090ff28dd1 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Wed, 13 Nov 2024 20:33:47 +0100 Subject: [PATCH] TestConsole supporting parallel execution (#17993) --- .../FSharp.Build.UnitTests.fsproj | 3 + .../EmittedIL/TryCatch/TryCatch.fs | 4 +- .../FSharp.Compiler.ComponentTests.fsproj | 3 + .../Miscellaneous/FsharpSuiteMigrated.fs | 3 +- .../xunit.runner.json | 9 +- .../DependencyManagerInteractiveTests.fs | 140 ++++------ ...ompiler.Private.Scripting.UnitTests.fsproj | 3 + .../FSharpScriptTests.fs | 33 +-- .../xunit.runner.json | 3 +- tests/FSharp.Compiler.Service.Tests/Common.fs | 2 +- .../FSharp.Compiler.Service.Tests.fsproj | 3 + .../ProjectAnalysisTests.fs | 2 +- .../xunit.runner.json | 5 +- .../FSharp.Core.UnitTests.fsproj | 3 + tests/FSharp.Core.UnitTests/xunit.runner.json | 9 +- tests/FSharp.Test.Utilities/Compiler.fs | 209 +++++++------- tests/FSharp.Test.Utilities/CompilerAssert.fs | 261 ++++++++---------- .../DirectoryAttribute.fs | 2 +- .../FSharp.Test.Utilities.fsproj | 6 + tests/FSharp.Test.Utilities/ScriptHelpers.fs | 45 +-- .../FSharp.Test.Utilities/ScriptingShims.fsx | 10 +- tests/FSharp.Test.Utilities/TestConsole.fs | 89 ++++++ tests/FSharp.Test.Utilities/TestFramework.fs | 36 ++- tests/FSharp.Test.Utilities/Utilities.fs | 29 -- tests/FSharp.Test.Utilities/XunitHelpers.fs | 23 ++ tests/FSharp.Test.Utilities/XunitSetup.fs | 8 + tests/fsharp/FSharpSuite.Tests.fsproj | 4 +- tests/fsharp/xunit.runner.json | 3 +- tests/scripts/scriptlib.fsx | 10 +- 29 files changed, 479 insertions(+), 481 deletions(-) create mode 100644 tests/FSharp.Test.Utilities/TestConsole.fs create mode 100644 tests/FSharp.Test.Utilities/XunitHelpers.fs create mode 100644 tests/FSharp.Test.Utilities/XunitSetup.fs diff --git a/tests/FSharp.Build.UnitTests/FSharp.Build.UnitTests.fsproj b/tests/FSharp.Build.UnitTests/FSharp.Build.UnitTests.fsproj index 0a2421f3262..e689cf1bf39 100644 --- a/tests/FSharp.Build.UnitTests/FSharp.Build.UnitTests.fsproj +++ b/tests/FSharp.Build.UnitTests/FSharp.Build.UnitTests.fsproj @@ -11,6 +11,9 @@ + + XunitSetup.fs + diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TryCatch/TryCatch.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TryCatch/TryCatch.fs index 1e5187167a7..70ca6baa84e 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TryCatch/TryCatch.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TryCatch/TryCatch.fs @@ -56,9 +56,9 @@ let ``Stackoverflow reproduction`` compilation = | CompilationResult.Success ({OutputPath = Some dllFile} as s) -> let fsharpCoreFile = typeof>.Assembly.Location File.Copy(fsharpCoreFile, Path.Combine(Path.GetDirectoryName(dllFile), Path.GetFileName(fsharpCoreFile)), true) - let _exitCode, _stdout, stderr, _exn = CompilerAssert.ExecuteAndReturnResult (dllFile, isFsx=false, deps = s.Dependencies, newProcess=true) + let result = CompilerAssert.ExecuteAndReturnResult (dllFile, isFsx=false, deps = s.Dependencies, newProcess=true) - Assert.True(stderr.Contains "stack overflow" || stderr.Contains "StackOverflow") + Assert.True(result.StdErr.Contains "stack overflow" || result.StdErr.Contains "StackOverflow") | _ -> failwith (sprintf "%A" compilationResult) diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 02b1c629b91..ffc935a2075 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -28,6 +28,9 @@ + + XunitSetup.fs + diff --git a/tests/FSharp.Compiler.ComponentTests/Miscellaneous/FsharpSuiteMigrated.fs b/tests/FSharp.Compiler.ComponentTests/Miscellaneous/FsharpSuiteMigrated.fs index 3f17e0cfa11..5ab961176f5 100644 --- a/tests/FSharp.Compiler.ComponentTests/Miscellaneous/FsharpSuiteMigrated.fs +++ b/tests/FSharp.Compiler.ComponentTests/Miscellaneous/FsharpSuiteMigrated.fs @@ -31,12 +31,13 @@ module ScriptRunner = let cu = cu |> withDefines defaultDefines match cu with | FS fsSource -> + use capture = new TestConsole.ExecutionCapture() let engine = createEngine (fsSource.Options |> Array.ofList,version) let res = evalScriptFromDiskInSharedSession engine cu match res with | CompilationResult.Failure _ -> res | CompilationResult.Success s -> - if engine.GetOutput().Contains "TEST PASSED OK" then + if capture.OutText |> TestFramework.outputPassed then res else failwith $"Results looked correct, but 'TEST PASSED OK' was not printed. Result: %A{s}" diff --git a/tests/FSharp.Compiler.ComponentTests/xunit.runner.json b/tests/FSharp.Compiler.ComponentTests/xunit.runner.json index 2d07715ae5f..d1866b85c61 100644 --- a/tests/FSharp.Compiler.ComponentTests/xunit.runner.json +++ b/tests/FSharp.Compiler.ComponentTests/xunit.runner.json @@ -1,7 +1,6 @@ { - "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", - "appDomain": "ifAvailable", - "shadowCopy": false, - "parallelizeTestCollections": false, - "maxParallelThreads": 1 + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "appDomain": "denied", + "parallelizeTestCollections": false, + "maxParallelThreads": 1 } diff --git a/tests/FSharp.Compiler.Private.Scripting.UnitTests/DependencyManagerInteractiveTests.fs b/tests/FSharp.Compiler.Private.Scripting.UnitTests/DependencyManagerInteractiveTests.fs index cc027d098af..20c5919e00e 100644 --- a/tests/FSharp.Compiler.Private.Scripting.UnitTests/DependencyManagerInteractiveTests.fs +++ b/tests/FSharp.Compiler.Private.Scripting.UnitTests/DependencyManagerInteractiveTests.fs @@ -13,6 +13,7 @@ open FSharp.Compiler.DependencyManager open FSharp.Compiler.Diagnostics open FSharp.DependencyManager.Nuget open FSharp.Test.ScriptHelpers +open FSharp.Test open FSharp.Test.Utilities open Internal.Utilities @@ -148,8 +149,7 @@ type DependencyManagerInteractiveTests() = Assert.Equal(0, result.Roots |> Seq.length) () - - [] + [] member _.``Multiple Instances of DependencyProvider should be isolated``() = let assemblyProbingPaths () = Seq.empty @@ -721,101 +721,65 @@ x |> Seq.iter(fun r -> [] member _.``Verify that #help produces help text for fsi + dependency manager``() = - let expected = [| - """ F# Interactive directives:""" - """""" - """ #r "file.dll";; // Reference (dynamically load) the given DLL""" - """ #i "package source uri";; // Include package source uri when searching for packages""" - """ #I "path";; // Add the given search path for referenced DLLs""" - """ #load "file.fs" ...;; // Load the given file(s) as if compiled and referenced""" - """ #time ["on"|"off"];; // Toggle timing on/off""" - """ #clear;; // Clear screen""" - """ #help;; // Display help""" - """ #help "idn";; // Display documentation for an identifier, e.g. #help "List.map";;""" - """ #quit;; // Exit""" - """""" - """ F# Interactive command line options:""" - """""" - - // this is the end of the line each different platform has a different mechanism for starting fsi - // Actual output looks similar to: """ See 'testhost --help' for options""" - """--help' for options""" - - """""" - """""" - |] + let expected = """ + F# Interactive directives: + + #r "file.dll";; // Reference (dynamically load) the given DLL + #i "package source uri";; // Include package source uri when searching for packages + #I "path";; // Add the given search path for referenced DLLs + #load "file.fs" ...;; // Load the given file(s) as if compiled and referenced + #time ["on"|"off"];; // Toggle timing on/off + #help;; // Display help + #help "idn";; // Display documentation for an identifier, e.g. #help "List.map";; + #clear;; // Clear screen + #quit;; // Exit + + F# Interactive command line options:""" - let mutable found = 0 - let lines = System.Collections.Generic.List() - use sawExpectedOutput = new ManualResetEvent(false) - let verifyOutput (line: string) = - let compareLine (s: string) = - if s = "" then line = "" - else line.EndsWith(s) - lines.Add(line) - match expected |> Array.tryFind(compareLine) with - | None -> () - | Some t -> - found <- found + 1 - if found = expected.Length then sawExpectedOutput.Set() |> ignore - - let text = "#help" use script = new FSharpScript(quiet = false, langVersion = LangVersion.V47) - let mutable found = 0 - script.OutputProduced.Add (fun line -> verifyOutput line) - let opt = script.Eval(text) |> getValue - Assert.True(sawExpectedOutput.WaitOne(TimeSpan.FromSeconds(5.0)), sprintf "Expected to see error sentinel value written\nexpected:%A\nactual:%A" expected lines) + use capture = new TestConsole.ExecutionCapture() + let opt = script.Eval("#help") |> getValue + + let output = capture.OutText + + Assert.Contains(expected, output) + + // this is the end of the line each different platform has a different mechanism for starting fsi + // Actual output looks similar to: """ See 'testhost --help' for options""" + Assert.EndsWith("--help' for options", output.Trim()) [] member _.``Verify that #help produces help text for fsi + dependency manager language version preview``() = - let expected = [| - """ F# Interactive directives:""" - """""" - """ #r "file.dll";; // Reference (dynamically load) the given DLL""" - """ #i "package source uri";; // Include package source uri when searching for packages""" - """ #I "path";; // Add the given search path for referenced DLLs""" - """ #load "file.fs" ...;; // Load the given file(s) as if compiled and referenced""" - """ #time ["on"|"off"];; // Toggle timing on/off""" - """ #help;; // Display help""" - """ #help "idn";; // Display documentation for an identifier, e.g. #help "List.map";;""" - """ #r "nuget:FSharp.Data, 3.1.2";; // Load Nuget Package 'FSharp.Data' version '3.1.2'""" - """ #r "nuget:FSharp.Data";; // Load Nuget Package 'FSharp.Data' with the highest version""" - """ #clear;; // Clear screen""" - """ #quit;; // Exit""" - """""" - """ F# Interactive command line options:""" - """""" - - // this is the end of the line each different platform has a different mechanism for starting fsi - // Actual output looks similar to: """ See 'testhost --help' for options""" - """--help' for options""" - - """""" - """""" - |] + let expected = """ + F# Interactive directives: + + #r "file.dll";; // Reference (dynamically load) the given DLL + #i "package source uri";; // Include package source uri when searching for packages + #I "path";; // Add the given search path for referenced DLLs + #load "file.fs" ...;; // Load the given file(s) as if compiled and referenced + #time ["on"|"off"];; // Toggle timing on/off + #help;; // Display help + #help "idn";; // Display documentation for an identifier, e.g. #help "List.map";; + #r "nuget:FSharp.Data, 3.1.2";; // Load Nuget Package 'FSharp.Data' version '3.1.2' + #r "nuget:FSharp.Data";; // Load Nuget Package 'FSharp.Data' with the highest version + #clear;; // Clear screen + #quit;; // Exit + + F# Interactive command line options:""" - let mutable found = 0 - let lines = System.Collections.Generic.List() - use sawExpectedOutput = new ManualResetEvent(false) - let verifyOutput (line: string) = - let compareLine (s: string) = - if s = "" then line = "" - else line.EndsWith(s) - lines.Add(line) - match expected |> Array.tryFind(compareLine) with - | None -> () - | Some t -> - found <- found + 1 - if found = expected.Length then sawExpectedOutput.Set() |> ignore - - let text = "#help" use script = new FSharpScript(quiet = false, langVersion = LangVersion.Preview) - let mutable found = 0 - script.OutputProduced.Add (fun line -> verifyOutput line) - let opt = script.Eval(text) |> getValue - Assert.True(sawExpectedOutput.WaitOne(TimeSpan.FromSeconds(5.0)), sprintf "Expected to see error sentinel value written\nexpected:%A\nactual:%A" expected lines) + use capture = new TestConsole.ExecutionCapture() + let opt = script.Eval("#help") |> getValue + + let output = capture.OutText + + Assert.Contains(expected, output) + + // this is the end of the line each different platform has a different mechanism for starting fsi + // Actual output looks similar to: """ See 'testhost --help' for options""" + Assert.EndsWith("--help' for options", output.Trim()) [] member _.``Verify that timeout --- times out and fails``() = diff --git a/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharp.Compiler.Private.Scripting.UnitTests.fsproj b/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharp.Compiler.Private.Scripting.UnitTests.fsproj index 3bf2d528a4f..a3ac4bd6d1a 100644 --- a/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharp.Compiler.Private.Scripting.UnitTests.fsproj +++ b/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharp.Compiler.Private.Scripting.UnitTests.fsproj @@ -12,6 +12,9 @@ + + XunitSetup.fs + diff --git a/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharpScriptTests.fs b/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharpScriptTests.fs index bf3a9cbaac6..d085005d9ac 100644 --- a/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharpScriptTests.fs +++ b/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharpScriptTests.fs @@ -3,6 +3,7 @@ namespace FSharp.Compiler.Scripting.UnitTests open System +open System.Text open System.Diagnostics open System.IO open System.Reflection @@ -11,6 +12,7 @@ open System.Threading open System.Threading.Tasks open FSharp.Compiler.Interactive open FSharp.Compiler.Interactive.Shell +open FSharp.Test open FSharp.Test.ScriptHelpers open Xunit @@ -87,7 +89,8 @@ x [] member _.``Capture console input``() = - use script = new FSharpScript(input = "stdin:1234\r\n") + use _ = new TestConsole.ProvideInput("stdin:1234\r\n") + use script = new FSharpScript() let opt = script.Eval("System.Console.ReadLine()") |> getValue let value = opt.Value Assert.Equal(typeof, value.ReflectionType) @@ -95,14 +98,11 @@ x [] member _.``Capture console output/error``() = + use capture = new TestConsole.ExecutionCapture() use script = new FSharpScript() - use sawOutputSentinel = new ManualResetEvent(false) - use sawErrorSentinel = new ManualResetEvent(false) - script.OutputProduced.Add (fun line -> if line = "stdout:1234" then sawOutputSentinel.Set() |> ignore) - script.ErrorProduced.Add (fun line -> if line = "stderr:5678" then sawErrorSentinel.Set() |> ignore) script.Eval("printfn \"stdout:1234\"; eprintfn \"stderr:5678\"") |> ignoreValue - Assert.True(sawOutputSentinel.WaitOne(TimeSpan.FromSeconds(5.0)), "Expected to see output sentinel value written") - Assert.True(sawErrorSentinel.WaitOne(TimeSpan.FromSeconds(5.0)), "Expected to see error sentinel value written") + Assert.Contains("stdout:1234", capture.OutText) + Assert.Contains("stderr:5678", capture.ErrorText) [] member _.``Maintain state between submissions``() = @@ -306,30 +306,26 @@ printfn "{@"%A"}" result [] member _.``Eval script with invalid PackageName should fail immediately``() = + use capture = new TestConsole.ExecutionCapture() use script = new FSharpScript(additionalArgs=[| |]) - let mutable found = 0 - let outp = System.Collections.Generic.List() - script.OutputProduced.Add( - fun line -> - if line.Contains("error NU1101:") && line.Contains("FSharp.Really.Not.A.Package") then - found <- found + 1 - outp.Add(line)) let result, errors = script.Eval("""#r "nuget:FSharp.Really.Not.A.Package" """) - Assert.True( (found = 0), "Did not expect to see output contains 'error NU1101:' and 'FSharp.Really.Not.A.Package'") + + let lines = capture.OutText.Split([| Environment.NewLine |], StringSplitOptions.None) + let found = lines |> Seq.exists (fun line -> line.Contains("error NU1101:") && line.Contains("FSharp.Really.Not.A.Package")) + Assert.False(found, "Did not expect to see output contains 'error NU1101:' and 'FSharp.Really.Not.A.Package'") Assert.True( errors |> Seq.exists (fun error -> error.Message.Contains("error NU1101:")), "Expect to error containing 'error NU1101:'") Assert.True( errors |> Seq.exists (fun error -> error.Message.Contains("FSharp.Really.Not.A.Package")), "Expect to error containing 'FSharp.Really.Not.A.Package'") [] member _.``Eval script with invalid PackageName should fail immediately and resolve one time only``() = + use capture = new TestConsole.ExecutionCapture() use script = new FSharpScript(additionalArgs=[| |]) - let mutable foundResolve = 0 - script.OutputProduced.Add (fun line -> if line.Contains("error NU1101:") then foundResolve <- foundResolve + 1) let result, errors = script.Eval(""" #r "nuget:FSharp.Really.Not.A.Package" #r "nuget:FSharp.Really.Not.Another.Package" """) - Assert.True( (foundResolve = 0), (sprintf "Did not expected to see 'error NU1101:' in output" )) + Assert.DoesNotContain("error NU1101:", capture.OutText) Assert.Equal(2, (errors |> Seq.filter (fun error -> error.Message.Contains("error NU1101:")) |> Seq.length)) Assert.Equal(1, (errors |> Seq.filter (fun error -> error.Message.Contains("FSharp.Really.Not.A.Package")) |> Seq.length)) Assert.Equal(1, (errors |> Seq.filter (fun error -> error.Message.Contains("FSharp.Really.Not.Another.Package")) |> Seq.length)) @@ -479,6 +475,7 @@ let x = script.Eval(code) |> ignoreValue Assert.False(foundInner) + [] member _.``Script with nuget package that yields out of order dependencies works correctly``() = // regression test for: https://github.com/dotnet/fsharp/issues/9217 diff --git a/tests/FSharp.Compiler.Private.Scripting.UnitTests/xunit.runner.json b/tests/FSharp.Compiler.Private.Scripting.UnitTests/xunit.runner.json index 2d07715ae5f..b180883cc0f 100644 --- a/tests/FSharp.Compiler.Private.Scripting.UnitTests/xunit.runner.json +++ b/tests/FSharp.Compiler.Private.Scripting.UnitTests/xunit.runner.json @@ -1,7 +1,6 @@ { "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", - "appDomain": "ifAvailable", - "shadowCopy": false, + "appDomain": "denied", "parallelizeTestCollections": false, "maxParallelThreads": 1 } diff --git a/tests/FSharp.Compiler.Service.Tests/Common.fs b/tests/FSharp.Compiler.Service.Tests/Common.fs index 5fd97f5bb21..986e2be5132 100644 --- a/tests/FSharp.Compiler.Service.Tests/Common.fs +++ b/tests/FSharp.Compiler.Service.Tests/Common.fs @@ -476,7 +476,7 @@ let assertRange [] module TempDirUtils = let getTempPath dir = - Path.Combine(TestFramework.tempDirectoryOfThisTestRun, dir) + Path.Combine(tempDirectoryOfThisTestRun.Value.FullName, dir) /// Returns the file name part of a temp file name created with tryCreateTemporaryFileName () /// and an added process id and thread id to ensure uniqueness between threads. diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj index 910e9543ada..fc64abb4138 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj @@ -21,6 +21,9 @@ + + XunitSetup.fs + diff --git a/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs b/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs index ac563c3e1ed..dfce65ef186 100644 --- a/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs @@ -4401,7 +4401,7 @@ let ``Test Project33 extension methods`` () = ("GetValue", ["member"; "extmem"])] module internal Project34 = - let directoryPath = createTemporaryDirectory "Project34" + let directoryPath = createTemporaryDirectory().FullName let sourceFileName = Path.Combine(directoryPath, "Program.fs") let dllName = Path.ChangeExtension(sourceFileName, ".dll") let projFileName = Path.ChangeExtension(sourceFileName, ".fsproj") diff --git a/tests/FSharp.Compiler.Service.Tests/xunit.runner.json b/tests/FSharp.Compiler.Service.Tests/xunit.runner.json index 743febb7028..2b65d19ea3e 100644 --- a/tests/FSharp.Compiler.Service.Tests/xunit.runner.json +++ b/tests/FSharp.Compiler.Service.Tests/xunit.runner.json @@ -1,5 +1,4 @@ { - "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", - "appDomain": "ifAvailable", - "shadowCopy": false + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "appDomain": "denied" } diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj b/tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj index 2ba4f6f837d..305d234a837 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj @@ -26,6 +26,9 @@ + + XunitSetup.fs + diff --git a/tests/FSharp.Core.UnitTests/xunit.runner.json b/tests/FSharp.Core.UnitTests/xunit.runner.json index 2d07715ae5f..d1866b85c61 100644 --- a/tests/FSharp.Core.UnitTests/xunit.runner.json +++ b/tests/FSharp.Core.UnitTests/xunit.runner.json @@ -1,7 +1,6 @@ { - "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", - "appDomain": "ifAvailable", - "shadowCopy": false, - "parallelizeTestCollections": false, - "maxParallelThreads": 1 + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "appDomain": "denied", + "parallelizeTestCollections": false, + "maxParallelThreads": 1 } diff --git a/tests/FSharp.Test.Utilities/Compiler.fs b/tests/FSharp.Test.Utilities/Compiler.fs index 5e5629b089f..115964702f8 100644 --- a/tests/FSharp.Test.Utilities/Compiler.fs +++ b/tests/FSharp.Test.Utilities/Compiler.fs @@ -170,16 +170,16 @@ module rec Compiler = Message: string SubCategory: string } - // This type is used either for the output of the compiler (typically in CompilationResult coming from 'compile') - // or for the output of the code generated by the compiler (in CompilationResult coming from 'run') - type ExecutionOutput = - { ExitCode: int option - StdOut: string - StdErr: string - Exn: exn option } +// This type is used either for the output of the compiler (typically in CompilationResult coming from 'compile') +// or for the output of the code generated by the compiler (in CompilationResult coming from 'run') + + type EvalOutput = + { Result: Result + StdOut: string + StdErr: string } type RunOutput = - | EvalOutput of Result + | EvalOutput of EvalOutput | ExecutionOutput of ExecutionOutput type SourceCodeFileName = string @@ -701,20 +701,23 @@ module rec Compiler = let private compileFSharpCompilation compilation ignoreWarnings (cUnit: CompilationUnit) : CompilationResult = - use redirect = new RedirectConsole() + use capture = new TestConsole.ExecutionCapture() + let ((err: FSharpDiagnostic[], exn, outputFilePath: string), deps) = CompilerAssert.CompileRaw(compilation, ignoreWarnings) // Create and stash the console output let diagnostics = err |> fromFSharpDiagnostic + let outcome = exn |> Option.map Failure |> Option.defaultValue NoExitCode + let result = { OutputPath = None Dependencies = deps Adjust = 0 PerFileErrors = diagnostics Diagnostics = diagnostics |> List.map snd - Output = Some (RunOutput.ExecutionOutput { ExitCode = None; StdOut = redirect.Output(); StdErr = redirect.ErrorOutput(); Exn = exn }) + Output = Some (RunOutput.ExecutionOutput { Outcome = outcome; StdOut = capture.OutText; StdErr = capture.ErrorText }) Compilation = cUnit } @@ -733,7 +736,7 @@ module rec Compiler = let outputDirectory = match fs.OutputDirectory with | Some di -> di - | None -> DirectoryInfo(createTemporaryDirectory "compileFSharp") + | None -> createTemporaryDirectory() let references = processReferences fs.References outputDirectory let compilation = Compilation.CreateFromSources([fs.Source] @ fs.AdditionalSources, output, options, fs.TargetFramework, references, name, outputDirectory) compileFSharpCompilation compilation fs.IgnoreWarnings (FS fs) @@ -783,7 +786,7 @@ module rec Compiler = let outputDirectory = match csSource.OutputDirectory with | Some di -> di - | None -> DirectoryInfo(createTemporaryDirectory "compileCSharp") + | None -> createTemporaryDirectory() let additionalReferences = processReferences csSource.References outputDirectory @@ -921,20 +924,18 @@ module rec Compiler = let fileName = fsSource.Source.ChangeExtension.GetSourceFileName let references = - let disposals = ResizeArray() let outputDirectory = match fsSource.OutputDirectory with | Some di -> di - | None -> DirectoryInfo(createTemporaryDirectory "typecheckResults") + | None -> createTemporaryDirectory() let references = processReferences fsSource.References outputDirectory if references.IsEmpty then Array.empty else outputDirectory.Create() - disposals.Add({ new IDisposable with member _.Dispose() = outputDirectory.Delete(true) }) // Note that only the references are relevant here let compilation = Compilation.Compilation([], CompileOutput.Exe,Array.empty, TargetFramework.Current, references, None, None) - evaluateReferences outputDirectory disposals fsSource.IgnoreWarnings compilation + evaluateReferences outputDirectory fsSource.IgnoreWarnings compilation |> fst let options = @@ -981,19 +982,17 @@ module rec Compiler = | SourceCodeFileKind.Fsx _ -> true | _ -> false | _ -> false - let exitCode, output, errors, exn = CompilerAssert.ExecuteAndReturnResult (p, isFsx, s.Dependencies, false) - printfn "---------output-------\n%s\n-------" output - printfn "---------errors-------\n%s\n-------" errors - let executionResult = { s with Output = Some (ExecutionOutput { ExitCode = exitCode; StdOut = output; StdErr = errors; Exn = exn }) } - match exn with - | None -> CompilationResult.Success executionResult - | Some _ -> CompilationResult.Failure executionResult + let output = CompilerAssert.ExecuteAndReturnResult (p, isFsx, s.Dependencies, false) + let executionResult = { s with Output = Some (ExecutionOutput output) } + match output.Outcome with + | Failure _ -> CompilationResult.Failure executionResult + | _ -> CompilationResult.Success executionResult let compileAndRun = compile >> run let compileExeAndRun = asExe >> compileAndRun - let private processScriptResults fs (evalResult: Result, err: FSharpDiagnostic[]) = + let private processScriptResults fs (evalResult: Result, err: FSharpDiagnostic[]) outputWritten errorsWritten = let perFileDiagnostics = err |> fromFSharpDiagnostic let diagnostics = perFileDiagnostics |> List.map snd let (errors, warnings) = partitionErrors diagnostics @@ -1003,7 +1002,7 @@ module rec Compiler = Adjust = 0 Diagnostics = if fs.IgnoreWarnings then errors else diagnostics PerFileErrors = perFileDiagnostics - Output = Some (EvalOutput evalResult) + Output = Some (EvalOutput ({Result = evalResult; StdOut = outputWritten; StdErr = errorsWritten})) Compilation = FS fs } let evalError = match evalResult with Ok _ -> false | _ -> true @@ -1015,7 +1014,10 @@ module rec Compiler = let private evalFSharp (fs: FSharpCompilationSource) (script:FSharpScript) : CompilationResult = let source = fs.Source.GetSourceText |> Option.defaultValue "" - script.Eval(source) |> (processScriptResults fs) + use capture = new TestConsole.ExecutionCapture() + let result = script.Eval(source) + let outputWritten, errorsWritten = capture.OutText, capture.ErrorText + processScriptResults fs result outputWritten errorsWritten let scriptingShim = Path.Combine(__SOURCE_DIRECTORY__,"ScriptingShims.fsx") let private evalScriptFromDisk (fs: FSharpCompilationSource) (script:FSharpScript) : CompilationResult = @@ -1027,7 +1029,10 @@ module rec Compiler = |> List.map (sprintf " @\"%s\"") |> String.Concat - script.Eval("#load " + fileNames ) |> (processScriptResults fs) + use capture = new TestConsole.ExecutionCapture() + let result = script.Eval("#load " + fileNames) + let outputWritten, errorsWritten = capture.OutText, capture.ErrorText + processScriptResults fs result outputWritten errorsWritten let eval (cUnit: CompilationUnit) : CompilationResult = match cUnit with @@ -1037,7 +1042,7 @@ module rec Compiler = evalFSharp fs script | _ -> failwith "Script evaluation is only supported for F#." - let getSessionForEval args version = new FSharpScript(additionalArgs=args,quiet=false,langVersion=version) + let getSessionForEval args version = new FSharpScript(additionalArgs=args,quiet=true,langVersion=version) let evalInSharedSession (script:FSharpScript) (cUnit: CompilationUnit) : CompilationResult = match cUnit with @@ -1052,58 +1057,51 @@ module rec Compiler = let runFsi (cUnit: CompilationUnit) : CompilationResult = match cUnit with | FS fs -> - let disposals = ResizeArray() - try - let source = fs.Source.GetSourceText |> Option.defaultValue "" - let name = fs.Name |> Option.defaultValue "unnamed" - let options = fs.Options |> Array.ofList - let outputDirectory = - match fs.OutputDirectory with - | Some di -> di - | None -> DirectoryInfo(createTemporaryDirectory "runFsi") - outputDirectory.Create() - disposals.Add({ new IDisposable with member _.Dispose() = outputDirectory.Delete(true) }) - - let references = processReferences fs.References outputDirectory - let cmpl = Compilation.Create(fs.Source, fs.OutputType, options, fs.TargetFramework, references, name, outputDirectory) - let _compilationRefs, _deps = evaluateReferences outputDirectory disposals fs.IgnoreWarnings cmpl - let options = - let opts = new ResizeArray(fs.Options) - - // For every built reference add a -I path so that fsi can find it easily - for reference in references do - match reference with - | CompilationReference( cmpl, _) -> - match cmpl with - | Compilation(_sources, _outputType, _options, _targetFramework, _references, _name, outputDirectory) -> - if outputDirectory.IsSome then - opts.Add($"-I:\"{(outputDirectory.Value.FullName)}\"") - | _ -> () - opts.ToArray() - let errors, stdOut = CompilerAssert.RunScriptWithOptionsAndReturnResult options source - - let mkResult output = - { OutputPath = None - Dependencies = [] - Adjust = 0 - Diagnostics = [] - PerFileErrors= [] - Output = Some output - Compilation = cUnit } - - if errors.Count = 0 then - let output = - ExecutionOutput { ExitCode = None; StdOut = stdOut; StdErr = ""; Exn = None } - CompilationResult.Success (mkResult output) - else - let err = (errors |> String.concat "\n").Replace("\r\n","\n") - let output = - ExecutionOutput {ExitCode = None; StdOut = String.Empty; StdErr = err; Exn = None } - CompilationResult.Failure (mkResult output) - - finally - disposals - |> Seq.iter (fun x -> x.Dispose()) + let source = fs.Source.GetSourceText |> Option.defaultValue "" + let name = fs.Name |> Option.defaultValue "unnamed" + let options = fs.Options |> Array.ofList + let outputDirectory = + match fs.OutputDirectory with + | Some di -> di + | None -> createTemporaryDirectory() + outputDirectory.Create() + + let references = processReferences fs.References outputDirectory + let cmpl = Compilation.Create(fs.Source, fs.OutputType, options, fs.TargetFramework, references, name, outputDirectory) + let _compilationRefs, _deps = evaluateReferences outputDirectory fs.IgnoreWarnings cmpl + let options = + let opts = new ResizeArray(fs.Options) + + // For every built reference add a -I path so that fsi can find it easily + for reference in references do + match reference with + | CompilationReference( cmpl, _) -> + match cmpl with + | Compilation(_sources, _outputType, _options, _targetFramework, _references, _name, outputDirectory) -> + if outputDirectory.IsSome then + opts.Add($"-I:\"{(outputDirectory.Value.FullName)}\"") + | _ -> () + opts.ToArray() + let errors, stdOut, stdErr = CompilerAssert.RunScriptWithOptionsAndReturnResult options source + + let mkResult output = + { OutputPath = None + Dependencies = [] + Adjust = 0 + Diagnostics = [] + PerFileErrors= [] + Output = Some output + Compilation = cUnit } + + if errors.Count = 0 then + let output = + ExecutionOutput { Outcome = NoExitCode; StdOut = stdOut; StdErr = stdErr } + CompilationResult.Success (mkResult output) + else + let err = (errors |> String.concat "\n").Replace("\r\n","\n") + let output = + ExecutionOutput {Outcome = NoExitCode; StdOut = String.Empty; StdErr = err } + CompilationResult.Failure (mkResult output) | _ -> failwith "FSI running only supports F#." @@ -1190,9 +1188,11 @@ Actual: | Some p -> match ILChecker.verifyILAndReturnActual [] p expected with | true, _, _ -> result - | false, errorMsg, _actualIL -> CompilationResult.Failure( {s with Output = Some (ExecutionOutput {ExitCode = None; StdOut = errorMsg; StdErr = ""; Exn = None })} ) - - | CompilationResult.Failure f -> failwith $"Result should be \"Success\" in order to get IL. Failure: {Environment.NewLine}{f}" + | false, errorMsg, _actualIL -> CompilationResult.Failure( {s with Output = Some (ExecutionOutput {Outcome = NoExitCode; StdOut = errorMsg; StdErr = "" })} ) + | CompilationResult.Failure f -> + printfn "Failure:" + printfn $"{f}" + failwith $"Result should be \"Success\" in order to get IL." let verifyIL = doILCheck ILChecker.checkIL @@ -1282,7 +1282,7 @@ Actual: | Some actual -> let expected = stripVersion (normalizeNewlines expected) if expected <> actual then - failwith $"""Output does not match expected: ------------{Environment.NewLine}{expected}{Environment.NewLine}Actual: ------------{Environment.NewLine}{actual}{Environment.NewLine}""" + failwith $"""Output does not match expected:{Environment.NewLine}{expected}{Environment.NewLine}Actual:{Environment.NewLine}{actual}{Environment.NewLine}""" else cResult @@ -1295,7 +1295,7 @@ Actual: | Some actual -> for item in expected do if not(actual.Contains(item)) then - failwith $"""Output does not match expected: ------------{Environment.NewLine}{item}{Environment.NewLine}Actual: ------------{Environment.NewLine}{actual}{Environment.NewLine}""" + failwith $"""Output does not match expected:{Environment.NewLine}{item}{Environment.NewLine}Actual:{Environment.NewLine}{actual}{Environment.NewLine}""" cResult type ImportScope = { Kind: ImportDefinitionKind; Name: string } @@ -1515,18 +1515,15 @@ Actual: match result with | CompilationResult.Success _ -> result | CompilationResult.Failure r -> - let message = - [ sprintf "Operation failed (expected to succeed).\n All errors:\n%A\n" r.Diagnostics - match r.Output with - | Some (ExecutionOutput output) -> - sprintf "----output-----\n%s\n----error-------\n%s\n----------" output.StdOut output.StdErr - | Some (EvalOutput (Result.Error exn) ) -> - sprintf "----script error-----\n%s\n----------" (exn.ToString()) - | Some (EvalOutput (Result.Ok fsiVal) ) -> - sprintf "----script output-----\n%A\n----------" (fsiVal) - | _ -> () ] - |> String.concat "\n" - failwith message + eprintfn "\nAll errors:" + r.Diagnostics |> Seq.iter (eprintfn "%A") + + match r.Output with + | Some (EvalOutput { Result = Result.Error ex }) + | Some (ExecutionOutput {Outcome = Failure ex }) -> + raise ex + | _ -> + failwithf "Operation failed (expected to succeed)." let shouldFail (result: CompilationResult) : CompilationResult = match result with @@ -1706,13 +1703,15 @@ Actual: | None -> failwith "Execution output is missing, cannot check exit code." | Some o -> match o with - | ExecutionOutput {ExitCode = Some exitCode} -> Assert.Equal(expectedExitCode, exitCode) + | ExecutionOutput {Outcome = ExitCode exitCode} -> Assert.Equal(expectedExitCode, exitCode) | _ -> failwith "Cannot check exit code on this run result." result let private checkOutputInOrder (category: string) (substrings: string list) (selector: ExecutionOutput -> string) (result: CompilationResult) : CompilationResult = match result.RunOutput with - | None -> failwith (sprintf "Execution output is missing cannot check \"%A\"" category) + | None -> + printfn "Execution output is missing cannot check \"%A\"" category + failwith "Execution output is missing." | Some o -> match o with | ExecutionOutput e -> @@ -1743,9 +1742,11 @@ Actual: let private assertEvalOutput (selector: FsiValue -> 'T) (value: 'T) (result: CompilationResult) : CompilationResult = match result.RunOutput with | None -> failwith "Execution output is missing cannot check value." - | Some (EvalOutput (Ok (Some e))) -> Assert.Equal<'T>(value, (selector e)) - | Some (EvalOutput (Ok None )) -> failwith "Cannot assert value of evaluation, since it is None." - | Some (EvalOutput (Result.Error ex)) -> raise ex + | Some (EvalOutput output) -> + match output.Result with + | Ok (Some e) -> Assert.Equal<'T>(value, (selector e)) + | Ok None -> failwith "Cannot assert value of evaluation, since it is None." + | Result.Error ex -> raise ex | Some _ -> failwith "Only 'eval' output is supported." result @@ -1775,7 +1776,9 @@ Actual: |> Array.filter (fun s -> s.Length > 0) if not (actual |> Array.contains expected) then - failwith ($"The following signature:\n%s{expected}\n\nwas not found in:\n" + (actual |> String.concat "\n")) + printfn $"The following signature:\n%s{expected}\n\nwas not found in:" + actual |> Array.iter (printfn "%s") + failwith "Expected signature was not found." let private printSignaturesImpl pageWidth cUnit = cUnit diff --git a/tests/FSharp.Test.Utilities/CompilerAssert.fs b/tests/FSharp.Test.Utilities/CompilerAssert.fs index 37a8e25e164..202f7704a9d 100644 --- a/tests/FSharp.Test.Utilities/CompilerAssert.fs +++ b/tests/FSharp.Test.Utilities/CompilerAssert.fs @@ -2,6 +2,8 @@ namespace FSharp.Test +open System.Threading + #nowarn "57" open System @@ -61,6 +63,16 @@ module AssemblyResolver = do addResolver() #endif +type ExecutionOutcome = + | NoExitCode + | ExitCode of int + | Failure of exn + +type ExecutionOutput = + { Outcome: ExecutionOutcome + StdOut: string + StdErr: string } + [] type ILVerifier (dllFilePath: string) = @@ -295,14 +307,13 @@ and Compilation = | n -> Some n Compilation(sources, output, options, targetFramework, cmplRefs, name, outputDirectory) - -module rec CompilerAssertHelpers = +module CompilerAssertHelpers = let UseTransparentCompiler = FSharp.Compiler.CompilerConfig.FSharpExperimentalFeaturesEnabledAutomatically || not (String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TEST_TRANSPARENT_COMPILER"))) - let checker = FSharpChecker.Create(suggestNamesForErrors=true, useTransparentCompiler=UseTransparentCompiler) + let checker = FSharpChecker.Create(suggestNamesForErrors=true, useTransparentCompiler = UseTransparentCompiler) // Unlike C# whose entrypoint is always string[] F# can make an entrypoint with 0 args, or with an array of string[] let mkDefaultArgs (entryPoint:MethodBase) : obj[] = [| @@ -322,48 +333,65 @@ module rec CompilerAssertHelpers = else entryPoint let args = mkDefaultArgs entryPoint - captureConsoleOutputs (fun () -> entryPoint.Invoke(Unchecked.defaultof, args)) + + use capture = new TestConsole.ExecutionCapture() + let outcome = + try + match entryPoint.Invoke(Unchecked.defaultof, args) with + | :? int as rc -> ExitCode rc + | _ -> NoExitCode + with + | exn -> Failure exn + outcome, capture.OutText, capture.ErrorText #if NETCOREAPP - let executeBuiltApp assembly deps isFsx = + let executeBuiltApp assemblyPath deps isFsx = let ctxt = AssemblyLoadContext("ContextName", true) try ctxt.add_Resolving(fun ctxt name -> deps |> List.tryFind (fun (x: string) -> Path.GetFileNameWithoutExtension x = name.Name) |> Option.map ctxt.LoadFromAssemblyPath - |> Option.defaultValue null) + |> Option.toObj) - executeAssemblyEntryPoint (ctxt.LoadFromAssemblyPath assembly) isFsx + executeAssemblyEntryPoint (ctxt.LoadFromAssemblyPath assemblyPath) isFsx finally ctxt.Unload() #else type Worker () = inherit MarshalByRefObject() - member x.ExecuteTestCase assemblyPath (deps: string[]) isFsx = - AppDomain.CurrentDomain.add_AssemblyResolve(ResolveEventHandler(fun _ args -> - deps - |> Array.tryFind (fun (x: string) -> Path.GetFileNameWithoutExtension x = AssemblyName(args.Name).Name) - |> Option.bind (fun x -> if FileSystem.FileExistsShim x then Some x else None) - |> Option.map Assembly.LoadFile - |> Option.defaultValue null)) - + member x.ExecuteTestCase assemblyPath isFsx = + // Set console streams for the AppDomain. + TestConsole.install() let assembly = Assembly.LoadFrom assemblyPath executeAssemblyEntryPoint assembly isFsx - let adSetup = - let setup = new System.AppDomainSetup () - let directory = Path.GetDirectoryName(typeof.Assembly.Location) - setup.ApplicationBase <- directory - setup + let executeBuiltApp assembly dependecies isFsx = + let thisAssemblyDirectory = Path.GetDirectoryName(typeof.Assembly.Location) + let setup = AppDomainSetup(ApplicationBase = thisAssemblyDirectory) + let testCaseDomain = AppDomain.CreateDomain($"built app {assembly}", null, setup) + + testCaseDomain.add_AssemblyResolve(fun _ args -> + dependecies + |> List.tryFind (fun path -> Path.GetFileNameWithoutExtension path = AssemblyName(args.Name).Name) + |> Option.filter FileSystem.FileExistsShim + |> Option.map Assembly.LoadFile + |> Option.toObj + ) - let executeBuiltApp assembly deps = - let ad = AppDomain.CreateDomain((Guid()).ToString(), null, adSetup) let worker = - use _ = new AlreadyLoadedAppDomainResolver() - (ad.CreateInstanceFromAndUnwrap(typeof.Assembly.CodeBase, typeof.FullName)) :?> Worker - worker.ExecuteTestCase assembly (deps |> Array.ofList) + (testCaseDomain.CreateInstanceFromAndUnwrap(typeof.Assembly.CodeBase, typeof.FullName)) :?> Worker + + let outcome, output, errors = worker.ExecuteTestCase assembly isFsx + // Replay streams captured in appdomain. + printf $"{output}" + eprintf $"{errors}" + + AppDomain.Unload testCaseDomain + + outcome, output, errors + #endif let defaultProjectOptions (targetFramework: TargetFramework) = @@ -413,27 +441,12 @@ module rec CompilerAssertHelpers = errors, ex, outputFilePath let compileDisposable (outputDirectory:DirectoryInfo) isExe options targetFramework nameOpt (sources:SourceCodeFileKind list) = - let disposeFile path = - { - new IDisposable with - member _.Dispose() = - try File.Delete path with | _ -> () - } - let disposals = ResizeArray() - let disposeList = - { - new IDisposable with - member _.Dispose() = - for item in disposals do - item.Dispose() - } let name = match nameOpt with | Some name -> name | _ -> getTemporaryFileNameInDirectory outputDirectory.FullName let outputFilePath = Path.ChangeExtension (Path.Combine(outputDirectory.FullName, name), if isExe then ".exe" else ".dll") - disposals.Add(disposeFile outputFilePath) let sources = [ for item in sources do @@ -443,7 +456,6 @@ module rec CompilerAssertHelpers = let source = item.ChangeExtension let destFileName = Path.Combine(outputDirectory.FullName, Path.GetFileName(source.GetSourceFileName)) File.WriteAllText (destFileName, text) - disposals.Add(disposeFile destFileName) yield source.WithFileName(destFileName) | None -> // On Disk file @@ -451,15 +463,9 @@ module rec CompilerAssertHelpers = let source = item.ChangeExtension let destFileName = Path.Combine(outputDirectory.FullName, Path.GetFileName(source.GetSourceFileName)) File.Copy(sourceFileName, destFileName, true) - disposals.Add(disposeFile destFileName) yield source.WithFileName(destFileName) ] - try - disposeList, rawCompile outputFilePath isExe options targetFramework sources - with - | _ -> - disposeList.Dispose() - reraise() + rawCompile outputFilePath isExe options targetFramework sources let assertErrors libAdjust ignoreWarnings (errors: FSharpDiagnostic []) expectedErrors = let errorMessage (error: FSharpDiagnostic) = @@ -520,7 +526,29 @@ module rec CompilerAssertHelpers = finally try Directory.Delete(tempDir, true) with | _ -> () - let rec evaluateReferences (outputPath:DirectoryInfo) (disposals: ResizeArray) ignoreWarnings (cmpl: Compilation) : string[] * string list = + let rec compileCompilationAux outputDirectory ignoreWarnings (cmpl: Compilation) : (FSharpDiagnostic[] * exn option * string) * string list = + + let compilationRefs, deps = evaluateReferences outputDirectory ignoreWarnings cmpl + let isExe, sources, options, targetFramework, name = + match cmpl with + | Compilation(sources, output, options, targetFramework, _, name, _) -> + (match output with | Module -> false | Library -> false | Exe -> true), // isExe + sources, + options, + targetFramework, + name + + let res = compileDisposable outputDirectory isExe (Array.append options compilationRefs) targetFramework name sources + + let deps2 = + compilationRefs + |> Array.filter (fun x -> not (x.Contains("--staticlink"))) + |> Array.map (fun x -> x.Replace("-r:", String.Empty)) + |> List.ofArray + + res, (deps @ deps2) + + and evaluateReferences (outputPath:DirectoryInfo) ignoreWarnings (cmpl: Compilation) : string[] * string list = match cmpl with | Compilation(_, _, _, _, cmpls, _, _) -> let compiledRefs = @@ -528,14 +556,13 @@ module rec CompilerAssertHelpers = |> List.map (fun cmpl -> match cmpl with | CompilationReference (cmpl, staticLink) -> - compileCompilationAux outputPath disposals ignoreWarnings cmpl, staticLink + compileCompilationAux outputPath ignoreWarnings cmpl, staticLink | TestCompilationReference (cmpl) -> let fileName = match cmpl with | TestCompilation.CSharp c when not (String.IsNullOrWhiteSpace c.AssemblyName) -> c.AssemblyName | _ -> getTemporaryFileNameInDirectory outputPath.FullName let tmp = Path.Combine(outputPath.FullName, Path.ChangeExtension(fileName, ".dll")) - disposals.Add({ new IDisposable with member _.Dispose() = File.Delete tmp }) cmpl.EmitAsFile tmp (([||], None, tmp), []), false) @@ -559,38 +586,9 @@ module rec CompilerAssertHelpers = compilationRefs, deps - let compileCompilationAux outputDirectory (disposals: ResizeArray) ignoreWarnings (cmpl: Compilation) : (FSharpDiagnostic[] * exn option * string) * string list = - - let compilationRefs, deps = evaluateReferences outputDirectory disposals ignoreWarnings cmpl - let isExe, sources, options, targetFramework, name = - match cmpl with - | Compilation(sources, output, options, targetFramework, _, name, _) -> - (match output with | Module -> false | Library -> false | Exe -> true), // isExe - sources, - options, - targetFramework, - name - - let disposal, res = compileDisposable outputDirectory isExe (Array.append options compilationRefs) targetFramework name sources - disposals.Add(disposal) - - let deps2 = - compilationRefs - |> Array.filter (fun x -> not (x.Contains("--staticlink"))) - |> Array.map (fun x -> x.Replace("-r:", String.Empty)) - |> List.ofArray - - res, (deps @ deps2) - let compileCompilation ignoreWarnings (cmpl: Compilation) f = - let disposals = ResizeArray() - try - let outputDirectory = DirectoryInfo(createTemporaryDirectory "compileCompilation") - disposals.Add({ new IDisposable with member _.Dispose() = try File.Delete (outputDirectory.FullName) with | _ -> () }) - f (compileCompilationAux outputDirectory disposals ignoreWarnings cmpl) - finally - disposals - |> Seq.iter (fun x -> x.Dispose()) + let outputDirectory = createTemporaryDirectory() + f (compileCompilationAux outputDirectory ignoreWarnings cmpl) // NOTE: This function will not clean up all the compiled projects after itself. // The reason behind is so we can compose verification of test runs easier. @@ -599,47 +597,14 @@ module rec CompilerAssertHelpers = let outputDirectory = match cmpl with | Compilation(outputDirectory = Some outputDirectory) -> DirectoryInfo(outputDirectory.FullName) - | Compilation _ -> DirectoryInfo(createTemporaryDirectory "returnCompilation") + | Compilation _ -> createTemporaryDirectory() outputDirectory.Create() - compileCompilationAux outputDirectory (ResizeArray()) ignoreWarnings cmpl - - let captureConsoleOutputs (func: unit -> obj) = - let out = Console.Out - let err = Console.Error + compileCompilationAux outputDirectory ignoreWarnings cmpl - let stdout = StringBuilder () - let stderr = StringBuilder () + let unwrapException (ex: exn) = ex.InnerException |> Option.ofObj |> Option.map _.Message |> Option.defaultValue ex.Message - use outWriter = new StringWriter (stdout) - use errWriter = new StringWriter (stderr) - - let rc, exn = - try - try - Console.SetOut outWriter - Console.SetError errWriter - let rc = func() - match rc with - | :? int as rc -> Some rc, None - | _ -> None, None - with e -> - let errorMessage = if e.InnerException <> null then e.InnerException.ToString() else e.ToString() - stderr.Append errorMessage |> ignore - None, Some e - finally - Console.SetOut out - Console.SetError err - outWriter.Close() - errWriter.Close() - - rc, stdout.ToString(), stderr.ToString(), exn - - let executeBuiltAppAndReturnResult (outputFilePath: string) (deps: string list) isFsx : (int option * string * string * exn option) = - let rc, stdout, stderr, exn = executeBuiltApp outputFilePath deps isFsx - rc, stdout, stderr, exn - - let executeBuiltAppNewProcessAndReturnResult (outputFilePath: string) : (int * string * string) = + let executeBuiltAppNewProcess (outputFilePath: string) = #if !NETCOREAPP let fileName = outputFilePath let arguments = "" @@ -659,13 +624,12 @@ module rec CompilerAssertHelpers = }""" let runtimeconfigPath = Path.ChangeExtension(outputFilePath, ".runtimeconfig.json") File.WriteAllText(runtimeconfigPath, runtimeconfig) - use _disposal = - { new IDisposable with - member _.Dispose() = try File.Delete runtimeconfigPath with | _ -> () } #endif let timeout = 30000 - let exitCode, output, errors = Commands.executeProcess fileName arguments (Path.GetDirectoryName(outputFilePath)) timeout - (exitCode, output |> String.concat "\n", errors |> String.concat "\n") + let rc, output, errors = Commands.executeProcess fileName arguments (Path.GetDirectoryName(outputFilePath)) timeout + let output = String.Join(Environment.NewLine, output) + let errors = String.Join(Environment.NewLine, errors) + ExitCode rc, output, errors open CompilerAssertHelpers @@ -678,7 +642,7 @@ type CompilerAssert private () = if errors.Length > 0 then Assert.Fail (sprintf "Compile had warnings and/or errors: %A" errors) - executeBuiltApp outputExe [] false |> ignore + executeBuiltApp outputExe [] false ) static let compileLibraryAndVerifyILWithOptions options (source: SourceCodeFileKind) (f: ILVerifier -> unit) = @@ -691,7 +655,6 @@ type CompilerAssert private () = f (ILVerifier outputFilePath) ) - static let compileLibraryAndVerifyDebugInfoWithOptions options (expectedFile: string) (source: SourceCodeFileKind) = let options = [| yield! options; yield"--test:DumpDebugInfo" |] compile false options source (fun (errors, _, outputFilePath) -> @@ -714,6 +677,8 @@ Updated automatically, please check diffs in your pull request, changes must be """ ) + static member UseTransparentCompiler = UseTransparentCompiler + static member Checker = checker static member DefaultProjectOptions = defaultProjectOptions @@ -740,15 +705,14 @@ Updated automatically, please check diffs in your pull request, changes must be returnCompilation cmpl (defaultArg ignoreWarnings false) static member ExecuteAndReturnResult (outputFilePath: string, isFsx: bool, deps: string list, newProcess: bool) = - if not newProcess then - let entryPointReturnCode, deps, isFsx, exn = executeBuiltAppAndReturnResult outputFilePath deps isFsx - entryPointReturnCode, deps, isFsx, exn - else - let processExitCode, deps, isFsx = executeBuiltAppNewProcessAndReturnResult outputFilePath - Some processExitCode, deps, isFsx, None - + let outcome, output, errors = + if not newProcess then + executeBuiltApp outputFilePath deps isFsx + else + executeBuiltAppNewProcess outputFilePath + { Outcome = outcome; StdOut = output; StdErr = errors} - static member Execute(cmpl: Compilation, ?ignoreWarnings, ?beforeExecute, ?newProcess, ?onOutput) = + static member ExecuteAux(cmpl: Compilation, ?ignoreWarnings, ?beforeExecute, ?newProcess) = let copyDependenciesToOutputDir (outputFilePath:string) (deps: string list) = let outputDirectory = Path.GetDirectoryName(outputFilePath) @@ -760,21 +724,26 @@ Updated automatically, please check diffs in your pull request, changes must be let ignoreWarnings = defaultArg ignoreWarnings false let beforeExecute = defaultArg beforeExecute copyDependenciesToOutputDir let newProcess = defaultArg newProcess false - let onOutput = defaultArg onOutput (fun _ -> ()) compileCompilation ignoreWarnings cmpl (fun ((errors, _, outputFilePath), deps) -> assertErrors 0 ignoreWarnings errors [||] beforeExecute outputFilePath deps - if newProcess then - let (exitCode, output, errors) = executeBuiltAppNewProcessAndReturnResult outputFilePath - if exitCode <> 0 then - Assert.Fail errors - onOutput output + if newProcess then + executeBuiltAppNewProcess outputFilePath else - let _rc, _stdout, _stderr, exn = executeBuiltApp outputFilePath deps false - exn |> Option.iter raise) + executeBuiltApp outputFilePath deps false + ) + + static member Execute(cmpl: Compilation, ?ignoreWarnings, ?beforeExecute, ?newProcess) = + let outcome, _, _ = CompilerAssert.ExecuteAux(cmpl, ?ignoreWarnings = ignoreWarnings, ?beforeExecute = beforeExecute, ?newProcess = newProcess) + match outcome with + | ExitCode n when n <> 0 -> failwith $"Process exited with code {n}." + | Failure exn -> raise exn + | _ -> () + static member ExecutionHasOutput(cmpl: Compilation, expectedOutput: string) = - CompilerAssert.Execute(cmpl, newProcess = true, onOutput = (fun output -> Assert.Equal(expectedOutput, output))) + let _, output, _ = CompilerAssert.ExecuteAux(cmpl, newProcess = true) + Assert.Equal(expectedOutput, output) static member Pass (source: string) = let parseResults, fileAnswer = checker.ParseAndCheckFileInProject("test.fs", 0, SourceText.ofString source, defaultProjectOptions TargetFramework.Current) |> Async.RunImmediate @@ -954,7 +923,7 @@ Updated automatically, please check diffs in your pull request, changes must be } )) - let snapshot = FSharpProjectSnapshot.FromOptions(projectOptions, getFileSnapshot) |> Async.RunSynchronously + let snapshot = FSharpProjectSnapshot.FromOptions(projectOptions, getFileSnapshot) |> Async.RunImmediate checker.ParseAndCheckProject(snapshot) else @@ -1038,10 +1007,10 @@ Updated automatically, please check diffs in your pull request, changes must be | Choice2Of2 ex -> errorMessages.Add(ex.Message) | _ -> () - errorMessages, outStream.ToString() + errorMessages, string outStream, string errStream static member RunScriptWithOptions options (source: string) (expectedErrorMessages: string list) = - let errorMessages, _ = CompilerAssert.RunScriptWithOptionsAndReturnResult options source + let errorMessages, _, _ = CompilerAssert.RunScriptWithOptionsAndReturnResult options source if expectedErrorMessages.Length <> errorMessages.Count then Assert.Fail(sprintf "Expected error messages: %A \n\n Actual error messages: %A" expectedErrorMessages errorMessages) else diff --git a/tests/FSharp.Test.Utilities/DirectoryAttribute.fs b/tests/FSharp.Test.Utilities/DirectoryAttribute.fs index 1266bc295a8..f54faec596d 100644 --- a/tests/FSharp.Test.Utilities/DirectoryAttribute.fs +++ b/tests/FSharp.Test.Utilities/DirectoryAttribute.fs @@ -32,7 +32,7 @@ type DirectoryAttribute(dir: string) = | _ -> None let createCompilationUnit path (filename: string) = - let outputDirectoryPath = createTemporaryDirectory "dir" + let outputDirectoryPath = createTemporaryDirectory().FullName let sourceFilePath = normalizePathSeparator (path ++ filename) let fsBslFilePath = sourceFilePath + baselineSuffix + ".err.bsl" let ilBslFilePath = diff --git a/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj b/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj index 7ccc5306751..52c1893073c 100644 --- a/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj +++ b/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj @@ -28,7 +28,9 @@ scriptlib.fsx + + @@ -42,6 +44,10 @@ + + + + diff --git a/tests/FSharp.Test.Utilities/ScriptHelpers.fs b/tests/FSharp.Test.Utilities/ScriptHelpers.fs index 688941934c9..f060cb91398 100644 --- a/tests/FSharp.Test.Utilities/ScriptHelpers.fs +++ b/tests/FSharp.Test.Utilities/ScriptHelpers.fs @@ -24,26 +24,7 @@ type LangVersion = | Latest | SupportsMl -type private EventedTextWriter() = - inherit TextWriter() - let sb = StringBuilder() - let sw = new StringWriter() - let lineWritten = Event() - member _.LineWritten = lineWritten.Publish - override _.Encoding = Encoding.UTF8 - override _.Write(c: char) = - if c = '\n' then - let line = - let v = sb.ToString() - if v.EndsWith("\r") then v.Substring(0, v.Length - 1) - else v - sb.Clear() |> ignore - sw.WriteLine line - lineWritten.Trigger(line) - else sb.Append(c) |> ignore - override _.ToString() = sw.ToString() - -type FSharpScript(?additionalArgs: string[], ?quiet: bool, ?langVersion: LangVersion, ?input: string) = +type FSharpScript(?additionalArgs: string[], ?quiet: bool, ?langVersion: LangVersion) = let additionalArgs = defaultArg additionalArgs [||] let quiet = defaultArg quiet true @@ -72,31 +53,12 @@ type FSharpScript(?additionalArgs: string[], ?quiet: bool, ?langVersion: LangVer let argv = Array.append baseArgs additionalArgs - let inReader = new StringReader(defaultArg input "") - let outWriter = new EventedTextWriter() - let errorWriter = new EventedTextWriter() - - let previousIn, previousOut, previousError = Console.In, Console.Out, Console.Error - - do - Console.SetIn inReader - Console.SetOut outWriter - Console.SetError errorWriter - let fsi = FsiEvaluationSession.Create (config, argv, stdin, stdout, stderr) member _.ValueBound = fsi.ValueBound member _.Fsi = fsi - member _.OutputProduced = outWriter.LineWritten - - member _.ErrorProduced = errorWriter.LineWritten - - member _.GetOutput() = string outWriter - - member _.GetErrorOutput() = string errorWriter - member this.Eval(code: string, ?cancellationToken: CancellationToken, ?desiredCulture: Globalization.CultureInfo) = let originalCulture = Thread.CurrentThread.CurrentCulture Thread.CurrentThread.CurrentCulture <- Option.defaultValue Globalization.CultureInfo.InvariantCulture desiredCulture @@ -127,9 +89,6 @@ type FSharpScript(?additionalArgs: string[], ?quiet: bool, ?langVersion: LangVer interface IDisposable with member this.Dispose() = ((this.Fsi) :> IDisposable).Dispose() - Console.SetIn previousIn - Console.SetOut previousOut - Console.SetError previousError [] module TestHelpers = @@ -141,4 +100,4 @@ module TestHelpers = | Ok(value) -> value | Error ex -> raise ex - let ignoreValue = getValue >> ignore + let ignoreValue x = getValue x |> ignore diff --git a/tests/FSharp.Test.Utilities/ScriptingShims.fsx b/tests/FSharp.Test.Utilities/ScriptingShims.fsx index 392d2b002b6..be9ce9142c5 100644 --- a/tests/FSharp.Test.Utilities/ScriptingShims.fsx +++ b/tests/FSharp.Test.Utilities/ScriptingShims.fsx @@ -2,13 +2,7 @@ namespace global [] module GlobalShims = - - let errorStringWriter = new System.IO.StringWriter() - let oldConsoleError = System.Console.Error - do System.Console.SetError(errorStringWriter) - let exit (code:int) = - System.Console.SetError(oldConsoleError) - if code=0 then + if code = 0 then () - else failwith $"Script called function 'exit' with code={code} and collected in stderr: {errorStringWriter.ToString()}" \ No newline at end of file + else failwith $"Script called function 'exit' with code={code}." diff --git a/tests/FSharp.Test.Utilities/TestConsole.fs b/tests/FSharp.Test.Utilities/TestConsole.fs new file mode 100644 index 00000000000..b28cd398408 --- /dev/null +++ b/tests/FSharp.Test.Utilities/TestConsole.fs @@ -0,0 +1,89 @@ +namespace FSharp.Test + +open System +open System.IO +open System.Text +open System.Threading + +module TestConsole = + + /// Redirects reads performed on different async execution contexts to the relevant TextReader held by AsyncLocal. + type private RedirectingTextReader() = + inherit TextReader() + let holder = AsyncLocal<_>() + do holder.Value <- TextReader.Null + + override _.Peek() = holder.Value.Peek() + override _.Read() = holder.Value.Read() + member _.Value = holder.Value + member _.Set (reader: TextReader) = holder.Value <- reader + + /// Redirects writes performed on different async execution contexts to the relevant TextWriter held by AsyncLocal. + type private RedirectingTextWriter() = + inherit TextWriter() + let holder = AsyncLocal() + do holder.Value <- TextWriter.Null + + override _.Encoding = Encoding.UTF8 + override _.Write(value: char) = holder.Value.Write(value) + override _.Write(value: string) = holder.Value.Write(value) + override _.WriteLine(value: string) = holder.Value.WriteLine(value) + member _.Value = holder.Value + member _.Set (writer: TextWriter) = holder.Value <- writer + + let private localIn = new RedirectingTextReader() + let private localOut = new RedirectingTextWriter() + let private localError = new RedirectingTextWriter() + + let install () = + Console.SetIn localIn + Console.SetOut localOut + Console.SetError localError + + // Taps into the redirected console stream. + type private CapturingWriter(redirecting: RedirectingTextWriter) as this = + inherit StringWriter() + let wrapped = redirecting.Value + do redirecting.Set this + override _.Encoding = Encoding.UTF8 + override _.Write(value: char) = wrapped.Write(value); base.Write(value) + override _.Write(value: string) = wrapped.Write(value); base.Write(value) + override _.WriteLine(value: string) = wrapped.WriteLine(value); base.Write(value) + override _.Dispose (disposing: bool) = + redirecting.Set wrapped + base.Dispose(disposing: bool) + + /// Captures console streams for the current async execution context. + /// Each simultaneously executing test case runs in a separate async execution context. + /// + /// Can be used to capture just a single compilation or eval as well as the whole test case execution output. + type ExecutionCapture() = + do + Console.Out.Flush() + Console.Error.Flush() + + let output = new CapturingWriter(localOut) + let error = new CapturingWriter(localError) + + member _.Dispose() = + output.Dispose() + error.Dispose() + + interface IDisposable with + member this.Dispose (): unit = this.Dispose() + + member _.OutText = + Console.Out.Flush() + string output + + member _.ErrorText = + Console.Error.Flush() + string error + + type ProvideInput(input: string) = + let oldIn = localIn.Value + do + new StringReader(input) |> localIn.Set + + interface IDisposable with + member this.Dispose (): unit = localIn.Set oldIn diff --git a/tests/FSharp.Test.Utilities/TestFramework.fs b/tests/FSharp.Test.Utilities/TestFramework.fs index dfde63f2352..ada5bf3363e 100644 --- a/tests/FSharp.Test.Utilities/TestFramework.fs +++ b/tests/FSharp.Test.Utilities/TestFramework.fs @@ -13,24 +13,24 @@ open Xunit.Sdk let getShortId() = Guid.NewGuid().ToString().[..7] -// Temporary directory is TempPath + "/FSharp.Test.Utilities/yyy-MM-dd-xxxxxxx/" +// Temporary directory is TempPath + "/FSharp.Test.Utilities/xxxxxxx/" let tempDirectoryOfThisTestRun = - let tempDir = Path.GetTempPath() - let today = DateTime.Now.ToString("yyyy-MM-dd") - DirectoryInfo(tempDir) - .CreateSubdirectory($"FSharp.Test.Utilities/{today}-{getShortId()}") - .FullName + let temp = Path.GetTempPath() + lazy DirectoryInfo(temp).CreateSubdirectory($"FSharp.Test.Utilities/{getShortId()}") -let createTemporaryDirectory (part: string) = - DirectoryInfo(tempDirectoryOfThisTestRun) - .CreateSubdirectory($"{part}-{getShortId()}") - .FullName +let cleanUpTemporaryDirectoryOfThisTestRun () = + if tempDirectoryOfThisTestRun.IsValueCreated then + try tempDirectoryOfThisTestRun.Value.Delete(true) with _ -> () + +let createTemporaryDirectory () = + tempDirectoryOfThisTestRun.Value + .CreateSubdirectory($"{getShortId()}") let getTemporaryFileName () = - (createTemporaryDirectory "temp") ++ $"tmp_{getShortId()}" + createTemporaryDirectory().FullName ++ getShortId() let getTemporaryFileNameInDirectory (directory: string) = - directory ++ $"tmp_{getShortId()}" + directory ++ getShortId() // Well, this function is AI generated. let rec copyDirectory (sourceDir: string) (destinationDir: string) (recursive: bool) = @@ -425,13 +425,12 @@ let logConfig (cfg: TestConfig) = log "PEVERIFY = %s" cfg.PEVERIFY log "---------------------------------------------------------------" -let checkOutputPassed (output: string) = - Assert.True(output.Contains "TEST PASSED OK", $"Output does not contain 'TEST PASSED OK':\n{output}") +let outputPassed (output: string) = output.Contains "TEST PASSED OK" let checkResultPassed result = match result with | CmdResult.ErrorLevel (msg1, err) -> Assert.Fail (sprintf "%s. ERRORLEVEL %d" msg1 err) - | CmdResult.Success output -> checkOutputPassed output + | CmdResult.Success output -> Assert.True(outputPassed output, "Output does not contain 'TEST PASSED OK'") let checkResult result = match result with @@ -474,11 +473,8 @@ let testConfig sourceDir (relativePathToTestFixture: string) = let cfg = suiteHelpers.Value let testFixtureFullPath = Path.GetFullPath(sourceDir ++ relativePathToTestFixture) - let description = relativePathToTestFixture.Split('\\', '/') |> String.concat "-" - - let tempTestRoot = createTemporaryDirectory description let tempTestDir = - DirectoryInfo(tempTestRoot) + createTemporaryDirectory() .CreateSubdirectory(relativePathToTestFixture) .FullName copyDirectory testFixtureFullPath tempTestDir true @@ -487,7 +483,7 @@ let testConfig sourceDir (relativePathToTestFixture: string) = let createConfigWithEmptyDirectory() = let cfg = suiteHelpers.Value - { cfg with Directory = createTemporaryDirectory "temp" } + { cfg with Directory = createTemporaryDirectory().FullName } type RedirectToType = | Overwrite of FilePath diff --git a/tests/FSharp.Test.Utilities/Utilities.fs b/tests/FSharp.Test.Utilities/Utilities.fs index 1a6d0ff60f8..484ee6049e2 100644 --- a/tests/FSharp.Test.Utilities/Utilities.fs +++ b/tests/FSharp.Test.Utilities/Utilities.fs @@ -40,21 +40,6 @@ type FactForDESKTOPAttribute() = // This file mimics how Roslyn handles their compilation references for compilation testing module Utilities = - type RedirectConsole() = - let oldStdOut = Console.Out - let oldStdErr = Console.Error - let newStdOut = new StringWriter() - let newStdErr = new StringWriter() - do Console.SetOut(newStdOut) - do Console.SetError(newStdErr) - member _.Output () = string newStdOut - - member _.ErrorOutput () =string newStdErr - - interface IDisposable with - member _.Dispose() = - Console.SetOut(oldStdOut) - Console.SetError(oldStdErr) type Async with static member RunImmediate (computation: Async<'T>, ?cancellationToken ) = @@ -69,20 +54,6 @@ module Utilities = cancellationToken) task.Result - /// Disposable type to implement a simple resolve handler that searches the currently loaded assemblies to see if the requested assembly is already loaded. - type AlreadyLoadedAppDomainResolver () = - let resolveHandler = - ResolveEventHandler(fun _ args -> - let assemblies = AppDomain.CurrentDomain.GetAssemblies() - let assembly = assemblies |> Array.tryFind(fun a -> String.Compare(a.FullName, args.Name,StringComparison.OrdinalIgnoreCase) = 0) - assembly |> Option.defaultValue Unchecked.defaultof - ) - do AppDomain.CurrentDomain.add_AssemblyResolve(resolveHandler) - - interface IDisposable with - member this.Dispose() = AppDomain.CurrentDomain.remove_AssemblyResolve(resolveHandler) - - [] type TargetFramework = | NetStandard20 diff --git a/tests/FSharp.Test.Utilities/XunitHelpers.fs b/tests/FSharp.Test.Utilities/XunitHelpers.fs new file mode 100644 index 00000000000..cda74f7867c --- /dev/null +++ b/tests/FSharp.Test.Utilities/XunitHelpers.fs @@ -0,0 +1,23 @@ +namespace FSharp.Test + +open System +open Xunit.Sdk +open Xunit.Abstractions + +open TestFramework + +/// Installs console support for parallel test runs. +type FSharpXunitFramework(sink: IMessageSink) = + inherit XunitTestFramework(sink) + do + // Because xUnit v2 lacks assembly fixture, the next best place to ensure things get called + // right at the start of the test run is here in the constructor. + // This gets executed once per test assembly. + MessageSink.sinkWriter |> ignore + TestConsole.install() + + interface IDisposable with + member _.Dispose() = + cleanUpTemporaryDirectoryOfThisTestRun () + base.Dispose() + diff --git a/tests/FSharp.Test.Utilities/XunitSetup.fs b/tests/FSharp.Test.Utilities/XunitSetup.fs new file mode 100644 index 00000000000..fb43569b1d0 --- /dev/null +++ b/tests/FSharp.Test.Utilities/XunitSetup.fs @@ -0,0 +1,8 @@ +namespace FSharp.Test + +open Xunit + +module XUnitSetup = + + [] + do () diff --git a/tests/fsharp/FSharpSuite.Tests.fsproj b/tests/fsharp/FSharpSuite.Tests.fsproj index 6673da9f911..dc8fb5a164e 100644 --- a/tests/fsharp/FSharpSuite.Tests.fsproj +++ b/tests/fsharp/FSharpSuite.Tests.fsproj @@ -17,8 +17,8 @@ - - scriptlib.fsx + + XunitSetup.fs diff --git a/tests/fsharp/xunit.runner.json b/tests/fsharp/xunit.runner.json index 4e5a48343ec..c222a4167a8 100644 --- a/tests/fsharp/xunit.runner.json +++ b/tests/fsharp/xunit.runner.json @@ -1,5 +1,4 @@ { "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", - "appDomain": "ifAvailable", - "shadowCopy": false + "appDomain": "denied" } \ No newline at end of file diff --git a/tests/scripts/scriptlib.fsx b/tests/scripts/scriptlib.fsx index 853aecb496e..04b23a93fa8 100644 --- a/tests/scripts/scriptlib.fsx +++ b/tests/scripts/scriptlib.fsx @@ -10,6 +10,14 @@ open System.IO open System.Text open System.Diagnostics +module MessageSink = + let sinkWriter = +#if DEBUG + Console.Out +#else + TextWriter.Null +#endif + [] module Scripting = @@ -77,7 +85,7 @@ module Scripting = if Directory.Exists output then Directory.Delete(output, true) - let log format = printfn format + let log format = fprintfn MessageSink.sinkWriter format type FilePath = string