diff --git a/src/Compiler/Service/service.fs b/src/Compiler/Service/service.fs index 525faf3be3d..5f6588bd770 100644 --- a/src/Compiler/Service/service.fs +++ b/src/Compiler/Service/service.fs @@ -72,10 +72,10 @@ module CompileHelpers = try f exiter - 0 + None with e -> stopProcessingRecovery e range0 - 1 + Some e /// Compile using the given flags. Source files names are resolved via the FileSystem API. The output file must be given by a -o flag. let compileFromArgs (ctok, argv: string[], legacyReferenceResolver, tcImportsCapture, dynamicAssemblyCreator) = diff --git a/src/Compiler/Service/service.fsi b/src/Compiler/Service/service.fsi index 0e48a0d6360..3e4fde2229c 100644 --- a/src/Compiler/Service/service.fsi +++ b/src/Compiler/Service/service.fsi @@ -400,11 +400,12 @@ type public FSharpChecker = /// Compile using the given flags. Source files names are resolved via the FileSystem API. /// The output file must be given by a -o flag. /// The first argument is ignored and can just be "fsc.exe". + /// The method returns the collected diagnostics, and (possibly) a terminating exception. /// /// /// The command line arguments for the project build. /// An optional string used for tracing compiler operations associated with this request. - member Compile: argv: string[] * ?userOpName: string -> Async + member Compile: argv: string[] * ?userOpName: string -> Async /// /// Try to get type check results for a file. This looks up the results of recent type checks of the diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerDirectives/Ifdef.fs b/tests/FSharp.Compiler.ComponentTests/CompilerDirectives/Ifdef.fs new file mode 100644 index 00000000000..99d019504d6 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/CompilerDirectives/Ifdef.fs @@ -0,0 +1,57 @@ +namespace CompilerDirectives + +open Xunit +open FSharp.Test.Compiler + +module Ifdef = + + let ifdefSource = """ +[] +let main _ = + #if MYDEFINE1 + 1 + #else + 2 + #endif +""" + + [] + [] + [] + let ifdefTest (mydefine, expectedExitCode) = + + FSharp ifdefSource + |> withDefines [mydefine] + |> compileExeAndRun + |> withExitCode expectedExitCode + + + let sourceExtraEndif = """ +#if MYDEFINE1 +printf "1" +#endif +(**)#endif(**) +0 +""" + + [] + let extraEndif () = + + FSharp sourceExtraEndif + |> withDefines ["MYDEFINE1"] + |> asExe + |> compile + |> withDiagnosticMessage "#endif has no matching #if in implementation file" + + let sourceUnknownHash = """ +module A +#ifxx +#abc +""" + + [] + let unknownHashDirectiveIsIgnored () = + + FSharp sourceUnknownHash + |> compile + |> shouldSucceed \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/ConstraintSolver/MemberConstraints.fs b/tests/FSharp.Compiler.ComponentTests/ConstraintSolver/MemberConstraints.fs index b3c33031acb..c560008da0c 100644 --- a/tests/FSharp.Compiler.ComponentTests/ConstraintSolver/MemberConstraints.fs +++ b/tests/FSharp.Compiler.ComponentTests/ConstraintSolver/MemberConstraints.fs @@ -45,7 +45,6 @@ else () |> compile |> run |> shouldSucceed - |> withExitCode 0 [] let ``Respect nowarn 957 for extension method`` () = diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TryCatch/TryCatch.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TryCatch/TryCatch.fs index 9a43d34d429..1e5187167a7 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 = CompilerAssert.ExecuteAndReturnResult (dllFile, isFsx=false, deps = s.Dependencies, newProcess=true) + let _exitCode, _stdout, stderr, _exn = CompilerAssert.ExecuteAndReturnResult (dllFile, isFsx=false, deps = s.Dependencies, newProcess=true) - Assert.NotEqual(0,exitCode) + Assert.True(stderr.Contains "stack overflow" || 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 4b50b28aa12..4ddec1e3405 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -33,6 +33,7 @@ + diff --git a/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/CompilationFromCmdlineArgsTests.fs b/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/CompilationFromCmdlineArgsTests.fs index 9cfbbb9df80..294db344223 100644 --- a/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/CompilationFromCmdlineArgsTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/CompilationFromCmdlineArgsTests.fs @@ -38,12 +38,12 @@ module CompilationFromCmdlineArgsTests = yield! methodOptions method |] - let diagnostics, exitCode = checker.Compile(args) |> Async.RunSynchronously + let diagnostics, exn = checker.Compile(args) |> Async.RunSynchronously for diag in diagnostics do printfn "%A" diag - Assert.Equal(exitCode, 0) + Assert.Equal(exn, None) finally Environment.CurrentDirectory <- oldWorkDir diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl index 2bbc7c5bf52..15e1dd848e6 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl @@ -2166,7 +2166,7 @@ FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.CodeAnalysis.FSharpParseFileResults,FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults]] GetBackgroundCheckResultsForFileInProject(System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.CodeAnalysis.FSharpProjectOptions,Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Diagnostics.FSharpDiagnostic]]] GetProjectOptionsFromScript(System.String, FSharp.Compiler.Text.ISourceText, Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.DateTime], Microsoft.FSharp.Core.FSharpOption`1[System.String[]], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[System.Int64], Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot,Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Diagnostics.FSharpDiagnostic]]] GetProjectSnapshotFromScript(System.String, FSharp.Compiler.Text.ISourceTextNew, Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.DocumentSource], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.DateTime], Microsoft.FSharp.Core.FSharpOption`1[System.String[]], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[System.Int64], Microsoft.FSharp.Core.FSharpOption`1[System.String]) -FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.Diagnostics.FSharpDiagnostic[],System.Int32]] Compile(System.String[], Microsoft.FSharp.Core.FSharpOption`1[System.String]) +FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.Diagnostics.FSharpDiagnostic[],Microsoft.FSharp.Core.FSharpOption`1[System.Exception]]] Compile(System.String[], Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.Text.Range,FSharp.Compiler.Text.Range][]] MatchBraces(System.String, FSharp.Compiler.Text.ISourceText, FSharp.Compiler.CodeAnalysis.FSharpParsingOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.Text.Range,FSharp.Compiler.Text.Range][]] MatchBraces(System.String, System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.IEvent`2[Microsoft.FSharp.Control.FSharpHandler`1[FSharp.Compiler.CodeAnalysis.FSharpProjectOptions],FSharp.Compiler.CodeAnalysis.FSharpProjectOptions] ProjectChecked diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl index 2bbc7c5bf52..15e1dd848e6 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl @@ -2166,7 +2166,7 @@ FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.CodeAnalysis.FSharpParseFileResults,FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults]] GetBackgroundCheckResultsForFileInProject(System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.CodeAnalysis.FSharpProjectOptions,Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Diagnostics.FSharpDiagnostic]]] GetProjectOptionsFromScript(System.String, FSharp.Compiler.Text.ISourceText, Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.DateTime], Microsoft.FSharp.Core.FSharpOption`1[System.String[]], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[System.Int64], Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot,Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Diagnostics.FSharpDiagnostic]]] GetProjectSnapshotFromScript(System.String, FSharp.Compiler.Text.ISourceTextNew, Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.DocumentSource], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.DateTime], Microsoft.FSharp.Core.FSharpOption`1[System.String[]], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[System.Int64], Microsoft.FSharp.Core.FSharpOption`1[System.String]) -FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.Diagnostics.FSharpDiagnostic[],System.Int32]] Compile(System.String[], Microsoft.FSharp.Core.FSharpOption`1[System.String]) +FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.Diagnostics.FSharpDiagnostic[],Microsoft.FSharp.Core.FSharpOption`1[System.Exception]]] Compile(System.String[], Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.Text.Range,FSharp.Compiler.Text.Range][]] MatchBraces(System.String, FSharp.Compiler.Text.ISourceText, FSharp.Compiler.CodeAnalysis.FSharpParsingOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.Text.Range,FSharp.Compiler.Text.Range][]] MatchBraces(System.String, System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.IEvent`2[Microsoft.FSharp.Control.FSharpHandler`1[FSharp.Compiler.CodeAnalysis.FSharpProjectOptions],FSharp.Compiler.CodeAnalysis.FSharpProjectOptions] ProjectChecked diff --git a/tests/FSharp.Test.Utilities/Compiler.fs b/tests/FSharp.Test.Utilities/Compiler.fs index f535bc21c5a..5e5629b089f 100644 --- a/tests/FSharp.Test.Utilities/Compiler.fs +++ b/tests/FSharp.Test.Utilities/Compiler.fs @@ -170,10 +170,13 @@ 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 + { ExitCode: int option StdOut: string - StdErr: string } + StdErr: string + Exn: exn option } type RunOutput = | EvalOutput of Result @@ -699,7 +702,7 @@ module rec Compiler = let private compileFSharpCompilation compilation ignoreWarnings (cUnit: CompilationUnit) : CompilationResult = use redirect = new RedirectConsole() - let ((err: FSharpDiagnostic[], rc: int, outputFilePath: string), deps) = + let ((err: FSharpDiagnostic[], exn, outputFilePath: string), deps) = CompilerAssert.CompileRaw(compilation, ignoreWarnings) // Create and stash the console output @@ -711,7 +714,7 @@ module rec Compiler = Adjust = 0 PerFileErrors = diagnostics Diagnostics = diagnostics |> List.map snd - Output = Some (RunOutput.ExecutionOutput { ExitCode = rc; StdOut = redirect.Output(); StdErr = redirect.ErrorOutput() }) + Output = Some (RunOutput.ExecutionOutput { ExitCode = None; StdOut = redirect.Output(); StdErr = redirect.ErrorOutput(); Exn = exn }) Compilation = cUnit } @@ -978,14 +981,13 @@ module rec Compiler = | SourceCodeFileKind.Fsx _ -> true | _ -> false | _ -> false - let exitCode, output, errors = CompilerAssert.ExecuteAndReturnResult (p, isFsx, s.Dependencies, 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 }) } - if exitCode = 0 then - CompilationResult.Success executionResult - else - CompilationResult.Failure executionResult + 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 compileAndRun = compile >> run @@ -1080,27 +1082,25 @@ module rec Compiler = opts.ToArray() let errors, stdOut = CompilerAssert.RunScriptWithOptionsAndReturnResult options source - let executionOutputwithStdOut: RunOutput option = - ExecutionOutput { StdOut = stdOut; ExitCode = 0; StdErr = "" } - |> Some - let result = + let mkResult output = { OutputPath = None Dependencies = [] Adjust = 0 Diagnostics = [] PerFileErrors= [] - Output = executionOutputwithStdOut + Output = Some output Compilation = cUnit } - - if errors.Count > 0 then - let output = ExecutionOutput { - ExitCode = -1 - StdOut = String.Empty - StdErr = ((errors |> String.concat "\n").Replace("\r\n","\n")) } - CompilationResult.Failure { result with Output = Some output } + + if errors.Count = 0 then + let output = + ExecutionOutput { ExitCode = None; StdOut = stdOut; StdErr = ""; Exn = None } + CompilationResult.Success (mkResult output) else - CompilationResult.Success result - + 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()) @@ -1190,7 +1190,7 @@ Actual: | Some p -> match ILChecker.verifyILAndReturnActual [] p expected with | true, _, _ -> result - | false, errorMsg, _actualIL -> CompilationResult.Failure( {s with Output = Some (ExecutionOutput { StdOut = errorMsg; ExitCode = 0; StdErr = "" })} ) + | 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}" @@ -1706,7 +1706,7 @@ Actual: | None -> failwith "Execution output is missing, cannot check exit code." | Some o -> match o with - | ExecutionOutput e -> Assert.Equal(expectedExitCode, e.ExitCode) + | ExecutionOutput {ExitCode = Some exitCode} -> Assert.Equal(expectedExitCode, exitCode) | _ -> failwith "Cannot check exit code on this run result." result diff --git a/tests/FSharp.Test.Utilities/CompilerAssert.fs b/tests/FSharp.Test.Utilities/CompilerAssert.fs index fc2875d8955..37a8e25e164 100644 --- a/tests/FSharp.Test.Utilities/CompilerAssert.fs +++ b/tests/FSharp.Test.Utilities/CompilerAssert.fs @@ -322,7 +322,7 @@ module rec CompilerAssertHelpers = else entryPoint let args = mkDefaultArgs entryPoint - captureConsoleOutputs (fun () -> entryPoint.Invoke(Unchecked.defaultof, args) |> ignore) + captureConsoleOutputs (fun () -> entryPoint.Invoke(Unchecked.defaultof, args)) #if NETCOREAPP let executeBuiltApp assembly deps isFsx = @@ -409,8 +409,8 @@ module rec CompilerAssertHelpers = // Generate a response file, purely for diagnostic reasons. File.WriteAllLines(Path.ChangeExtension(outputFilePath, ".rsp"), args) - let errors, rc = checker.Compile args |> Async.RunImmediate - errors, rc, outputFilePath + let errors, ex = checker.Compile args |> Async.RunImmediate + errors, ex, outputFilePath let compileDisposable (outputDirectory:DirectoryInfo) isExe options targetFramework nameOpt (sources:SourceCodeFileKind list) = let disposeFile path = @@ -537,7 +537,7 @@ module rec CompilerAssertHelpers = let tmp = Path.Combine(outputPath.FullName, Path.ChangeExtension(fileName, ".dll")) disposals.Add({ new IDisposable with member _.Dispose() = File.Delete tmp }) cmpl.EmitAsFile tmp - (([||], 0, tmp), []), false) + (([||], None, tmp), []), false) let compilationRefs = compiledRefs @@ -559,7 +559,7 @@ module rec CompilerAssertHelpers = compilationRefs, deps - let compileCompilationAux outputDirectory (disposals: ResizeArray) ignoreWarnings (cmpl: Compilation) : (FSharpDiagnostic[] * int * string) * string list = + 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 = @@ -604,7 +604,7 @@ module rec CompilerAssertHelpers = outputDirectory.Create() compileCompilationAux outputDirectory (ResizeArray()) ignoreWarnings cmpl - let captureConsoleOutputs (func: unit -> unit) = + let captureConsoleOutputs (func: unit -> obj) = let out = Console.Out let err = Console.Error @@ -614,29 +614,30 @@ module rec CompilerAssertHelpers = use outWriter = new StringWriter (stdout) use errWriter = new StringWriter (stderr) - let succeeded, exn = + let rc, exn = try try Console.SetOut outWriter Console.SetError errWriter - func () - true, None + 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 - false, Some e + None, Some e finally Console.SetOut out Console.SetError err outWriter.Close() errWriter.Close() - succeeded, stdout.ToString(), stderr.ToString(), exn + rc, stdout.ToString(), stderr.ToString(), exn - let executeBuiltAppAndReturnResult (outputFilePath: string) (deps: string list) isFsx : (int * string * string) = - let succeeded, stdout, stderr, _ = executeBuiltApp outputFilePath deps isFsx - let exitCode = if succeeded then 0 else -1 - exitCode, stdout, stderr + 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) = #if !NETCOREAPP @@ -663,7 +664,7 @@ module rec CompilerAssertHelpers = member _.Dispose() = try File.Delete runtimeconfigPath with | _ -> () } #endif let timeout = 30000 - let exitCode, output, errors = Commands.executeProcess (Some fileName) arguments (Path.GetDirectoryName(outputFilePath)) timeout + let exitCode, output, errors = Commands.executeProcess fileName arguments (Path.GetDirectoryName(outputFilePath)) timeout (exitCode, output |> String.concat "\n", errors |> String.concat "\n") open CompilerAssertHelpers @@ -677,7 +678,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 |> ignore ) static let compileLibraryAndVerifyILWithOptions options (source: SourceCodeFileKind) (f: ILVerifier -> unit) = @@ -739,11 +740,13 @@ 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 we execute in-process (true by default), then the only way of getting STDOUT is to redirect it to SB, and STDERR is from catching an exception. if not newProcess then - executeBuiltAppAndReturnResult outputFilePath deps isFsx + let entryPointReturnCode, deps, isFsx, exn = executeBuiltAppAndReturnResult outputFilePath deps isFsx + entryPointReturnCode, deps, isFsx, exn else - executeBuiltAppNewProcessAndReturnResult outputFilePath + let processExitCode, deps, isFsx = executeBuiltAppNewProcessAndReturnResult outputFilePath + Some processExitCode, deps, isFsx, None + static member Execute(cmpl: Compilation, ?ignoreWarnings, ?beforeExecute, ?newProcess, ?onOutput) = @@ -767,7 +770,7 @@ Updated automatically, please check diffs in your pull request, changes must be Assert.Fail errors onOutput output else - let _succeeded, _stdout, _stderr, exn = executeBuiltApp outputFilePath deps false + let _rc, _stdout, _stderr, exn = executeBuiltApp outputFilePath deps false exn |> Option.iter raise) static member ExecutionHasOutput(cmpl: Compilation, expectedOutput: string) = diff --git a/tests/FSharp.Test.Utilities/ILChecker.fs b/tests/FSharp.Test.Utilities/ILChecker.fs index 4474ef01c10..de0fbd8050b 100644 --- a/tests/FSharp.Test.Utilities/ILChecker.fs +++ b/tests/FSharp.Test.Utilities/ILChecker.fs @@ -16,7 +16,7 @@ module ILChecker = let private exec exe args = let arguments = args |> String.concat " " let timeout = 30000 - let exitCode, _output, errors = Commands.executeProcess (Some exe) arguments "" timeout + let exitCode, _output, errors = Commands.executeProcess exe arguments "" timeout let errors = errors |> String.concat Environment.NewLine errors, exitCode diff --git a/tests/FSharp.Test.Utilities/Peverifier.fs b/tests/FSharp.Test.Utilities/Peverifier.fs index 35db5b208fb..f3ccc7de2b1 100644 --- a/tests/FSharp.Test.Utilities/Peverifier.fs +++ b/tests/FSharp.Test.Utilities/Peverifier.fs @@ -25,7 +25,7 @@ module PEVerifier = let private exec exe args = let arguments = args |> String.concat " " let timeout = 30000 - let exitCode, _output, errors = Commands.executeProcess (Some exe) arguments "" timeout + let exitCode, _output, errors = Commands.executeProcess exe arguments "" timeout let errors = errors |> String.concat Environment.NewLine errors, exitCode diff --git a/tests/FSharp.Test.Utilities/ProjectGeneration.fs b/tests/FSharp.Test.Utilities/ProjectGeneration.fs index 78feb049435..a75784240dd 100644 --- a/tests/FSharp.Test.Utilities/ProjectGeneration.fs +++ b/tests/FSharp.Test.Utilities/ProjectGeneration.fs @@ -1360,9 +1360,8 @@ type ProjectWorkflowBuilder yield! projectOptions.OtherOptions yield! projectOptions.SourceFiles |] - let! _diagnostics, exitCode = checker.Compile(arguments) - if exitCode <> 0 then - exn $"Compilation failed with exit code {exitCode}" |> raise + let! _diagnostics, ex = checker.Compile(arguments) + if ex.IsSome then raise ex.Value return ctx } diff --git a/tests/FSharp.Test.Utilities/TestFramework.fs b/tests/FSharp.Test.Utilities/TestFramework.fs index 6e1611beb5c..dfde63f2352 100644 --- a/tests/FSharp.Test.Utilities/TestFramework.fs +++ b/tests/FSharp.Test.Utilities/TestFramework.fs @@ -63,69 +63,66 @@ module Commands = // Execute the process pathToExe passing the arguments: arguments with the working directory: workingDir timeout after timeout milliseconds -1 = wait forever // returns exit code, stdio and stderr as string arrays let executeProcess pathToExe arguments workingDir (timeout:int) = - match pathToExe with - | Some path -> - let commandLine = ResizeArray() - let errorsList = ResizeArray() - let outputList = ResizeArray() - let errorslock = obj() - let outputlock = obj() - let outputDataReceived (message: string) = - if not (isNull message) then - lock outputlock (fun () -> outputList.Add(message)) - - let errorDataReceived (message: string) = - if not (isNull message) then - lock errorslock (fun () -> errorsList.Add(message)) - - commandLine.Add $"cd {workingDir}" - commandLine.Add $"{path} {arguments} /bl" - - let psi = ProcessStartInfo() - psi.FileName <- path - psi.WorkingDirectory <- workingDir - psi.RedirectStandardOutput <- true - psi.RedirectStandardError <- true - psi.Arguments <- arguments - psi.CreateNoWindow <- true - // When running tests, we want to roll forward to minor versions (including previews). - psi.EnvironmentVariables["DOTNET_ROLL_FORWARD"] <- "LatestMajor" - psi.EnvironmentVariables["DOTNET_ROLL_FORWARD_TO_PRERELEASE"] <- "1" - psi.EnvironmentVariables.Remove("MSBuildSDKsPath") // Host can sometimes add this, and it can break things - psi.UseShellExecute <- false - - use p = new Process() - p.StartInfo <- psi - - p.OutputDataReceived.Add(fun a -> outputDataReceived a.Data) - p.ErrorDataReceived.Add(fun a -> errorDataReceived a.Data) - - if p.Start() then - p.BeginOutputReadLine() - p.BeginErrorReadLine() - if not(p.WaitForExit(timeout)) then - // Timed out resolving throw a diagnostic. - raise (new TimeoutException(sprintf "Timeout executing command '%s' '%s'" (psi.FileName) (psi.Arguments))) - else - p.WaitForExit() - #if DEBUG - let workingDir' = - if workingDir = "" - then - // Assign working dir to prevent default to C:\Windows\System32 - let executionLocation = Assembly.GetExecutingAssembly().Location - Path.GetDirectoryName executionLocation - else - workingDir - - lock gate (fun () -> - File.WriteAllLines(Path.Combine(workingDir', "commandline.txt"), commandLine) - File.WriteAllLines(Path.Combine(workingDir', "StandardOutput.txt"), outputList) - File.WriteAllLines(Path.Combine(workingDir', "StandardError.txt"), errorsList) - ) - #endif - p.ExitCode, outputList.ToArray(), errorsList.ToArray() - | None -> -1, Array.empty, Array.empty + let commandLine = ResizeArray() + let errorsList = ResizeArray() + let outputList = ResizeArray() + let errorslock = obj() + let outputlock = obj() + let outputDataReceived (message: string) = + if not (isNull message) then + lock outputlock (fun () -> outputList.Add(message)) + + let errorDataReceived (message: string) = + if not (isNull message) then + lock errorslock (fun () -> errorsList.Add(message)) + + commandLine.Add $"cd {workingDir}" + commandLine.Add $"{pathToExe} {arguments} /bl" + + let psi = ProcessStartInfo() + psi.FileName <- pathToExe + psi.WorkingDirectory <- workingDir + psi.RedirectStandardOutput <- true + psi.RedirectStandardError <- true + psi.Arguments <- arguments + psi.CreateNoWindow <- true + // When running tests, we want to roll forward to minor versions (including previews). + psi.EnvironmentVariables["DOTNET_ROLL_FORWARD"] <- "LatestMajor" + psi.EnvironmentVariables["DOTNET_ROLL_FORWARD_TO_PRERELEASE"] <- "1" + psi.EnvironmentVariables.Remove("MSBuildSDKsPath") // Host can sometimes add this, and it can break things + psi.UseShellExecute <- false + + use p = new Process() + p.StartInfo <- psi + + p.OutputDataReceived.Add(fun a -> outputDataReceived a.Data) + p.ErrorDataReceived.Add(fun a -> errorDataReceived a.Data) + + if p.Start() then + p.BeginOutputReadLine() + p.BeginErrorReadLine() + if not(p.WaitForExit(timeout)) then + // Timed out resolving throw a diagnostic. + raise (new TimeoutException(sprintf "Timeout executing command '%s' '%s'" (psi.FileName) (psi.Arguments))) + else + p.WaitForExit() +#if DEBUG + let workingDir' = + if workingDir = "" + then + // Assign working dir to prevent default to C:\Windows\System32 + let executionLocation = Assembly.GetExecutingAssembly().Location + Path.GetDirectoryName executionLocation + else + workingDir + + lock gate (fun () -> + File.WriteAllLines(Path.Combine(workingDir', "commandline.txt"), commandLine) + File.WriteAllLines(Path.Combine(workingDir', "StandardOutput.txt"), outputList) + File.WriteAllLines(Path.Combine(workingDir', "StandardError.txt"), errorsList) + ) +#endif + p.ExitCode, outputList.ToArray(), errorsList.ToArray() let getfullpath workDir (path:string) = let rooted = diff --git a/tests/FSharp.Test.Utilities/Utilities.fs b/tests/FSharp.Test.Utilities/Utilities.fs index 6ed885d4e44..1a6d0ff60f8 100644 --- a/tests/FSharp.Test.Utilities/Utilities.fs +++ b/tests/FSharp.Test.Utilities/Utilities.fs @@ -245,7 +245,7 @@ let main argv = 0""" File.WriteAllText(directoryBuildTargetsFileName, directoryBuildTargets) let timeout = 120000 - let exitCode, dotnetoutput, dotneterrors = Commands.executeProcess (Some config.DotNetExe) "build" projectDirectory timeout + let exitCode, dotnetoutput, dotneterrors = Commands.executeProcess config.DotNetExe "build" projectDirectory timeout if exitCode <> 0 || errors.Length > 0 then errors <- dotneterrors