Skip to content

Commit dc1a4fe

Browse files
committed
Add FSI options tests and wire up CLI test infrastructure
Add runFsiProcess/runFscProcess subprocess helpers to Compiler.fs and include the existing CLI test files (FsiCliTests.fs, FscCliTests.fs, CliProcessTests.fs) in the project build. Add 19 new tests covering --quiet, --exec, --use, --load, --gui, --readline, --quotations-debug, --shadowcopyreferences, --nologo, and error cases for unknown options and invalid --warn levels. Contributes to #13878
1 parent cb4bfe4 commit dc1a4fe

4 files changed

Lines changed: 234 additions & 2 deletions

File tree

tests/FSharp.Compiler.ComponentTests/CompilerOptions/Fsc/FscCliTests.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ module FscCliTests =
3434
/// Original: SOURCE="E_MissingSourceFile02.fs X:\doesnotexist.fs"
3535
/// Expected: //<Expects id="FS0225" status="error">Source file ['"].+['"] could not be found</Expects>
3636
/// CLI Test: FSC with non-existent absolute path (Windows-style)
37-
[<FactForWINDOWS>]
37+
[<FactForDESKTOP>]
3838
let ``fsc missing source file - absolute Windows path reports FS0225`` () =
3939
let result = runFscProcess ["X:\\doesnotexist.fs"]
4040
Assert.NotEqual(0, result.ExitCode)
@@ -54,7 +54,7 @@ module FscCliTests =
5454
/// Original: SOURCE="E_MissingSourceFile03.fs \\qwerty\y\doesnotexist.fs"
5555
/// Expected: //<Expects id="FS0225" status="error">Source file ['"].+['"] could not be found</Expects>
5656
/// CLI Test: FSC with non-existent UNC path
57-
[<FactForWINDOWS>]
57+
[<FactForDESKTOP>]
5858
let ``fsc missing source file - UNC path reports FS0225`` () =
5959
let result = runFscProcess ["\\\\qwerty\\y\\doesnotexist.fs"]
6060
Assert.NotEqual(0, result.ExitCode)

tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsi/FsiCliTests.fs

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

33
namespace CompilerOptions.Fsi
44

