diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerOptions/Fsc/FscCliTests.fs b/tests/FSharp.Compiler.ComponentTests/CompilerOptions/Fsc/FscCliTests.fs
index c5d48ad37f2..8319b875521 100644
--- a/tests/FSharp.Compiler.ComponentTests/CompilerOptions/Fsc/FscCliTests.fs
+++ b/tests/FSharp.Compiler.ComponentTests/CompilerOptions/Fsc/FscCliTests.fs
@@ -34,7 +34,7 @@ module FscCliTests =
/// Original: SOURCE="E_MissingSourceFile02.fs X:\doesnotexist.fs"
/// Expected: //Source file ['"].+['"] could not be found
/// CLI Test: FSC with non-existent absolute path (Windows-style)
- []
+ []
let ``fsc missing source file - absolute Windows path reports FS0225`` () =
let result = runFscProcess ["X:\\doesnotexist.fs"]
Assert.NotEqual(0, result.ExitCode)
@@ -54,7 +54,7 @@ module FscCliTests =
/// Original: SOURCE="E_MissingSourceFile03.fs \\qwerty\y\doesnotexist.fs"
/// Expected: //Source file ['"].+['"] could not be found
/// CLI Test: FSC with non-existent UNC path
- []
+ []
let ``fsc missing source file - UNC path reports FS0225`` () =
let result = runFscProcess ["\\\\qwerty\\y\\doesnotexist.fs"]
Assert.NotEqual(0, result.ExitCode)
diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsi/FsiCliTests.fs b/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsi/FsiCliTests.fs
index 938f093e83b..3bf5b15301b 100644
--- a/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsi/FsiCliTests.fs
+++ b/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsi/FsiCliTests.fs
@@ -2,6 +2,8 @@
namespace CompilerOptions.Fsi
+open System
+open System.IO
open Xunit
open FSharp.Test
open FSharp.Test.Compiler
@@ -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
+ []
+ 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
+ []
+ 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)
+ []
+ 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
+ []
+ 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
+ []
+ 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)
+ []
+ 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
+ []
+ 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
+ []
+ let ``fsi gui - gui minus accepted`` () =
+ Fsx """1+1"""
+ |> withOptions ["--gui-"]
+ |> runFsi
+ |> shouldSucceed
+
+ /// CLI Test: --gui+ is accepted without error
+ []
+ let ``fsi gui - gui plus accepted`` () =
+ Fsx """1+1"""
+ |> withOptions ["--gui+"]
+ |> runFsi
+ |> shouldSucceed
+
+ // ============================================================================
+ // --readline option tests (switch: +/-)
+ // ============================================================================
+
+ /// CLI Test: --readline- is accepted without error
+ []
+ let ``fsi readline - readline minus accepted`` () =
+ Fsx """1+1"""
+ |> withOptions ["--readline-"]
+ |> runFsi
+ |> shouldSucceed
+
+ /// CLI Test: --readline+ is accepted without error
+ []
+ 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
+ []
+ let ``fsi quotations-debug - plus accepted`` () =
+ Fsx """1+1"""
+ |> withOptions ["--quotations-debug+"]
+ |> runFsi
+ |> shouldSucceed
+
+ /// CLI Test: --quotations-debug- is accepted without error
+ []
+ 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
+ []
+ let ``fsi shadowcopyreferences - plus accepted`` () =
+ Fsx """1+1"""
+ |> withOptions ["--shadowcopyreferences+"]
+ |> runFsi
+ |> shouldSucceed
+
+ /// CLI Test: --shadowcopyreferences- is accepted without error
+ []
+ let ``fsi shadowcopyreferences - minus accepted`` () =
+ Fsx """1+1"""
+ |> withOptions ["--shadowcopyreferences-"]
+ |> runFsi
+ |> shouldSucceed
+
+ // ============================================================================
+ // --nologo option tests
+ // ============================================================================
+
+ /// CLI Test: --nologo suppresses the banner
+ []
+ 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
+ []
+ 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
+ []
+ 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
+ []
+ let ``fsi error - invalid warn level produces error`` () =
+ let result = runFsiProcess ["--warn:invalid"; "--exec"]
+ Assert.NotEqual(0, result.ExitCode)
diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj
index f236ca6599d..8283ec2c565 100644
--- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj
+++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj
@@ -320,6 +320,9 @@
+
+
+
diff --git a/tests/FSharp.Test.Utilities/Compiler.fs b/tests/FSharp.Test.Utilities/Compiler.fs
index b93dea8fd1c..62195b943b2 100644
--- a/tests/FSharp.Test.Utilities/Compiler.fs
+++ b/tests/FSharp.Test.Utilities/Compiler.fs
@@ -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