Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ module FscCliTests =
/// Original: SOURCE="E_MissingSourceFile02.fs X:\doesnotexist.fs"
/// Expected: //<Expects id="FS0225" status="error">Source file ['"].+['"] could not be found</Expects>
/// CLI Test: FSC with non-existent absolute path (Windows-style)
[<FactForWINDOWS>]
[<FactForDESKTOP>]
let ``fsc missing source file - absolute Windows path reports FS0225`` () =
let result = runFscProcess ["X:\\doesnotexist.fs"]
Assert.NotEqual(0, result.ExitCode)
Expand All @@ -54,7 +54,7 @@ module FscCliTests =
/// Original: SOURCE="E_MissingSourceFile03.fs \\qwerty\y\doesnotexist.fs"
/// Expected: //<Expects id="FS0225" status="error">Source file ['"].+['"] could not be found</Expects>
/// CLI Test: FSC with non-existent UNC path
[<FactForWINDOWS>]
[<FactForDESKTOP>]
let ``fsc missing source file - UNC path reports FS0225`` () =
let result = runFscProcess ["\\\\qwerty\\y\\doesnotexist.fs"]
Assert.NotEqual(0, result.ExitCode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace CompilerOptions.Fsi

open System
open System.IO
open Xunit
open FSharp.Test
open FSharp.Test.Compiler
Expand Down Expand Up @@ -110,3 +112,197 @@ module FsiCliTests =
// FSI should report error for unrecognized option
Assert.NotEqual(0, result.ExitCode)
Assert.Contains("Unrecognized option: '--subsystemversion'", result.StdErr)

// ============================================================================
// --quiet option tests
// ============================================================================

/// CLI Test: --quiet suppresses the banner
[<Fact>]
let ``fsi quiet - suppresses banner`` () =
let result = runFsiProcess ["--quiet"; "--exec"; "--nologo"]
Assert.Equal(0, result.ExitCode)
Assert.DoesNotContain("Microsoft (R) F# Interactive", result.StdOut)

/// In-process test: --quiet suppresses feedback output but expressions still evaluate
[<Fact>]
let ``fsi quiet - expressions evaluate correctly`` () =
Fsx """let x = 1 + 1"""
|> withOptions ["--quiet"]
|> runFsi
|> shouldSucceed

// ============================================================================
// --exec option tests
// ============================================================================

/// CLI Test: --exec causes FSI to exit after evaluating (no interactive prompt)
[<Fact>]
let ``fsi exec - exits after evaluating script`` () =
let tmpFile = Path.Combine(Path.GetTempPath(), $"fsi_exec_test_{Guid.NewGuid()}.fsx")
try
File.WriteAllText(tmpFile, "printfn \"hello from exec\"")
let result = runFsiProcess ["--exec"; "--nologo"; tmpFile]
Assert.Equal(0, result.ExitCode)
Assert.Contains("hello from exec", result.StdOut)
finally
try File.Delete(tmpFile) with _ -> ()

// ============================================================================
// --use option tests
// ============================================================================

/// CLI Test: --use:file.fsx loads and executes a script file
[<Fact>]
let ``fsi use - loads and executes script file`` () =
let tmpFile = Path.Combine(Path.GetTempPath(), $"fsi_use_test_{Guid.NewGuid()}.fsx")
try
File.WriteAllText(tmpFile, "printfn \"loaded via use\"")
let result = runFsiProcess ["--nologo"; "--exec"; $"--use:{tmpFile}"]
Assert.Equal(0, result.ExitCode)
Assert.Contains("loaded via use", result.StdOut)
finally
try File.Delete(tmpFile) with _ -> ()

/// CLI Test: --use with nonexistent file produces error
[<Fact>]
let ``fsi use - nonexistent file produces error`` () =
let result = runFsiProcess ["--exec"; "--use:nonexistent_file_xyz.fsx"]
Assert.NotEqual(0, result.ExitCode)

// ============================================================================
// --load option tests
// ============================================================================

/// CLI Test: --load:file.fsx loads a file (definitions available)
[<Fact>]
let ``fsi load - loads file definitions`` () =
let tmpFile = Path.Combine(Path.GetTempPath(), $"fsi_load_test_{Guid.NewGuid()}.fsx")
try
File.WriteAllText(tmpFile, "let loadedValue = 42")
let result = runFsiProcess ["--nologo"; "--exec"; $"--load:{tmpFile}"]
Assert.Equal(0, result.ExitCode)
finally
try File.Delete(tmpFile) with _ -> ()

/// CLI Test: --load with nonexistent file produces error
[<Fact>]
let ``fsi load - nonexistent file produces error`` () =
let result = runFsiProcess ["--exec"; "--load:nonexistent_file_xyz.fsx"]
Assert.NotEqual(0, result.ExitCode)

// ============================================================================
// --gui option tests (switch: +/-)
// ============================================================================

/// CLI Test: --gui- is accepted without error
[<Fact>]
let ``fsi gui - gui minus accepted`` () =
Fsx """1+1"""
|> withOptions ["--gui-"]
|> runFsi
|> shouldSucceed

/// CLI Test: --gui+ is accepted without error
[<Fact>]
let ``fsi gui - gui plus accepted`` () =
Fsx """1+1"""
|> withOptions ["--gui+"]
|> runFsi
|> shouldSucceed

// ============================================================================
// --readline option tests (switch: +/-)
// ============================================================================

/// CLI Test: --readline- is accepted without error
[<Fact>]
let ``fsi readline - readline minus accepted`` () =
Fsx """1+1"""
|> withOptions ["--readline-"]
|> runFsi
|> shouldSucceed

/// CLI Test: --readline+ is accepted without error
[<Fact>]
let ``fsi readline - readline plus accepted`` () =
Fsx """1+1"""
|> withOptions ["--readline+"]
|> runFsi
|> shouldSucceed

// ============================================================================
// --quotations-debug option tests (switch: +/-)
// ============================================================================

/// CLI Test: --quotations-debug+ is accepted without error
[<Fact>]
let ``fsi quotations-debug - plus accepted`` () =
Fsx """1+1"""
|> withOptions ["--quotations-debug+"]
|> runFsi
|> shouldSucceed

/// CLI Test: --quotations-debug- is accepted without error
[<Fact>]
let ``fsi quotations-debug - minus accepted`` () =
Fsx """1+1"""
|> withOptions ["--quotations-debug-"]
|> runFsi
|> shouldSucceed

// ============================================================================
// --shadowcopyreferences option tests (switch: +/-)
// ============================================================================

/// CLI Test: --shadowcopyreferences+ is accepted without error
[<Fact>]
let ``fsi shadowcopyreferences - plus accepted`` () =
Fsx """1+1"""
|> withOptions ["--shadowcopyreferences+"]
|> runFsi
|> shouldSucceed

/// CLI Test: --shadowcopyreferences- is accepted without error
[<Fact>]
let ``fsi shadowcopyreferences - minus accepted`` () =
Fsx """1+1"""
|> withOptions ["--shadowcopyreferences-"]
|> runFsi
|> shouldSucceed

// ============================================================================
// --nologo option tests
// ============================================================================

/// CLI Test: --nologo suppresses the banner
[<Fact>]
let ``fsi nologo - suppresses banner in subprocess`` () =
let result = runFsiProcess ["--nologo"; "--exec"]
Assert.Equal(0, result.ExitCode)
Assert.DoesNotContain("Microsoft (R) F# Interactive", result.StdOut)

/// In-process test: FSI without --nologo shows the banner
[<Fact>]
let ``fsi nologo - without nologo shows banner`` () =
Fsx """1+1"""
|> runFsi
|> shouldSucceed
|> withStdOutContains "Microsoft"

// ============================================================================
// Additional error case tests
// ============================================================================

/// CLI Test: completely unknown option produces FS0243
[<Fact>]
let ``fsi error - unknown option produces FS0243`` () =
let result = runFsiProcess ["--not-a-real-option"]
Assert.NotEqual(0, result.ExitCode)
Assert.Contains("Unrecognized option: '--not-a-real-option'", result.StdErr)

/// CLI Test: --warn with invalid level produces error
[<Fact>]
let ``fsi error - invalid warn level produces error`` () =
let result = runFsiProcess ["--warn:invalid"; "--exec"]
Assert.NotEqual(0, result.ExitCode)
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,9 @@
<Compile Include="CompilerOptions\fsc\reflectionfree.fs" />
<Compile Include="CompilerOptions\fsc\refonlyrefout.fs" />
<Compile Include="CompilerOptions\fsc\sourceFiles.fs" />
<Compile Include="CompilerOptions\fsc\FscCliTests.fs" />
<Compile Include="CompilerOptions\fsi\FsiCliTests.fs" />
<Compile Include="CompilerOptions\CliProcessTests.fs" />
<Compile Include="CompilerService\RangeModule.fs" />
<Compile Include="CompilerService\Caches.fs" />
<Compile Include="CompilerService\LruCache.fs" />
Expand Down
33 changes: 33 additions & 0 deletions tests/FSharp.Test.Utilities/Compiler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2074,3 +2074,36 @@ Actual:
match hash with
| Some h -> h
| None -> failwith "Implied signature hash returned 'None' which should not happen"

// ========================================================================
// CLI subprocess helpers for testing options that cause FSI/FSC to exit
// (e.g. --help, unrecognized options, missing files).
// These cannot be tested in-process because the process exits before
// a session is created.
// ========================================================================

type CliProcessResult =
{ ExitCode: int
StdOut: string
StdErr: string }

let private runCliProcess (toolDll: string) (args: string list) : CliProcessResult =
let envVars = Map.ofList [ "DOTNET_ROLL_FORWARD", "LatestMajor"
"DOTNET_ROLL_FORWARD_TO_PRERELEASE", "1" ]
let cfg = config "Debug" envVars
let dotnet = cfg.DotNetExe
let allArgs = toolDll :: args |> String.concat " "
let exitCode, stdout, stderr = Commands.executeProcess dotnet allArgs (Directory.GetCurrentDirectory())
{ ExitCode = exitCode; StdOut = stdout; StdErr = stderr }

let runFsiProcess (args: string list) : CliProcessResult =
let envVars = Map.ofList [ "DOTNET_ROLL_FORWARD", "LatestMajor"
"DOTNET_ROLL_FORWARD_TO_PRERELEASE", "1" ]
let cfg = config "Debug" envVars
runCliProcess cfg.FSI args

let runFscProcess (args: string list) : CliProcessResult =
let envVars = Map.ofList [ "DOTNET_ROLL_FORWARD", "LatestMajor"
"DOTNET_ROLL_FORWARD_TO_PRERELEASE", "1" ]
let cfg = config "Debug" envVars
runCliProcess cfg.FSC args