5+
open System
6+
open System.IO
57
open Xunit
68
open FSharp.Test
79
open FSharp.Test.Compiler
@@ -110,3 +112,197 @@ module FsiCliTests =
110112
// FSI should report error for unrecognized option
111113
Assert.NotEqual(0, result.ExitCode)
112114
Assert.Contains("Unrecognized option: '--subsystemversion'", result.StdErr)
115+
116+
// ============================================================================
117+
// --quiet option tests
118+
// ============================================================================
119+
120+
/// CLI Test: --quiet suppresses the banner
121+
[<Fact>]
122+
let ``fsi quiet - suppresses banner`` () =
123+
let result = runFsiProcess ["--quiet"; "--exec"; "--nologo"]
124+
Assert.Equal(0, result.ExitCode)
125+
Assert.DoesNotContain("Microsoft (R) F# Interactive", result.StdOut)
126+
127+
/// In-process test: --quiet suppresses feedback output but expressions still evaluate
128+
[<Fact>]
129+
let ``fsi quiet - expressions evaluate correctly`` () =
130+
Fsx """let x = 1 + 1"""
131+
|> withOptions ["--quiet"]
132+
|> runFsi
133+
|> shouldSucceed
134+
135+
// ============================================================================
136+
// --exec option tests
137+
// ============================================================================
138+
139+
/// CLI Test: --exec causes FSI to exit after evaluating (no interactive prompt)
140+
[<Fact>]
141+
let ``fsi exec - exits after evaluating script`` () =
142+
let tmpFile = Path.Combine(Path.GetTempPath(), $"fsi_exec_test_{Guid.NewGuid()}.fsx")
143+
try
144+
File.WriteAllText(tmpFile, "printfn \"hello from exec\"")
145+
let result = runFsiProcess ["--exec"; "--nologo"; tmpFile]
146+
Assert.Equal(0, result.ExitCode)
147+
Assert.Contains("hello from exec", result.StdOut)
148+
finally
149+
try File.Delete(tmpFile) with _ -> ()
150+
151+
// ============================================================================
152+
// --use option tests
153+
// ============================================================================
154+
155+
/// CLI Test: --use:file.fsx loads and executes a script file
156+
[<Fact>]
157+
let ``fsi use - loads and executes script file`` () =
158+
let tmpFile = Path.Combine(Path.GetTempPath(), $"fsi_use_test_{Guid.NewGuid()}.fsx")
159+
try
160+
File.WriteAllText(tmpFile, "printfn \"loaded via use\"")
161+
let result = runFsiProcess ["--nologo"; "--exec"; $"--use:{tmpFile}"]
162+
Assert.Equal(0, result.ExitCode)
163+
Assert.Contains("loaded via use", result.StdOut)
164+
finally
165+
try File.Delete(tmpFile) with _ -> ()
166+
167+
/// CLI Test: --use with nonexistent file produces error
168+
[<Fact>]
169+
let ``fsi use - nonexistent file produces error`` () =
170+
let result = runFsiProcess ["--exec"; "--use:nonexistent_file_xyz.fsx"]
171+
Assert.NotEqual(0, result.ExitCode)
172+
173+
// ============================================================================
174+
// --load option tests
175+
// ============================================================================
176+
177+
/// CLI Test: --load:file.fsx loads a file (definitions available)
178+
[<Fact>]
179+
let ``fsi load - loads file definitions`` () =
180+
let tmpFile = Path.Combine(Path.GetTempPath(), $"fsi_load_test_{Guid.NewGuid()}.fsx")
181+
try
182+
File.WriteAllText(tmpFile, "let loadedValue = 42")
183+
let result = runFsiProcess ["--nologo"; "--exec"; $"--load:{tmpFile}"]
184+
Assert.Equal(0, result.ExitCode)
185+
finally
186+
try File.Delete(tmpFile) with _ -> ()
187+
188+
/// CLI Test: --load with nonexistent file produces error
189+
[<Fact>]
190+
let ``fsi load - nonexistent file produces error`` () =
191+
let result = runFsiProcess ["--exec"; "--load:nonexistent_file_xyz.fsx"]
192+
Assert.NotEqual(0, result.ExitCode)
193+
194+
// ============================================================================
195+
// --gui option tests (switch: +/-)
196+
// ============================================================================
197+
198+
/// CLI Test: --gui- is accepted without error
199+
[<Fact>]
200+
let ``fsi gui - gui minus accepted`` () =
201+
Fsx """1+1"""
202+
|> withOptions ["--gui-"]
203+
|> runFsi
204+
|> shouldSucceed
205+
206+
/// CLI Test: --gui+ is accepted without error
207+
[<Fact>]
208+
let ``fsi gui - gui plus accepted`` () =
209+
Fsx """1+1"""
210+
|> withOptions ["--gui+"]
211+
|> runFsi
212+
|> shouldSucceed
213+
214+
// ============================================================================
215+
// --readline option tests (switch: +/-)
216+
// ============================================================================
217+
218+
/// CLI Test: --readline- is accepted without error
219+
[<Fact>]
220+
let ``fsi readline - readline minus accepted`` () =
221+
Fsx """1+1"""
222+
|> withOptions ["--readline-"]
223+
|> runFsi
224+
|> shouldSucceed
225+
226+
/// CLI Test: --readline+ is accepted without error
227+
[<Fact>]
228+
let ``fsi readline - readline plus accepted`` () =
229+
Fsx """1+1"""
230+
|> withOptions ["--readline+"]
231+
|> runFsi
232+
|> shouldSucceed
233+
234+
// ============================================================================
235+
// --quotations-debug option tests (switch: +/-)
236+
// ============================================================================
237+
238+
/// CLI Test: --quotations-debug+ is accepted without error
239+
[<Fact>]
240+
let ``fsi quotations-debug - plus accepted`` () =
241+
Fsx """1+1"""
242+
|> withOptions ["--quotations-debug+"]
243+
|> runFsi
244+
|> shouldSucceed
245+
246+
/// CLI Test: --quotations-debug- is accepted without error
247+
[<Fact>]
248+
let ``fsi quotations-debug - minus accepted`` () =
249+
Fsx """1+1"""
250+
|> withOptions ["--quotations-debug-"]
251+
|> runFsi
252+
|> shouldSucceed
253+
254+
// ============================================================================
255+
// --shadowcopyreferences option tests (switch: +/-)
256+
// ============================================================================
257+
258+
/// CLI Test: --shadowcopyreferences+ is accepted without error
259+
[<Fact>]
260+
let ``fsi shadowcopyreferences - plus accepted`` () =
261+
Fsx """1+1"""
262+
|> withOptions ["--shadowcopyreferences+"]
263+
|> runFsi
264+
|> shouldSucceed
265+
266+
/// CLI Test: --shadowcopyreferences- is accepted without error
267+
[<Fact>]
268+
let ``fsi shadowcopyreferences - minus accepted`` () =
269+
Fsx """1+1"""
270+
|> withOptions ["--shadowcopyreferences-"]
271+
|> runFsi
272+
|> shouldSucceed
273+
274+
// ============================================================================
275+
// --nologo option tests
276+
// ============================================================================
277+
278+
/// CLI Test: --nologo suppresses the banner
279+
[<Fact>]
280+
let ``fsi nologo - suppresses banner in subprocess`` () =
281+
let result = runFsiProcess ["--nologo"; "--exec"]
282+
Assert.Equal(0, result.ExitCode)
283+
Assert.DoesNotContain("Microsoft (R) F# Interactive", result.StdOut)
284+
285+
/// In-process test: FSI without --nologo shows the banner
286+
[<Fact>]
287+
let ``fsi nologo - without nologo shows banner`` () =
288+
Fsx """1+1"""
289+
|> runFsi
290+
|> shouldSucceed
291+
|> withStdOutContains "Microsoft"
292+
293+
// ============================================================================
294+
// Additional error case tests
295+
// ============================================================================
296+
297+
/// CLI Test: completely unknown option produces FS0243
298+
[<Fact>]
299+
let ``fsi error - unknown option produces FS0243`` () =
300+
let result = runFsiProcess ["--not-a-real-option"]
301+
Assert.NotEqual(0, result.ExitCode)
302+
Assert.Contains("Unrecognized option: '--not-a-real-option'", result.StdErr)
303+
304+
/// CLI Test: --warn with invalid level produces error
305+
[<Fact>]
306+
let ``fsi error - invalid warn level produces error`` () =
307+
let result = runFsiProcess ["--warn:invalid"; "--exec"]
308+
Assert.NotEqual(0, result.ExitCode)

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,9 @@
320320
<Compile Include="CompilerOptions\fsc\reflectionfree.fs" />
321321
<Compile Include="CompilerOptions\fsc\refonlyrefout.fs" />
322322
<Compile Include="CompilerOptions\fsc\sourceFiles.fs" />
323+
<Compile Include="CompilerOptions\fsc\FscCliTests.fs" />
324+
<Compile Include="CompilerOptions\fsi\FsiCliTests.fs" />
325+
<Compile Include="CompilerOptions\CliProcessTests.fs" />
323326
<Compile Include="CompilerService\RangeModule.fs" />
324327
<Compile Include="CompilerService\Caches.fs" />
325328
<Compile Include="CompilerService\LruCache.fs" />

