diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs b/tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs new file mode 100644 index 0000000000..2c33904451 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs @@ -0,0 +1,125 @@ +module FSharp.Compiler.ComponentTests.CompilerCompatibilityTests + +open System +open System.IO +open Xunit +open FSharp.Test +open FSharp.Test.Assert +open TestFramework + +type CompilerCompatibilityTests() = + + let projectsPath = Path.GetFullPath(Path.Combine(__SOURCE_DIRECTORY__, "../projects/CompilerCompat")) + let libProjectPath = Path.Combine(projectsPath, "CompilerCompatLib") + let appProjectPath = Path.Combine(projectsPath, "CompilerCompatApp") + + let runDotnetBuild projectPath compilerVersion = + let args = + match compilerVersion with + | "local" -> "build -c Release -p:LoadLocalFSharpBuild=True" + | _ -> "build -c Release" + + let (exitCode, output, error) = Commands.executeProcess "dotnet" args projectPath + + if exitCode <> 0 then + let outputStr = String.concat "\n" (List.ofArray output) + let errorStr = String.concat "\n" (List.ofArray error) + failwith $"Build failed with exit code {exitCode}. Output: {outputStr}. Error: {errorStr}" + + String.concat "\n" (List.ofArray output) + + let runApp appBinaryPath = + let (exitCode, output, error) = Commands.executeProcess "dotnet" appBinaryPath (Path.GetDirectoryName(appBinaryPath)) + (exitCode, String.concat "\n" (List.ofArray output), String.concat "\n" (List.ofArray error)) + + let cleanBinObjDirectories projectPath = + let binPath = Path.Combine(projectPath, "bin") + let objPath = Path.Combine(projectPath, "obj") + + if Directory.Exists(binPath) then + Directory.Delete(binPath, true) + if Directory.Exists(objPath) then + Directory.Delete(objPath, true) + + let getAppDllPath () = + // The app is built to artifacts directory due to Directory.Build.props + Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "artifacts", "bin", "CompilerCompatApp", "Release", "net8.0", "CompilerCompatApp.dll") + + [] + [] + [] + [] + member _.``Compiler compatibility test``(libCompilerVersion: string, appCompilerVersion: string, scenarioDescription: string) = + // Clean previous builds + cleanBinObjDirectories libProjectPath + cleanBinObjDirectories appProjectPath + + // Build library with specified compiler version + let libOutput = runDotnetBuild libProjectPath libCompilerVersion + Assert.Contains("CompilerCompatLib -> ", libOutput) + + // Build app with specified compiler version + let appOutput = runDotnetBuild appProjectPath appCompilerVersion + Assert.Contains("CompilerCompatApp -> ", appOutput) + + // Run app and verify it works + let appDllPath = getAppDllPath() + Assert.True(File.Exists(appDllPath), $"App DLL not found at {appDllPath} for scenario: {scenarioDescription}") + + let (exitCode, output, _error) = runApp appDllPath + Assert.Equal(0, exitCode) + Assert.Contains("SUCCESS: All compiler compatibility tests passed", output) + + // Parse build info from output to validate compiler usage consistency + let lines = output.Split('\n') |> Array.map (fun s -> s.Trim()) + + // Extract isLocalBuild values from the output + let parseIsLocalBuild (prefix: string) = + lines + |> Array.tryFindIndex (fun l -> l.StartsWith(prefix)) + |> Option.bind (fun startIdx -> + lines + |> Array.skip (startIdx + 1) + |> Array.tryFind (fun l -> l.Contains("Is Local Build: ")) + |> Option.map (fun l -> l.Contains("Is Local Build: true"))) + |> function Some x -> x | None -> false + + let libIsLocalBuild = parseIsLocalBuild "Library Build Info:" + let appIsLocalBuild = parseIsLocalBuild "Application Build Info:" + + // Validate that build info matches expected compiler versions + let expectedLibIsLocal = libCompilerVersion = "local" + let expectedAppIsLocal = appCompilerVersion = "local" + + Assert.True((libIsLocalBuild = expectedLibIsLocal), + $"Library build info mismatch: expected isLocalBuild={expectedLibIsLocal} for version '{libCompilerVersion}', but got {libIsLocalBuild}") + Assert.True((appIsLocalBuild = expectedAppIsLocal), + $"Application build info mismatch: expected isLocalBuild={expectedAppIsLocal} for version '{appCompilerVersion}', but got {appIsLocalBuild}") + + // Validate consistency: same compiler versions should have same build info + if libCompilerVersion = appCompilerVersion then + Assert.True((libIsLocalBuild = appIsLocalBuild), + $"Inconsistent build info: both lib and app use '{libCompilerVersion}' but have different isLocalBuild values (lib={libIsLocalBuild}, app={appIsLocalBuild})") + else + Assert.True((libIsLocalBuild <> appIsLocalBuild), + $"Expected different build info for different compiler versions (lib='{libCompilerVersion}', app='{appCompilerVersion}'), but both have isLocalBuild={libIsLocalBuild}") + + // Additional validation: check that we have actual build-time values + Assert.True((lines |> Array.exists (fun l -> l.Contains("SDK Version:") && not (l.Contains("Unknown")))), + "SDK Version should be captured from build-time, not show 'Unknown'") + Assert.True((lines |> Array.exists (fun l -> l.Contains("F# Compiler Path:") && not (l.Contains("Unknown")))), + "F# Compiler Path should be captured from build-time, not show 'Unknown'") + + // Validate that local builds have artifacts path and non-local builds don't + if expectedLibIsLocal then + Assert.True((lines |> Array.exists (fun l -> l.Contains("Library") && l.Contains("artifacts"))), + "Local library build should reference artifacts path") + if expectedAppIsLocal then + Assert.True((lines |> Array.exists (fun l -> l.Contains("Application") && l.Contains("artifacts"))), + "Local app build should reference artifacts path") + + // Ensure build verification section is present + Assert.True((output.Contains("=== BUILD VERIFICATION ===")), + "Build verification section should be present in output") + Assert.True((output.Contains("==========================")), + "Build verification section should be properly formatted") \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 960057baf9..f91e6d6088 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -352,6 +352,7 @@ + diff --git a/tests/projects/CompilerCompat/.gitignore b/tests/projects/CompilerCompat/.gitignore new file mode 100644 index 0000000000..658db31ab7 --- /dev/null +++ b/tests/projects/CompilerCompat/.gitignore @@ -0,0 +1,6 @@ +# Ignore test-generated files +global.json +commandline.txt +**/*BuildInfo.fs +StandardOutput.txt +StandardError.txt \ No newline at end of file diff --git a/tests/projects/CompilerCompat/CompilerCompatApp/CompilerCompatApp.fsproj b/tests/projects/CompilerCompat/CompilerCompatApp/CompilerCompatApp.fsproj new file mode 100644 index 0000000000..9b7ed2aaf3 --- /dev/null +++ b/tests/projects/CompilerCompat/CompilerCompatApp/CompilerCompatApp.fsproj @@ -0,0 +1,37 @@ + + + + Exe + net8.0 + ../../../../UseLocalCompiler.Directory.Build.props + + + + + + + + + + + + + + + true + false + $(DotnetFscCompilerPath.Replace('"', '')) + N/A + + + + + \ No newline at end of file diff --git a/tests/projects/CompilerCompat/CompilerCompatApp/Program.fs b/tests/projects/CompilerCompat/CompilerCompatApp/Program.fs new file mode 100644 index 0000000000..67af3a5b5e --- /dev/null +++ b/tests/projects/CompilerCompat/CompilerCompatApp/Program.fs @@ -0,0 +1,52 @@ +open CompilerCompatLib +open CompilerCompatApp +open System + +[] +let main _argv = + try + // Print detailed build information to verify which compiler was used + printfn "=== BUILD VERIFICATION ===" + printfn "Library Build Info:" + printfn " SDK Version: %s" LibBuildInfo.sdkVersion + printfn " F# Compiler Path: %s" LibBuildInfo.fsharpCompilerPath + printfn " .NET FSC Compiler Path: %s" LibBuildInfo.dotnetFscCompilerPath + printfn " Is Local Build: %b" LibBuildInfo.isLocalBuild + printfn "Application Build Info:" + printfn " SDK Version: %s" AppBuildInfo.sdkVersion + printfn " F# Compiler Path: %s" AppBuildInfo.fsharpCompilerPath + printfn " .NET FSC Compiler Path: %s" AppBuildInfo.dotnetFscCompilerPath + printfn " Is Local Build: %b" AppBuildInfo.isLocalBuild + printfn "==========================" + + // Test basic anonymous record functionality + let record = Library.getAnonymousRecord() + printfn "Basic record: X=%d, Y=%s" record.X record.Y + + // Verify expected values + if record.X <> 42 || record.Y <> "hello" then + printfn "ERROR: Basic record values don't match expected" + 1 + else + printfn "SUCCESS: Basic record test passed" + + // Test complex anonymous record functionality + let complex = Library.getComplexAnonymousRecord() + printfn "Complex record: Simple.A=%d, Simple.B=%s" complex.Simple.A complex.Simple.B + printfn "Complex record: List has %d items" complex.List.Length + printfn "Complex record: Tuple=(%d, Value=%f)" (fst complex.Tuple) (snd complex.Tuple).Value + + // Test function that takes anonymous record + let processed = Library.processAnonymousRecord({| X = 123; Y = "test" |}) + printfn "Processed result: %s" processed + + if processed = "Processed: X=123, Y=test" then + printfn "SUCCESS: All compiler compatibility tests passed" + 0 + else + printfn "ERROR: Processed result doesn't match expected" + 1 + + with ex -> + printfn "ERROR: Exception occurred: %s" ex.Message + 1 \ No newline at end of file diff --git a/tests/projects/CompilerCompat/CompilerCompatLib/CompilerCompatLib.fsproj b/tests/projects/CompilerCompat/CompilerCompatLib/CompilerCompatLib.fsproj new file mode 100644 index 0000000000..d77753838a --- /dev/null +++ b/tests/projects/CompilerCompat/CompilerCompatLib/CompilerCompatLib.fsproj @@ -0,0 +1,33 @@ + + + + net8.0 + true + ../../../../UseLocalCompiler.Directory.Build.props + + + + + + + + + + + true + false + $(DotnetFscCompilerPath.Replace('"', '')) + N/A + + + + + \ No newline at end of file diff --git a/tests/projects/CompilerCompat/CompilerCompatLib/Library.fs b/tests/projects/CompilerCompat/CompilerCompatLib/Library.fs new file mode 100644 index 0000000000..e0b4f38080 --- /dev/null +++ b/tests/projects/CompilerCompat/CompilerCompatLib/Library.fs @@ -0,0 +1,17 @@ +namespace CompilerCompatLib + +module Library = + /// Returns an anonymous record to test compiler compatibility + let getAnonymousRecord () = {| X = 42; Y = "hello" |} + + /// Returns a more complex anonymous record with nested structure + let getComplexAnonymousRecord () = + {| + Simple = {| A = 1; B = "test" |}; + List = [ {| Id = 1; Name = "first" |}; {| Id = 2; Name = "second" |} ]; + Tuple = (42, {| Value = 3.14; Label = "pi" |}) + |} + + /// Function that takes an anonymous record as parameter + let processAnonymousRecord (record: {| X: int; Y: string |}) = + sprintf "Processed: X=%d, Y=%s" record.X record.Y \ No newline at end of file