Skip to content
Merged
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 @@ -85,23 +85,43 @@ module FsiCliTests =
finally
try System.IO.File.Delete(scriptPath) with _ -> ()

// The FSI #r "nuget:" restore below must request a package version that is guaranteed to be in
// the offline restore cache on the internal signed build (which cannot restore online). Central
// package management + transitive pinning means only the centrally-pinned version (eng/Packages.props)
// is ever restored into that cache, and it changes whenever the pin is bumped. Rather than hardcode
// a version that would silently drift, read the exact pinned version baked into this test assembly
// at build time via AssemblyMetadata (see FSharp.Compiler.ComponentTests.fsproj). The package id
// (MessagePack) is kept in sync with that project; any centrally-pinned standalone package would do.
// On failure, surface the FSI subprocess output so CI logs show what actually happened (e.g. a
// NuGet restore error) instead of a bare "Expected 0, Actual 1". xunit's Assert.Equal/Contains do
// not include the process stdout/stderr, so these wrappers append it to the failure message.
let private fsiDiagnostics (result: ProcessResult) =
$"FSI exit code: %d{result.ExitCode}\n--- FSI STDOUT ---\n%s{result.StdOut}\n--- FSI STDERR ---\n%s{result.StdErr}\n--- end FSI output ---"

let private assertFsiExitCode (expected: int) (result: ProcessResult) =
if result.ExitCode <> expected then
Assert.Fail($"Expected FSI exit code %d{expected} but got %d{result.ExitCode}.\n%s{fsiDiagnostics result}")

let private assertStdOutContains (expected: string) (result: ProcessResult) =
if not (result.StdOut.Contains(expected)) then
Assert.Fail($"Expected FSI stdout to contain '%s{expected}'.\n%s{fsiDiagnostics result}")

let private assertStdOutDoesNotContain (unexpected: string) (result: ProcessResult) =
if result.StdOut.Contains(unexpected) then
Assert.Fail($"Expected FSI stdout NOT to contain '%s{unexpected}'.\n%s{fsiDiagnostics result}")

// The FSI #r "nuget:" restore below must request a package (and closure) already in the offline
// restore cache on the internal signed build (which cannot restore online), and it must be a genuine
// third-party assembly (not in the shared framework) so that on .NET Core it resolves to a restored
// package rather than the framework (which would emit NU1510 and skip real nuget resolution). FsCheck
// fits: a real third-party library whose only dependency (FSharp.Core) is always cached and filtered
// from fsx resolution, centrally pinned (eng/Packages.props) and restored by FSharp.Core.UnitTests, so
// it restores offline-clean on both net472 and .NET Core. Read the exact pinned version baked into this
// test assembly via AssemblyMetadata (see FSharp.Compiler.ComponentTests.fsproj) so the request never
// drifts from the pin; keep the package id below in sync with that project.
[<Literal>]
let private restoreTestPackageId = "MessagePack"
let private restoreTestPackageId = "FsCheck"

let private restoreTestPackageVersion =
System.Reflection.Assembly.GetExecutingAssembly().GetCustomAttributes(typeof<System.Reflection.AssemblyMetadataAttribute>, false)
|> Array.tryPick (fun a ->
let m = a :?> System.Reflection.AssemblyMetadataAttribute
if m.Key = "FsiRestoreTestPackageVersion" && not (System.String.IsNullOrWhiteSpace m.Value) then Some m.Value else None)
|> Option.defaultWith (fun () ->
failwith "AssemblyMetadata 'FsiRestoreTestPackageVersion' is missing. It should be emitted by FSharp.Compiler.ComponentTests.fsproj from the central MessagePack PackageVersion.")
failwith "AssemblyMetadata 'FsiRestoreTestPackageVersion' is missing. It should be emitted by FSharp.Compiler.ComponentTests.fsproj from the central FsCheck PackageVersion.")

