-
Notifications
You must be signed in to change notification settings - Fork 833
Add compiler compatibility test suite for anonymous records with MSBuild-generated build verification #18913
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c628564
e8d910f
bbefae5
13ca28d
c5a2700
360ae63
9ea30da
86a9207
2720a6f
91da72a
9846cef
194b5fe
8fc9d44
f3cd733
8294e80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
Check failure on line 25 in tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs
|
||
let errorStr = String.concat "\n" (List.ofArray error) | ||
Check failure on line 26 in tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs
|
||
failwith $"Build failed with exit code {exitCode}. Output: {outputStr}. Error: {errorStr}" | ||
|
||
String.concat "\n" (List.ofArray output) | ||
Check failure on line 29 in tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs
|
||
|
||
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)) | ||
Check failure on line 33 in tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs
|
||
|
||
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") | ||
|
||
[<Theory>] | ||
[<InlineData("local", "local", "Baseline scenario - Both library and app built with local compiler")>] | ||
[<InlineData("latest", "local", "Forward compatibility - Library built with latest SDK, app with local compiler")>] | ||
[<InlineData("local", "latest", "Backward compatibility - Library built with local compiler, app with latest SDK")>] | ||
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 | ||
T-Gro marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# Ignore test-generated files | ||
global.json | ||
commandline.txt | ||
**/*BuildInfo.fs | ||
StandardOutput.txt | ||
StandardError.txt |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<DirectoryBuildPropsPath Condition="'$(LoadLocalFSharpBuild)' == 'True'">../../../../UseLocalCompiler.Directory.Build.props</DirectoryBuildPropsPath> | ||
<DirectoryBuildPropsPath Condition="'$(LoadLocalFSharpBuild)' != 'True'"></DirectoryBuildPropsPath> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<Compile Include="AppBuildInfo.fs" /> | ||
<Compile Include="Program.fs" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="../CompilerCompatLib/CompilerCompatLib.fsproj" /> | ||
</ItemGroup> | ||
|
||
<Target Name="GenerateAppBuildInfo" BeforeTargets="BeforeCompile"> | ||
<PropertyGroup> | ||
<IsLocalBuildValue Condition="'$(LoadLocalFSharpBuild)' == 'True'">true</IsLocalBuildValue> | ||
<IsLocalBuildValue Condition="'$(LoadLocalFSharpBuild)' != 'True'">false</IsLocalBuildValue> | ||
<CleanDotnetFscCompilerPath Condition="'$(DotnetFscCompilerPath)' != ''">$(DotnetFscCompilerPath.Replace('"', ''))</CleanDotnetFscCompilerPath> | ||
<CleanDotnetFscCompilerPath Condition="'$(DotnetFscCompilerPath)' == ''">N/A</CleanDotnetFscCompilerPath> | ||
</PropertyGroup> | ||
<WriteLinesToFile | ||
File="AppBuildInfo.fs" | ||
Overwrite="true" | ||
Lines="namespace CompilerCompatApp | ||
module AppBuildInfo = | ||
let sdkVersion = "$(NETCoreSdkVersion)" | ||
let fsharpCompilerPath = "$(FscToolPath)\$(FscToolExe)" | ||
let dotnetFscCompilerPath = "$(CleanDotnetFscCompilerPath)" | ||
let isLocalBuild = $(IsLocalBuildValue)" /> | ||
</Target> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
open CompilerCompatLib | ||
open CompilerCompatApp | ||
open System | ||
|
||
[<EntryPoint>] | ||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<GenerateDocumentationFile>true</GenerateDocumentationFile> | ||
<DirectoryBuildPropsPath Condition="'$(LoadLocalFSharpBuild)' == 'True'">../../../../UseLocalCompiler.Directory.Build.props</DirectoryBuildPropsPath> | ||
<DirectoryBuildPropsPath Condition="'$(LoadLocalFSharpBuild)' != 'True'"></DirectoryBuildPropsPath> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<Compile Include="LibBuildInfo.fs" /> | ||
<Compile Include="Library.fs" /> | ||
</ItemGroup> | ||
|
||
<Target Name="GenerateLibBuildInfo" BeforeTargets="BeforeCompile"> | ||
<PropertyGroup> | ||
<IsLocalBuildValue Condition="'$(LoadLocalFSharpBuild)' == 'True'">true</IsLocalBuildValue> | ||
<IsLocalBuildValue Condition="'$(LoadLocalFSharpBuild)' != 'True'">false</IsLocalBuildValue> | ||
<CleanDotnetFscCompilerPath Condition="'$(DotnetFscCompilerPath)' != ''">$(DotnetFscCompilerPath.Replace('"', ''))</CleanDotnetFscCompilerPath> | ||
<CleanDotnetFscCompilerPath Condition="'$(DotnetFscCompilerPath)' == ''">N/A</CleanDotnetFscCompilerPath> | ||
</PropertyGroup> | ||
<WriteLinesToFile | ||
File="LibBuildInfo.fs" | ||
Overwrite="true" | ||
Lines="namespace CompilerCompatLib | ||
module LibBuildInfo = | ||
let sdkVersion = "$(NETCoreSdkVersion)" | ||
let fsharpCompilerPath = "$(FscToolPath)\$(FscToolExe)" | ||
let dotnetFscCompilerPath = "$(CleanDotnetFscCompilerPath)" | ||
let isLocalBuild = $(IsLocalBuildValue)" /> | ||
</Target> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Uh oh!
There was an error while loading. Please reload this page.