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