[<Fact>]
let ``FSI quiet mode suppresses NuGet restore output from stdout`` () =
Expand All @@ -110,11 +130,11 @@ module FsiCliTests =
printfn "RESULT_MARKER_18086"
"""
let result = runFsiScript ["--quiet"] script
Assert.Equal(0, result.ExitCode)
Assert.Contains("RESULT_MARKER_18086", result.StdOut)
Assert.DoesNotContain("Determining projects to restore", result.StdOut)
Assert.DoesNotContain("Restored ", result.StdOut)
Assert.DoesNotContain("NU1", result.StdOut)
assertFsiExitCode 0 result
assertStdOutContains "RESULT_MARKER_18086" result
assertStdOutDoesNotContain "Determining projects to restore" result
assertStdOutDoesNotContain "Restored " result
assertStdOutDoesNotContain "NU1" result

[<Fact>]
let ``FSI default (non-quiet) mode still evaluates script and prints user output`` () =
Expand All @@ -123,12 +143,12 @@ printfn "RESULT_MARKER_18086"
printfn "RESULT_MARKER_18086_DEFAULT"
"""
let result = runFsiScript [] script
Assert.Equal(0, result.ExitCode)
Assert.Contains("RESULT_MARKER_18086_DEFAULT", result.StdOut)
assertFsiExitCode 0 result
assertStdOutContains "RESULT_MARKER_18086_DEFAULT" result

[<Fact>]
let ``FSI quiet mode still prints user printfn output to stdout`` () =
let script = """printfn "hello from quiet script" """
let result = runFsiScript ["--quiet"] script
Assert.Equal(0, result.ExitCode)
Assert.Contains("hello from quiet script", result.StdOut)
assertFsiExitCode 0 result
assertStdOutContains "hello from quiet script" result
Original file line number Diff line number Diff line change
Expand Up @@ -564,15 +564,17 @@

<!-- FsiCliTests spawns FSI and does `#r "nuget: <pkg>, <version>"` to verify that quiet mode
suppresses NuGet restore chatter. On the internal signed build NuGet restore is offline, so the
requested version must be one that is already in the restore cache. Under central package
management with transitive pinning, that is exactly the centrally-pinned version
(eng/Packages.props). Bake that version into the test assembly so the test can request it at
runtime with no manual sync when the central pin is bumped. MessagePack is used (rather than a
general-purpose JSON package) because it is an actively-maintained, non-system standalone library
that is centrally pinned and restored transitively by the product (StreamJsonRpc); keep the
package id here in sync with the id in FsiCliTests.fs. -->
requested package AND ITS ENTIRE TRANSITIVE CLOSURE must already be in the restore cache. The
package must also be a genuine third-party assembly (NOT in the shared framework), otherwise on
.NET Core it resolves to the framework (NU1510) instead of a restored package and the test no
longer exercises real nuget resolution. FsCheck fits: a real third-party library whose only
dependency is FSharp.Core (always cached, and filtered from fsx resolution), centrally pinned
(eng/Packages.props) and restored by FSharp.Core.UnitTests, so its closure is cached and it
restores offline-clean on both net472 and .NET Core. Bake the centrally-pinned version into the
test assembly so the test can request exactly the cached version with no manual sync when the pin
is bumped; keep the package id here in sync with the id in FsiCliTests.fs. -->
<PropertyGroup>
<FsiRestoreTestPackageVersion>@(PackageVersion->WithMetadataValue('Identity','MessagePack')->'%(Version)')</FsiRestoreTestPackageVersion>
<FsiRestoreTestPackageVersion>@(PackageVersion->WithMetadataValue('Identity','FsCheck')->'%(Version)')</FsiRestoreTestPackageVersion>
</PropertyGroup>
<ItemGroup>
<AssemblyMetadata Include="FsiRestoreTestPackageVersion" Value="$(FsiRestoreTestPackageVersion)" />
Expand Down
Loading