diff --git a/tests/FSharp.Test.Utilities/CompilerAssert.fs b/tests/FSharp.Test.Utilities/CompilerAssert.fs index f18022a0159..3e6f721379b 100644 --- a/tests/FSharp.Test.Utilities/CompilerAssert.fs +++ b/tests/FSharp.Test.Utilities/CompilerAssert.fs @@ -83,7 +83,7 @@ type CompilerAssert private () = static let _ = config |> ignore - // Do a one time dotnet sdk build to compute the proper set of reference assemblies to pass to the compiler +// Do a one time dotnet sdk build to compute the proper set of reference assemblies to pass to the compiler static let projectFile = """ @@ -98,6 +98,7 @@ type CompilerAssert private () = + @@ -116,6 +117,16 @@ type CompilerAssert private () = """ + static let directoryBuildProps = """ + + + true + + + + +""" + static let programFs = """ open System @@ -126,13 +137,15 @@ let main argv = 0""" let mutable output = "" let mutable errors = "" let mutable cleanUp = true - let projectDirectory = Path.Combine(Path.GetTempPath(), "CompilerAssert", Path.GetRandomFileName()) + let pathToTemp = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "../../../../Temp")) + let projectDirectory = Path.Combine(pathToTemp, "CompilerAssert", Path.GetRandomFileName()) let pathToFSharpCore = typeof.Assembly.Location try try Directory.CreateDirectory(projectDirectory) |> ignore let projectFileName = Path.Combine(projectDirectory, "ProjectFile.fsproj") let programFsFileName = Path.Combine(projectDirectory, "Program.fs") + let directoryBuildPropsFileName = Path.Combine(projectDirectory, "Directory.Build.props") let frameworkReferencesFileName = Path.Combine(projectDirectory, "FrameworkReferences.txt") #if NETCOREAPP File.WriteAllText(projectFileName, projectFile.Replace("$TARGETFRAMEWORK", "netcoreapp3.1").Replace("$FSHARPCORELOCATION", pathToFSharpCore)) @@ -140,6 +153,7 @@ let main argv = 0""" File.WriteAllText(projectFileName, projectFile.Replace("$TARGETFRAMEWORK", "net472").Replace("$FSHARPCORELOCATION", pathToFSharpCore)) #endif File.WriteAllText(programFsFileName, programFs) + File.WriteAllText(directoryBuildPropsFileName, directoryBuildProps) let pInfo = ProcessStartInfo () pInfo.FileName <- config.DotNetExe @@ -150,19 +164,24 @@ let main argv = 0""" pInfo.UseShellExecute <- false let p = Process.Start(pInfo) - let succeeded = p.WaitForExit(10000) + let timeout = 30000 + let succeeded = p.WaitForExit(timeout) output <- p.StandardOutput.ReadToEnd () errors <- p.StandardError.ReadToEnd () - if not (String.IsNullOrWhiteSpace errors) then Assert.Fail errors - if p.ExitCode <> 0 || not succeeded then Assert.Fail(sprintf "Program exited with exit code %d" p.ExitCode) + if not (String.IsNullOrWhiteSpace errors) then Assert.Fail errors + if p.ExitCode <> 0 then Assert.Fail(sprintf "Program exited with exit code %d" p.ExitCode) + if not succeeded then Assert.Fail(sprintf "Program timed out after %d ms" timeout) File.ReadLines(frameworkReferencesFileName) |> Seq.toArray with | e -> cleanUp <- false - printfn "%s" output - printfn "%s" errors + printfn "Project directory: %s" projectDirectory + printfn "STDOUT: %s" output + File.WriteAllText(Path.Combine(projectDirectory, "project.stdout"), output) + printfn "STDERR: %s" errors + File.WriteAllText(Path.Combine(projectDirectory, "project.stderror"), errors) raise (new Exception (sprintf "An error occurred getting netcoreapp references: %A" e)) finally if cleanUp then @@ -275,8 +294,10 @@ let main argv = 0""" Array.zip errors expectedErrors |> Array.iter (fun (actualError, expectedError) -> - let (expectedSeverity, expectedErrorNumber, expectedErrorRange, expectedErrorMsg) = expectedError - let (actualSeverity, actualErrorNumber, actualErrorRange, actualErrorMsg) = actualError + let (expectedSeverity, expectedErrorNumber, expectedErrorRange, expectedErrorMsg: string) = expectedError + let (actualSeverity, actualErrorNumber, actualErrorRange, actualErrorMsg: string) = actualError + let expectedErrorMsg = expectedErrorMsg.Replace("\r\n", "\n") + let actualErrorMsg = actualErrorMsg.Replace("\r\n", "\n") checkEqual "Severity" expectedSeverity actualSeverity checkEqual "ErrorNumber" expectedErrorNumber actualErrorNumber checkEqual "ErrorRange" expectedErrorRange actualErrorRange @@ -300,7 +321,7 @@ let main argv = 0""" | TestCompilationReference (cmpl) -> let filename = match cmpl with - | TestCompilation.CSharp c -> c.AssemblyName + | TestCompilation.CSharp c when not (String.IsNullOrWhiteSpace c.AssemblyName) -> c.AssemblyName | _ -> Path.GetRandomFileName() let tmp = Path.Combine(outputPath, Path.ChangeExtension(filename, ".dll")) disposals.Add({ new IDisposable with @@ -380,10 +401,10 @@ let main argv = 0""" // 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. // TODO: We must not rely on the filesystem when compiling - static let rec returnCompilation (cmpl: Compilation) = + static let rec returnCompilation (cmpl: Compilation) ignoreWarnings = let compileDirectory = Path.Combine(Path.GetTempPath(), "CompilerAssert", Path.GetRandomFileName()) Directory.CreateDirectory(compileDirectory) |> ignore - compileCompilationAux compileDirectory (ResizeArray()) false cmpl + compileCompilationAux compileDirectory (ResizeArray()) ignoreWarnings cmpl static let executeBuiltAppAndReturnResult (outputFilePath: string) (deps: string list) : (int * string * string) = let out = Console.Out @@ -462,8 +483,8 @@ let main argv = 0""" static member Compile(cmpl: Compilation, ?ignoreWarnings) = CompilerAssert.CompileWithErrors(cmpl, [||], defaultArg ignoreWarnings false) - static member CompileRaw(cmpl: Compilation) = - lock gate (fun () -> returnCompilation cmpl) + static member CompileRaw(cmpl: Compilation, ?ignoreWarnings) = + lock gate (fun () -> returnCompilation cmpl (defaultArg ignoreWarnings false)) static member ExecuteAndReturnResult (outputFilePath: string, 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. @@ -493,6 +514,58 @@ let main argv = 0""" CompilerAssert.Execute(cmpl, newProcess = true, onOutput = (fun output -> Assert.AreEqual(expectedOutput, output))) /// Assert that the given source code compiles with the `defaultProjectOptions`, with no errors or warnings + static member CompileOfAst isExe source = + let outputFilePath = Path.ChangeExtension (Path.GetTempFileName(), if isExe then "exe" else ".dll") + let parseOptions = { FSharpParsingOptions.Default with SourceFiles = [|"test.fs"|] } + + let parseResults = + checker.ParseFile("test.fs", SourceText.ofString source, parseOptions) + |> Async.RunSynchronously + + Assert.IsEmpty(parseResults.Errors, sprintf "Parse errors: %A" parseResults.Errors) + Assert.IsTrue(parseResults.ParseTree.IsSome, "no parse tree returned") + + let dependencies = + #if NETCOREAPP + Array.toList getNetCoreAppReferences + #else + [] + #endif + + let compileErrors, statusCode = + checker.Compile([parseResults.ParseTree.Value], "test", outputFilePath, dependencies, executable = isExe, noframework = true) + |> Async.RunSynchronously + + Assert.IsEmpty(compileErrors, sprintf "Compile errors: %A" compileErrors) + Assert.AreEqual(0, statusCode, sprintf "Nonzero status code: %d" statusCode) + outputFilePath + + static member CompileOfAstToDynamicAssembly source = + let assemblyName = sprintf "test-%O" (Guid.NewGuid()) + let parseOptions = { FSharpParsingOptions.Default with SourceFiles = [|"test.fs"|] } + let parseResults = + checker.ParseFile("test.fs", SourceText.ofString source, parseOptions) + |> Async.RunSynchronously + + Assert.IsEmpty(parseResults.Errors, sprintf "Parse errors: %A" parseResults.Errors) + Assert.IsTrue(parseResults.ParseTree.IsSome, "no parse tree returned") + + let dependencies = + #if NETCOREAPP + Array.toList getNetCoreAppReferences + #else + [] + #endif + + let compileErrors, statusCode, assembly = + checker.CompileToDynamicAssembly([parseResults.ParseTree.Value], assemblyName, dependencies, None, noframework = true) + |> Async.RunSynchronously + + Assert.IsEmpty(compileErrors, sprintf "Compile errors: %A" compileErrors) + Assert.AreEqual(0, statusCode, sprintf "Nonzero status code: %d" statusCode) + Assert.IsTrue(assembly.IsSome, "no assembly returned") + Option.get assembly + static member Pass (source: string) = lock gate <| fun () -> let parseResults, fileAnswer = checker.ParseAndCheckFileInProject("test.fs", 0, SourceText.ofString source, defaultProjectOptions) |> Async.RunSynchronously @@ -550,6 +623,27 @@ let main argv = 0""" Assert.AreEqual(errorsExpectedBaseLine.Replace("\r\n","\n"), errorsActual.Replace("\r\n","\n")) + static member TypeCheckWithOptionsAndName options name (source: string) = + lock gate <| fun () -> + let errors = + let parseResults, fileAnswer = + checker.ParseAndCheckFileInProject( + name, + 0, + SourceText.ofString source, + { defaultProjectOptions with OtherOptions = Array.append options defaultProjectOptions.OtherOptions; SourceFiles = [|name|] }) + |> Async.RunSynchronously + + if parseResults.Errors.Length > 0 then + parseResults.Errors + else + + match fileAnswer with + | FSharpCheckFileAnswer.Aborted _ -> Assert.Fail("Type Checker Aborted"); [| |] + | FSharpCheckFileAnswer.Succeeded(typeCheckResults) -> typeCheckResults.Errors + + errors + static member TypeCheckWithOptions options (source: string) = lock gate <| fun () -> let errors = @@ -638,35 +732,39 @@ let main argv = 0""" static member CompileLibraryAndVerifyIL (source: string) (f: ILVerifier -> unit) = CompilerAssert.CompileLibraryAndVerifyILWithOptions [||] source f - static member RunScriptWithOptions options (source: string) (expectedErrorMessages: string list) = - lock gate <| fun () -> - // Intialize output and input streams - use inStream = new StringReader("") - use outStream = new StringWriter() - use errStream = new StringWriter() - - // Build command line arguments & start FSI session - let argv = [| "C:\\fsi.exe" |] - #if NETCOREAPP - let args = Array.append argv [|"--noninteractive"; "--targetprofile:netcore"|] - #else - let args = Array.append argv [|"--noninteractive"; "--targetprofile:mscorlib"|] - #endif - let allArgs = Array.append args options - - let fsiConfig = FsiEvaluationSession.GetDefaultConfiguration() - use fsiSession = FsiEvaluationSession.Create(fsiConfig, allArgs, inStream, outStream, errStream, collectible = true) - - let ch, errors = fsiSession.EvalInteractionNonThrowing source - - let errorMessages = ResizeArray() - errors - |> Seq.iter (fun error -> errorMessages.Add(error.Message)) + static member RunScriptWithOptionsAndReturnResult options (source: string) = + // Intialize output and input streams + use inStream = new StringReader("") + use outStream = new StringWriter() + use errStream = new StringWriter() - match ch with - | Choice2Of2 ex -> errorMessages.Add(ex.Message) - | _ -> () + // Build command line arguments & start FSI session + let argv = [| "C:\\fsi.exe" |] +#if NETCOREAPP + let args = Array.append argv [|"--noninteractive"; "--targetprofile:netcore"|] +#else + let args = Array.append argv [|"--noninteractive"; "--targetprofile:mscorlib"|] +#endif + let allArgs = Array.append args options + + let fsiConfig = FsiEvaluationSession.GetDefaultConfiguration() + use fsiSession = FsiEvaluationSession.Create(fsiConfig, allArgs, inStream, outStream, errStream, collectible = true) + + let ch, errors = fsiSession.EvalInteractionNonThrowing source + + let errorMessages = ResizeArray() + errors + |> Seq.iter (fun error -> errorMessages.Add(error.Message)) + match ch with + | Choice2Of2 ex -> errorMessages.Add(ex.Message) + | _ -> () + + errorMessages + + static member RunScriptWithOptions options (source: string) (expectedErrorMessages: string list) = + lock gate <| fun () -> + 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