From 77919e52b99c8d033153250829b86287d1634281 Mon Sep 17 00:00:00 2001 From: simontreanor Date: Sun, 27 Sep 2026 21:21:21 +0100 Subject: [PATCH] Tests: compile legacy-framework fixtures against net48 reference assemblies from NuGet parseAndCheckLegacyFramework resolved .NET Framework references with assumeDotNetFramework = true, which finds them in Program Files: only Windows has them. On the Linux CI runner the script got no usable mscorlib (`string` did not resolve), so since requireTypechecks went in, "check-then-add stays where the framework has no Dictionary TryAdd" fails there; before, it passed without testing anything. The test project now takes Microsoft.NETFramework.ReferenceAssemblies.net48 (assets excluded, only its files are read) and passes its folder to the tests as assembly metadata; the helper compiles against mscorlib, System, System.Core, System.Numerics, System.Xml, System.Data, the netstandard and System.Runtime facades and FSharp.Core. The Windows-only guard on the FR0106/FR0166/FR0167 legacy test goes, since it now runs everywhere. Claude-Session: https://claude.ai/code/session_019V58BHhd4xcqmxoYHpKdTk --- .../FSharp.Refactor.Tests.fsproj | 12 ++++ tests/FSharp.Refactor.Tests/Parsing.fs | 53 +++++++++++++--- tests/FSharp.Refactor.Tests/PerfTests.fs | 61 +++++++++---------- 3 files changed, 88 insertions(+), 38 deletions(-) diff --git a/tests/FSharp.Refactor.Tests/FSharp.Refactor.Tests.fsproj b/tests/FSharp.Refactor.Tests/FSharp.Refactor.Tests.fsproj index 126cbab..12bd02c 100644 --- a/tests/FSharp.Refactor.Tests/FSharp.Refactor.Tests.fsproj +++ b/tests/FSharp.Refactor.Tests/FSharp.Refactor.Tests.fsproj @@ -113,6 +113,18 @@ + + + + + + + <_Parameter1>Net48ReferenceAssemblies + <_Parameter2>$(PkgMicrosoft_NETFramework_ReferenceAssemblies_net48)/build/.NETFramework/v4.8 + diff --git a/tests/FSharp.Refactor.Tests/Parsing.fs b/tests/FSharp.Refactor.Tests/Parsing.fs index 9681413..c491df7 100644 --- a/tests/FSharp.Refactor.Tests/Parsing.fs +++ b/tests/FSharp.Refactor.Tests/Parsing.fs @@ -119,14 +119,53 @@ let parseAndCheck (source: string) : ParsedInput * ISourceText * FSharpCheckFile requireTypechecks "parseAndCheck" source checkResults tree, sourceText, checkResults -/// `parseAndCheck` against the LEGACY .NET Framework reference set (the -/// machine's mscorlib), without requiring a clean typecheck. +/// The .NET Framework 4.8 reference assemblies a legacy compilation reads, +/// from the Microsoft.NETFramework.ReferenceAssemblies.net48 package (see the +/// test project). `assumeDotNetFramework = true` alone resolves them from +/// Program Files, which only Windows has: on Linux the script got no usable +/// mscorlib, `string` itself did not resolve, and a legacy test could only +/// pass for the wrong reason. +let private net48References = + lazy + (let directory = + System.Reflection.Assembly + .GetExecutingAssembly() + .GetCustomAttributes(typeof, false) + |> Seq.cast + |> Seq.find (fun a -> a.Key = "Net48ReferenceAssemblies") + |> fun a -> a.Value + + [ + "mscorlib" + "System" + "System.Core" + "System.Numerics" + "System.Xml" + "System.Data" + "Facades/netstandard" + "Facades/System.Runtime" + ] + |> List.map (fun name -> Path.Combine(directory, name + ".dll")) + // FSharp.Core targets netstandard, which the facade above bridges + |> fun framework -> typeof>.Assembly.Location :: framework) + +/// `parseAndCheck` against the LEGACY .NET Framework reference set +/// (net48's mscorlib), without requiring a clean typecheck. let parseAndCheckLegacyFrameworkAllowingErrors (source: string) : ParsedInput * ISourceText * FSharpCheckFileResults = let sourceText = SourceText.ofString source - let options, _ = - checker.GetProjectOptionsFromScript("Legacy.fsx", sourceText, assumeDotNetFramework = true) - |> Async.RunSynchronously + let options = + let scriptOptions, _ = + checker.GetProjectOptionsFromScript("Legacy.fsx", sourceText, assumeDotNetFramework = true) + |> Async.RunSynchronously + + { scriptOptions with + OtherOptions = + [| + yield! scriptOptions.OtherOptions |> Array.filter (fun o -> not (o.StartsWith "-r:")) + for reference in net48References.Value -> "-r:" + reference + |] + } let parseResults, answer = checker.ParseAndCheckFileInProject("Legacy.fsx", source.GetHashCode(), sourceText, options) @@ -137,8 +176,8 @@ let parseAndCheckLegacyFrameworkAllowingErrors (source: string) : ParsedInput * | FSharpCheckFileAnswer.Aborted -> failwith $"Typechecking was aborted, calling parseAndCheckLegacyFramework with source: {source}" -/// `parseAndCheck` against the LEGACY .NET Framework reference set (the -/// machine's mscorlib): the compilation a netstandard2.0/net4x project +/// `parseAndCheck` against the LEGACY .NET Framework reference set +/// (net48's mscorlib): the compilation a netstandard2.0/net4x project /// sees, where the char and span overloads of String do not exist. The /// rules that gate on a modern framework must stay quiet here. Fails the /// test when the input has a type error there. diff --git a/tests/FSharp.Refactor.Tests/PerfTests.fs b/tests/FSharp.Refactor.Tests/PerfTests.fs index 760716a..7525016 100644 --- a/tests/FSharp.Refactor.Tests/PerfTests.fs +++ b/tests/FSharp.Refactor.Tests/PerfTests.fs @@ -901,38 +901,37 @@ let ``FR0106, FR0166 and FR0167 stay quiet in a legacy .NET Framework compilatio // the same shapes fire against modern .NET above; against mscorlib the // char and span overloads of String are absent, so nothing is proven // and nothing fires — this is how netstandard2.0/net4x stay untouched. - // Only Windows ships the .NET Framework reference assemblies a legacy - // script compilation resolves; elsewhere the fixture cannot typecheck - if OperatingSystem.IsWindows() then - let source = - fsharp - """ - module Test - open System - open System.Text - let f (s: string) (sb: StringBuilder) = - sb.Append(s.Substring(6, 5)) |> ignore - let a = s.[..5] = "ORDER-" - let b = s.Length >= 6 && s.Substring(0, 6) = "ORDER-" - let mutable n = 0 - for c in s.ToCharArray() do n <- n + int c - let d = Array.exists Char.IsDigit (s.ToCharArray()) - Int32.Parse(s.Substring(0, 2)), a, b, n, d - """ - - let tree, sourceText, check = - FSharp.Refactor.Tests.Parsing.parseAndCheckLegacyFramework source - - Assert.False(FSharp.Refactor.OptionModule.hasErrors check, "the legacy fixture itself must typecheck") - Assert.Empty(FSharp.Refactor.SubstringSpan.find tree sourceText check) - Assert.Empty(FSharp.Refactor.PrefixCompare.find true tree sourceText check) - Assert.Empty(FSharp.Refactor.CharArrayCopy.find tree sourceText check) + // The legacy compilation reads the net48 reference assemblies from a NuGet + // package (Parsing.fs), so this runs on every OS, not only Windows + let source = + fsharp + """ + module Test + open System + open System.Text + let f (s: string) (sb: StringBuilder) = + sb.Append(s.Substring(6, 5)) |> ignore + let a = s.[..5] = "ORDER-" + let b = s.Length >= 6 && s.Substring(0, 6) = "ORDER-" + let mutable n = 0 + for c in s.ToCharArray() do n <- n + int c + let d = Array.exists Char.IsDigit (s.ToCharArray()) + Int32.Parse(s.Substring(0, 2)), a, b, n, d + """ - // and the modern compilation of the very same text fires all three - let tree, sourceText, check = FSharp.Refactor.Tests.Parsing.parseAndCheck source - Assert.Equal(2, (FSharp.Refactor.SubstringSpan.find tree sourceText check).Length) - Assert.Equal(2, (FSharp.Refactor.PrefixCompare.find true tree sourceText check).Length) - Assert.Equal(2, (FSharp.Refactor.CharArrayCopy.find tree sourceText check).Length) + let tree, sourceText, check = + FSharp.Refactor.Tests.Parsing.parseAndCheckLegacyFramework source + + Assert.False(FSharp.Refactor.OptionModule.hasErrors check, "the legacy fixture itself must typecheck") + Assert.Empty(FSharp.Refactor.SubstringSpan.find tree sourceText check) + Assert.Empty(FSharp.Refactor.PrefixCompare.find true tree sourceText check) + Assert.Empty(FSharp.Refactor.CharArrayCopy.find tree sourceText check) + + // and the modern compilation of the very same text fires all three + let tree, sourceText, check = FSharp.Refactor.Tests.Parsing.parseAndCheck source + Assert.Equal(2, (FSharp.Refactor.SubstringSpan.find tree sourceText check).Length) + Assert.Equal(2, (FSharp.Refactor.PrefixCompare.find true tree sourceText check).Length) + Assert.Equal(2, (FSharp.Refactor.CharArrayCopy.find tree sourceText check).Length) // ---- CapabilityFix dual-framework emission ----