diff --git a/eng/Build.ps1 b/eng/Build.ps1 index 33b86aba6e..f4b8149a16 100644 --- a/eng/Build.ps1 +++ b/eng/Build.ps1 @@ -286,11 +286,11 @@ function TestUsingMSBuild([string] $testProject, [string] $targetFramework, [str } function TestUsingXUnit([string] $testProject, [string] $targetFramework, [string]$testadapterpath) { - TestUsingMsBuild -testProject $testProject -targetFramework $targetFramework -testadapterpath $testadapterpath -noTestFilter $false + TestUsingMsBuild -testProject $testProject -targetFramework $targetFramework -testadapterpath $testadapterpath -noTestFilter $true } function TestUsingNUnit([string] $testProject, [string] $targetFramework, [string]$testadapterpath) { - TestUsingMsBuild -testProject $testProject -targetFramework $targetFramework -testadapterpath $testadapterpath -noTestFilter $true + TestUsingMsBuild -testProject $testProject -targetFramework $targetFramework -testadapterpath $testadapterpath -noTestFilter $false } function BuildCompiler() { diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index b82cc462d4..f97a823591 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -46,6 +46,7 @@ + diff --git a/tests/FSharp.Compiler.ComponentTests/Language/CompilerDirectiveTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/CompilerDirectiveTests.fs index 32219920d9..0237a6d4d8 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/CompilerDirectiveTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/CompilerDirectiveTests.fs @@ -23,3 +23,13 @@ module ``Test Compiler Directives`` = """ |> compile |> shouldFail |> withSingleDiagnostic (Warning 213, Line 2, Col 1, Line 2, Col 10, "'' is not a valid assembly name") + +module ``Test compiler directives in FSI`` = + [] + let ``r# "" is invalid`` () = + Fsx""" +#r "" + """ |> ignoreWarnings + |> eval + |> shouldFail + |> withSingleDiagnostic (Error 2301, Line 2, Col 1, Line 2, Col 6, "'' is not a valid assembly name") \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Scripting/Interactive.fs b/tests/FSharp.Compiler.ComponentTests/Scripting/Interactive.fs new file mode 100644 index 0000000000..a1eb8fa884 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Scripting/Interactive.fs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace FSharp.Compiler.ComponentTests.Scripting + +open Xunit +open FSharp.Test.Utilities.Compiler + +module ``Interactive tests`` = + [] + let ``Eval object value``() = + Fsx "1+1" + |> eval + |> shouldSucceed + |> withEvalTypeEquals typeof + |> withEvalValueEquals 2 + + +module ``External FSI tests`` = + [] + let ``Eval object value``() = + Fsx "1+1" + |> runFsi + |> shouldSucceed + + [] + let ``Invalid expression should fail``() = + Fsx "1+a" + |> runFsi + |> shouldFail \ No newline at end of file diff --git a/tests/FSharp.Test.Utilities/Compiler.fs b/tests/FSharp.Test.Utilities/Compiler.fs index d1312d1989..f63bf89e32 100644 --- a/tests/FSharp.Test.Utilities/Compiler.fs +++ b/tests/FSharp.Test.Utilities/Compiler.fs @@ -2,6 +2,8 @@ namespace FSharp.Test.Utilities +open FSharp.Compiler.Interactive.Shell +open FSharp.Compiler.Scripting open FSharp.Compiler.SourceCodeServices open FSharp.Test.Utilities open FSharp.Test.Utilities.Assert @@ -68,17 +70,23 @@ module rec Compiler = Range: Range Message: string } + type EvalOutput = Result + type ExecutionOutput = { ExitCode: int StdOut: string StdErr: string } + type RunOutput = + | EvalOutput of EvalOutput + | ExecutionOutput of ExecutionOutput + type Output = { OutputPath: string option Dependencies: string list Adjust: int Diagnostics: ErrorInfo list - Output: ExecutionOutput option } + Output: RunOutput option } type TestResult = | Success of Output @@ -400,15 +408,71 @@ module rec Compiler = | None -> failwith "Compilation didn't produce any output. Unable to run. (did you forget to set output type to Exe?)" | Some p -> let (exitCode, output, errors) = CompilerAssert.ExecuteAndReturnResult (p, s.Dependencies, false) - let executionResult = { s with Output = Some { ExitCode = exitCode; StdOut = output; StdErr = errors } } + let executionResult = { s with Output = Some (ExecutionOutput { ExitCode = exitCode; StdOut = output; StdErr = errors }) } if exitCode = 0 then Success executionResult else Failure executionResult let compileAndRun = compile >> run - let compileExeAndRun = asExe >> compileAndRun + let private evalFSharp (fs: FSharpCompilationSource) : TestResult = + let source = getSource fs.Source + let options = fs.Options |> Array.ofList + + use script = new FSharpScript(additionalArgs=options) + + let ((evalresult: Result), (err: FSharpErrorInfo[])) = script.Eval(source) + + let diagnostics = err |> fromFSharpErrorInfo + + let result = + { OutputPath = None + Dependencies = [] + Adjust = 0 + Diagnostics = diagnostics + Output = Some(EvalOutput evalresult) } + + let (errors, warnings) = partitionErrors diagnostics + + let evalError = match evalresult with Ok _ -> false | _ -> true + + if evalError || errors.Length > 0 || (warnings.Length > 0 && not fs.IgnoreWarnings) then + Failure result + else + Success result + + let eval (cUnit: CompilationUnit) : TestResult = + match cUnit with + | FS fs -> evalFSharp fs + | _ -> failwith "Script evaluation is only supported for F#." + + let runFsi (cUnit: CompilationUnit) : TestResult = + match cUnit with + | FS fs -> + let source = getSource fs.Source + + let options = fs.Options |> Array.ofList + + let errors = CompilerAssert.RunScriptWithOptionsAndReturnResult options source + + let result = + { OutputPath = None + Dependencies = [] + Adjust = 0 + Diagnostics = [] + Output = None } + + if errors.Count > 0 then + let output = ExecutionOutput { + ExitCode = -1 + StdOut = String.Empty + StdErr = ((errors |> String.concat "\n").Replace("\r\n","\n")) } + Failure { result with Output = Some output } + else + Success result + | _ -> failwith "FSI running only supports F#." + let private createBaselineErrors (baseline: Baseline) actualErrors extension : unit = match baseline.SourceFilename with @@ -465,6 +529,7 @@ module rec Compiler = if not success then createBaselineErrors bsl actualIL "fs.il.err" Assert.Fail(errorMsg) + let verifyILBaseline (cUnit: CompilationUnit) : CompilationUnit = match cUnit with | FS fs -> @@ -541,7 +606,7 @@ module rec Compiler = let private assertResultsCategory (what: string) (selector: Output -> ErrorInfo list) (expected: ErrorInfo list) (result: TestResult) : TestResult = match result with - | Success r | Failure r -> + | Success r | Failure r -> assertErrors what r.Adjust (selector r) expected result @@ -605,7 +670,7 @@ module rec Compiler = result let withMessages (messages: string list) (result: TestResult) : TestResult = - checkErrorMessages messages (fun r -> r.Diagnostics) result + checkErrorMessages messages (fun r -> r.Diagnostics) result let withMessage (message: string) (result: TestResult) : TestResult = withMessages [message] result @@ -627,7 +692,10 @@ module rec Compiler = | Success r | Failure r -> match r.Output with | None -> failwith "Execution output is missing, cannot check exit code." - | Some o -> Assert.AreEqual(o.ExitCode, expectedExitCode, sprintf "Exit code was expected to be: %A, but got %A." expectedExitCode o.ExitCode) + | Some o -> + match o with + | ExecutionOutput e -> Assert.AreEqual(e.ExitCode, expectedExitCode, sprintf "Exit code was expected to be: %A, but got %A." expectedExitCode e.ExitCode) + | _ -> failwith "Cannot check exit code on this run result." result let private checkOutput (category: string) (substring: string) (selector: ExecutionOutput -> string) (result: TestResult) : TestResult = @@ -636,9 +704,12 @@ module rec Compiler = match r.Output with | None -> failwith (sprintf "Execution output is missing cannot check \"%A\"" category) | Some o -> - let where = selector o - if not (where.Contains(substring)) then - failwith (sprintf "\nThe following substring:\n %A\nwas not found in the %A\nOutput:\n %A" substring category where) + match o with + | ExecutionOutput e -> + let where = selector e + if not (where.Contains(substring)) then + failwith (sprintf "\nThe following substring:\n %A\nwas not found in the %A\nOutput:\n %A" substring category where) + | _ -> failwith "Cannot check output on this run result." result let withOutputContains (substring: string) (result: TestResult) : TestResult = @@ -649,3 +720,31 @@ module rec Compiler = let withStdErrContains (substring: string) (result: TestResult) : TestResult = checkOutput "STDERR" substring (fun o -> o.StdErr) result + + // TODO: probably needs a bit of simplification, + need to remove that pyramid of doom. + let private assertEvalOutput (selector: FsiValue -> 'T) (value: 'T) (result: TestResult) : TestResult = + match result with + | Success r | Failure r -> + match r.Output with + | None -> failwith "Execution output is missing cannot check value." + | Some o -> + match o with + | EvalOutput e -> + match e with + | Ok v -> + match v with + | None -> failwith "Cannot assert value of evaluation, since it is None." + | Some e -> Assert.AreEqual(value, (selector e)) + | Result.Error ex -> raise ex + | _ -> failwith "Only 'eval' output is supported." + result + + // TODO: Need to support for: + // STDIN, to test completions + // Contains + // Cancellation + let withEvalValueEquals (value: 'T) (result: TestResult) : TestResult = + assertEvalOutput (fun (x: FsiValue) -> x.ReflectionValue :?> 'T) value result + + let withEvalTypeEquals t (result: TestResult) : TestResult = + assertEvalOutput (fun (x: FsiValue) -> x.ReflectionType) t result \ No newline at end of file diff --git a/tests/FSharp.Test.Utilities/CompilerAssert.fs b/tests/FSharp.Test.Utilities/CompilerAssert.fs index 82599ffc14..843d6d5839 100644 --- a/tests/FSharp.Test.Utilities/CompilerAssert.fs +++ b/tests/FSharp.Test.Utilities/CompilerAssert.fs @@ -665,35 +665,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() + + // 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 - match ch with - | Choice2Of2 ex -> errorMessages.Add(ex.Message) - | _ -> () + 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 diff --git a/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj b/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj index 3ea86adf99..0bfdf0255f 100644 --- a/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj +++ b/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj @@ -32,6 +32,7 @@ + @@ -53,6 +54,7 @@ + Always diff --git a/tests/FSharp.Test.Utilities/ILChecker.fs b/tests/FSharp.Test.Utilities/ILChecker.fs index 91126acb5c..bff010c5f4 100644 --- a/tests/FSharp.Test.Utilities/ILChecker.fs +++ b/tests/FSharp.Test.Utilities/ILChecker.fs @@ -134,7 +134,6 @@ module ILChecker = let verifyIL (dllFilePath: string) (expectedIL: string) = checkIL dllFilePath [expectedIL] - let verifyILAndReturnActual (dllFilePath: string) (expectedIL: string) = checkILAux' [] dllFilePath [expectedIL] let reassembleIL ilFilePath dllFilePath = let ilasmPath = config.ILASM