tests/FSharp.Test.Utilities/Compiler.fs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2074,3 +2074,36 @@ Actual:
20742074
match hash with
20752075
| Some h -> h
20762076
| None -> failwith "Implied signature hash returned 'None' which should not happen"
2077+
2078+
// ========================================================================
2079+
// CLI subprocess helpers for testing options that cause FSI/FSC to exit
2080+
// (e.g. --help, unrecognized options, missing files).
2081+
// These cannot be tested in-process because the process exits before
2082+
// a session is created.
2083+
// ========================================================================
2084+
2085+
type CliProcessResult =
2086+
{ ExitCode: int
2087+
StdOut: string
2088+
StdErr: string }
2089+
2090+
let private runCliProcess (toolDll: string) (args: string list) : CliProcessResult =
2091+
let envVars = Map.ofList [ "DOTNET_ROLL_FORWARD", "LatestMajor"
2092+
"DOTNET_ROLL_FORWARD_TO_PRERELEASE", "1" ]
2093+
let cfg = config "Debug" envVars
2094+
let dotnet = cfg.DotNetExe
2095+
let allArgs = toolDll :: args |> String.concat " "
2096+
let exitCode, stdout, stderr = Commands.executeProcess dotnet allArgs (Directory.GetCurrentDirectory())
2097+
{ ExitCode = exitCode; StdOut = stdout; StdErr = stderr }
2098+
2099+
let runFsiProcess (args: string list) : CliProcessResult =
2100+
let envVars = Map.ofList [ "DOTNET_ROLL_FORWARD", "LatestMajor"
2101+
"DOTNET_ROLL_FORWARD_TO_PRERELEASE", "1" ]
2102+
let cfg = config "Debug" envVars
2103+
runCliProcess cfg.FSI args
2104+
2105+
let runFscProcess (args: string list) : CliProcessResult =
2106+
let envVars = Map.ofList [ "DOTNET_ROLL_FORWARD", "LatestMajor"
2107+
"DOTNET_ROLL_FORWARD_TO_PRERELEASE", "1" ]
2108+
let cfg = config "Debug" envVars
2109+
runCliProcess cfg.FSC args

0 commit comments

Comments
 (0)