22
33namespace FSharp.Test.Utilities
44
5+ open FSharp.Compiler .Interactive .Shell
6+ open FSharp.Compiler .Scripting
57open FSharp.Compiler .SourceCodeServices
68open FSharp.Test .Utilities
79open FSharp.Test .Utilities .Assert
@@ -68,17 +70,23 @@ module rec Compiler =
6870 Range: Range
6971 Message: string }
7072
73+ type EvalOutput = Result< FsiValue option, exn>
74+
7175 type ExecutionOutput =
7276 { ExitCode: int
7377 StdOut: string
7478 StdErr: string }
7579
80+ type RunOutput =
81+ | EvalOutput of EvalOutput
82+ | ExecutionOutput of ExecutionOutput
83+
7684 type Output =
7785 { OutputPath: string option
7886 Dependencies: string list
7987 Adjust: int
8088 Diagnostics: ErrorInfo list
81- Output: ExecutionOutput option }
89+ Output: RunOutput option }
8290
8391 type TestResult =
8492 | Success of Output
@@ -400,15 +408,71 @@ module rec Compiler =
400408 | None -> failwith " Compilation didn't produce any output. Unable to run. (did you forget to set output type to Exe?)"
401409 | Some p ->
402410 let ( exitCode , output , errors ) = CompilerAssert.ExecuteAndReturnResult ( p, s.Dependencies, false )
403- let executionResult = { s with Output = Some { ExitCode = exitCode; StdOut = output; StdErr = errors } }
411+ let executionResult = { s with Output = Some ( ExecutionOutput { ExitCode = exitCode; StdOut = output; StdErr = errors }) }
404412 if exitCode = 0 then
405413 Success executionResult
406414 else
407415 Failure executionResult
408416
409417 let compileAndRun = compile >> run
410-
411418 let compileExeAndRun = asExe >> compileAndRun
419+ let private evalFSharp ( fs : FSharpCompilationSource ) : TestResult =
420+ let source = getSource fs.Source
421+ let options = fs.Options |> Array.ofList
422+
423+ use script = new FSharpScript( additionalArgs= options)
424+
425+ let (( evalresult : Result < FsiValue option , exn >), ( err : FSharpErrorInfo [])) = script.Eval( source)
426+
427+ let diagnostics = err |> fromFSharpErrorInfo
428+
429+ let result =
430+ { OutputPath = None
431+ Dependencies = []
432+ Adjust = 0
433+ Diagnostics = diagnostics
434+ Output = Some( EvalOutput evalresult) }
435+
436+ let ( errors , warnings ) = partitionErrors diagnostics
437+
438+ let evalError = match evalresult with Ok _ -> false | _ -> true
439+
440+ if evalError || errors.Length > 0 || ( warnings.Length > 0 && not fs.IgnoreWarnings) then
441+ Failure result
442+ else
443+ Success result
444+
445+ let eval ( cUnit : CompilationUnit ) : TestResult =
446+ match cUnit with
447+ | FS fs -> evalFSharp fs
448+ | _ -> failwith " Script evaluation is only supported for F#."
449+
450+ let runFsi ( cUnit : CompilationUnit ) : TestResult =
451+ match cUnit with
452+ | FS fs ->
453+ let source = getSource fs.Source
454+
455+ let options = fs.Options |> Array.ofList
456+
457+ let errors = CompilerAssert.RunScriptWithOptionsAndReturnResult options source
458+
459+ let result =
460+ { OutputPath = None
461+ Dependencies = []
462+ Adjust = 0
463+ Diagnostics = []
464+ Output = None }
465+
466+ if errors.Count > 0 then
467+ let output = ExecutionOutput {
468+ ExitCode = - 1
469+ StdOut = String.Empty
470+ StdErr = (( errors |> String.concat " \n " ). Replace( " \r\n " , " \n " )) }
471+ Failure { result with Output = Some output }
472+ else
473+ Success result
474+ | _ -> failwith " FSI running only supports F#."
475+
412476
413477 let private createBaselineErrors ( baseline : Baseline ) actualErrors extension : unit =
414478 match baseline.SourceFilename with
@@ -465,6 +529,7 @@ module rec Compiler =
465529 if not success then
466530 createBaselineErrors bsl actualIL " fs.il.err"
467531 Assert.Fail( errorMsg)
532+
468533 let verifyILBaseline ( cUnit : CompilationUnit ) : CompilationUnit =
469534 match cUnit with
470535 | FS fs ->
@@ -541,7 +606,7 @@ module rec Compiler =
541606
542607 let private assertResultsCategory ( what : string ) ( selector : Output -> ErrorInfo list ) ( expected : ErrorInfo list ) ( result : TestResult ) : TestResult =
543608 match result with
544- | Success r | Failure r ->
609+ | Success r | Failure r ->
545610 assertErrors what r.Adjust ( selector r) expected
546611 result
547612
@@ -605,7 +670,7 @@ module rec Compiler =
605670 result
606671
607672 let withMessages ( messages : string list ) ( result : TestResult ) : TestResult =
608- checkErrorMessages messages ( fun r -> r.Diagnostics) result
673+ checkErrorMessages messages ( fun r -> r.Diagnostics) result
609674
610675 let withMessage ( message : string ) ( result : TestResult ) : TestResult =
611676 withMessages [ message] result
@@ -627,7 +692,10 @@ module rec Compiler =
627692 | Success r | Failure r ->
628693 match r.Output with
629694 | None -> failwith " Execution output is missing, cannot check exit code."
630- | Some o -> Assert.AreEqual( o.ExitCode, expectedExitCode, sprintf " Exit code was expected to be: %A , but got %A ." expectedExitCode o.ExitCode)
695+ | Some o ->
696+ match o with
697+ | ExecutionOutput e -> Assert.AreEqual( e.ExitCode, expectedExitCode, sprintf " Exit code was expected to be: %A , but got %A ." expectedExitCode e.ExitCode)
698+ | _ -> failwith " Cannot check exit code on this run result."
631699 result
632700
633701 let private checkOutput ( category : string ) ( substring : string ) ( selector : ExecutionOutput -> string ) ( result : TestResult ) : TestResult =
@@ -636,9 +704,12 @@ module rec Compiler =
636704 match r.Output with
637705 | None -> failwith ( sprintf " Execution output is missing cannot check \" %A \" " category)
638706 | Some o ->
639- let where = selector o
640- if not ( where.Contains( substring)) then
641- failwith ( sprintf " \n The following substring:\n %A \n was not found in the %A \n Output:\n %A " substring category where)
707+ match o with
708+ | ExecutionOutput e ->
709+ let where = selector e
710+ if not ( where.Contains( substring)) then
711+ failwith ( sprintf " \n The following substring:\n %A \n was not found in the %A \n Output:\n %A " substring category where)
712+ | _ -> failwith " Cannot check output on this run result."
642713 result
643714
644715 let withOutputContains ( substring : string ) ( result : TestResult ) : TestResult =
@@ -649,3 +720,31 @@ module rec Compiler =
649720
650721 let withStdErrContains ( substring : string ) ( result : TestResult ) : TestResult =
651722 checkOutput " STDERR" substring ( fun o -> o.StdErr) result
723+
724+ // TODO: probably needs a bit of simplification, + need to remove that pyramid of doom.
725+ let private assertEvalOutput ( selector : FsiValue -> 'T ) ( value : 'T ) ( result : TestResult ) : TestResult =
726+ match result with
727+ | Success r | Failure r ->
728+ match r.Output with
729+ | None -> failwith " Execution output is missing cannot check value."
730+ | Some o ->
731+ match o with
732+ | EvalOutput e ->
733+ match e with
734+ | Ok v ->
735+ match v with
736+ | None -> failwith " Cannot assert value of evaluation, since it is None."
737+ | Some e -> Assert.AreEqual( value, ( selector e))
738+ | Result.Error ex -> raise ex
739+ | _ -> failwith " Only 'eval' output is supported."
740+ result
741+
742+ // TODO: Need to support for:
743+ // STDIN, to test completions
744+ // Contains
745+ // Cancellation
746+ let withEvalValueEquals ( value : 'T ) ( result : TestResult ) : TestResult =
747+ assertEvalOutput ( fun ( x : FsiValue ) -> x.ReflectionValue :?> 'T) value result
748+
749+ let withEvalTypeEquals t ( result : TestResult ) : TestResult =
750+ assertEvalOutput ( fun ( x : FsiValue ) -> x.ReflectionType) t result
0 commit comments