Skip to content
This repository was archived by the owner on Dec 23, 2024. It is now read-only.

Commit 9bfc726

Browse files
vzarytovskiinosami
authored andcommitted
Added fsx evaluation/running to new testing framework. (dotnet#10106)
* Added fsx evaluation via the FSharpScript.Eval as well as FsiEvaluationSession.EvalInteractionNonThrowing * Turn off test filter for XUnit, and add to NUnit
1 parent 914a983 commit 9bfc726

File tree

8 files changed

+183
-39
lines changed

8 files changed

+183
-39
lines changed

eng/Build.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,11 +286,11 @@ function TestUsingMSBuild([string] $testProject, [string] $targetFramework, [str
286286
}
287287

288288
function TestUsingXUnit([string] $testProject, [string] $targetFramework, [string]$testadapterpath) {
289-
TestUsingMsBuild -testProject $testProject -targetFramework $targetFramework -testadapterpath $testadapterpath -noTestFilter $false
289+
TestUsingMsBuild -testProject $testProject -targetFramework $targetFramework -testadapterpath $testadapterpath -noTestFilter $true
290290
}
291291

292292
function TestUsingNUnit([string] $testProject, [string] $targetFramework, [string]$testadapterpath) {
293-
TestUsingMsBuild -testProject $testProject -targetFramework $targetFramework -testadapterpath $testadapterpath -noTestFilter $true
293+
TestUsingMsBuild -testProject $testProject -targetFramework $targetFramework -testadapterpath $testadapterpath -noTestFilter $false
294294
}
295295

296296
function BuildCompiler() {

tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
<Compile Include="ConstraintSolver\MemberConstraints.fs" />
4949
<Compile Include="Interop\SimpleInteropTests.fs" />
5050
<Compile Include="Interop\VisibilityTests.fs" />
51+
<Compile Include="Scripting\Interactive.fs" />
5152
</ItemGroup>
5253

5354
<ItemGroup>

tests/FSharp.Compiler.ComponentTests/Language/CompilerDirectiveTests.fs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,13 @@ module ``Test Compiler Directives`` =
2323
""" |> compile
2424
|> shouldFail
2525
|> withSingleDiagnostic (Warning 213, Line 2, Col 1, Line 2, Col 10, "'' is not a valid assembly name")
26+
27+
module ``Test compiler directives in FSI`` =
28+
[<Fact>]
29+
let ``r# "" is invalid`` () =
30+
Fsx"""
31+
#r ""
32+
""" |> ignoreWarnings
33+
|> eval
34+
|> shouldFail
35+
|> withSingleDiagnostic (Error 2301, Line 2, Col 1, Line 2, Col 6, "'' is not a valid assembly name")
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
2+
3+
namespace FSharp.Compiler.ComponentTests.Scripting
4+
5+
open Xunit
6+
open FSharp.Test.Utilities.Compiler
7+
8+
module ``Interactive tests`` =
9+
[<Fact>]
10+
let ``Eval object value``() =
11+
Fsx "1+1"
12+
|> eval
13+
|> shouldSucceed
14+
|> withEvalTypeEquals typeof<int>
15+
|> withEvalValueEquals 2
16+
17+
18+
module ``External FSI tests`` =
19+
[<Fact>]
20+
let ``Eval object value``() =
21+
Fsx "1+1"
22+
|> runFsi
23+
|> shouldSucceed
24+
25+
[<Fact>]
26+
let ``Invalid expression should fail``() =
27+
Fsx "1+a"
28+
|> runFsi
29+
|> shouldFail

tests/FSharp.Test.Utilities/Compiler.fs

Lines changed: 108 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace FSharp.Test.Utilities
44

5+
open FSharp.Compiler.Interactive.Shell
6+
open FSharp.Compiler.Scripting
57
open FSharp.Compiler.SourceCodeServices
68
open FSharp.Test.Utilities
79
open 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 "\nThe following substring:\n %A\nwas not found in the %A\nOutput:\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 "\nThe following substring:\n %A\nwas not found in the %A\nOutput:\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

tests/FSharp.Test.Utilities/CompilerAssert.fs

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -665,35 +665,39 @@ let main argv = 0"""
665665
static member CompileLibraryAndVerifyIL (source: string) (f: ILVerifier -> unit) =
666666
CompilerAssert.CompileLibraryAndVerifyILWithOptions [||] source f
667667

668-
static member RunScriptWithOptions options (source: string) (expectedErrorMessages: string list) =
669-
lock gate <| fun () ->
670-
// Intialize output and input streams
671-
use inStream = new StringReader("")
672-
use outStream = new StringWriter()
673-
use errStream = new StringWriter()
674-
675-
// Build command line arguments & start FSI session
676-
let argv = [| "C:\\fsi.exe" |]
677-
#if NETCOREAPP
678-
let args = Array.append argv [|"--noninteractive"; "--targetprofile:netcore"|]
679-
#else
680-
let args = Array.append argv [|"--noninteractive"; "--targetprofile:mscorlib"|]
681-
#endif
682-
let allArgs = Array.append args options
683-
684-
let fsiConfig = FsiEvaluationSession.GetDefaultConfiguration()
685-
use fsiSession = FsiEvaluationSession.Create(fsiConfig, allArgs, inStream, outStream, errStream, collectible = true)
686-
687-
let ch, errors = fsiSession.EvalInteractionNonThrowing source
688-
689-
let errorMessages = ResizeArray()
690-
errors
691-
|> Seq.iter (fun error -> errorMessages.Add(error.Message))
668+
static member RunScriptWithOptionsAndReturnResult options (source: string) =
669+
// Intialize output and input streams
670+
use inStream = new StringReader("")
671+
use outStream = new StringWriter()
672+
use errStream = new StringWriter()
673+
674+
// Build command line arguments & start FSI session
675+
let argv = [| "C:\\fsi.exe" |]
676+
#if NETCOREAPP
677+
let args = Array.append argv [|"--noninteractive"; "--targetprofile:netcore"|]
678+
#else
679+
let args = Array.append argv [|"--noninteractive"; "--targetprofile:mscorlib"|]
680+
#endif
681+
let allArgs = Array.append args options
692682

693-
match ch with
694-
| Choice2Of2 ex -> errorMessages.Add(ex.Message)
695-
| _ -> ()
683+
let fsiConfig = FsiEvaluationSession.GetDefaultConfiguration()
684+
use fsiSession = FsiEvaluationSession.Create(fsiConfig, allArgs, inStream, outStream, errStream, collectible = true)
696685

686+
let ch, errors = fsiSession.EvalInteractionNonThrowing source
687+
688+
let errorMessages = ResizeArray()
689+
errors
690+
|> Seq.iter (fun error -> errorMessages.Add(error.Message))
691+
692+
match ch with
693+
| Choice2Of2 ex -> errorMessages.Add(ex.Message)
694+
| _ -> ()
695+
696+
errorMessages
697+
698+
static member RunScriptWithOptions options (source: string) (expectedErrorMessages: string list) =
699+
lock gate <| fun () ->
700+
let errorMessages = CompilerAssert.RunScriptWithOptionsAndReturnResult options source
697701
if expectedErrorMessages.Length <> errorMessages.Count then
698702
Assert.Fail(sprintf "Expected error messages: %A \n\n Actual error messages: %A" expectedErrorMessages errorMessages)
699703
else

tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
<ProjectReference Include="$(FSharpSourcesRoot)\fsharp\FSharp.Core\FSharp.Core.fsproj" />
3636
<ProjectReference Include="$(FSharpSourcesRoot)\fsharp\FSharp.Build\FSharp.Build.fsproj" />
3737
<ProjectReference Include="$(FSharpSourcesRoot)\fsharp\FSharp.Compiler.Private\FSharp.Compiler.Private.fsproj" />
38+
<ProjectReference Include="$(FSharpSourcesRoot)\fsharp\FSharp.Compiler.Private.Scripting\FSharp.Compiler.Private.Scripting.fsproj" />
3839
<ProjectReference Include="$(FSharpTestsRoot)\fsharpqa\testenv\src\PEVerify\PEVerify.csproj" />
3940
</ItemGroup>
4041

@@ -56,6 +57,7 @@
5657
<PackageReference Include="Microsoft.CodeAnalysis.Test.Resources.Proprietary" Version="$(MicrosoftCodeAnalysisTestResourcesProprietaryVersion)" />
5758
<PackageReference Include="Microsoft.NETCore.App.Ref" Version="3.1.0" IncludeAssets="none" PrivateAssets="all" GeneratePathProperty="true" />
5859
</ItemGroup>
60+
5961
<ItemGroup>
6062
<EmbeddedResource Include="$(PkgMicrosoft_NETCore_App_Ref)\ref\netcoreapp3.1\mscorlib.dll" LogicalName="netcoreapp31.mscorlib.dll">
6163
<CopyToOutputDirectory>Always</CopyToOutputDirectory>

tests/FSharp.Test.Utilities/ILChecker.fs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,6 @@ module ILChecker =
134134

135135
let verifyIL (dllFilePath: string) (expectedIL: string) =
136136
checkIL dllFilePath [expectedIL]
137-
138137
let verifyILAndReturnActual (dllFilePath: string) (expectedIL: string) = checkILAux' [] dllFilePath [expectedIL]
139138
let reassembleIL ilFilePath dllFilePath =
140139
let ilasmPath = config.ILASM

0 commit comments

Comments
 (0)