Skip to content
Draft
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
125 changes: 125 additions & 0 deletions tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs
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

View check run for this annotation

Azure Pipelines / fsharp-ci (Build Linux)

tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs#L25

tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs(25,62): error FS0001: (NETCORE_ENGINEERING_TELEMETRY=Build) This expression was expected to have type� 'string array' �but here has type� 'string'

Check failure on line 25 in tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs

View check run for this annotation

Azure Pipelines / fsharp-ci (Build MacOS)

tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs#L25

tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs(25,62): error FS0001: (NETCORE_ENGINEERING_TELEMETRY=Build) This expression was expected to have type� 'string array' �but here has type� 'string'
let errorStr = String.concat "\n" (List.ofArray error)

Check failure on line 26 in tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs

View check run for this annotation

Azure Pipelines / fsharp-ci (Build Linux)

tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs#L26

tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs(26,61): error FS0001: (NETCORE_ENGINEERING_TELEMETRY=Build) This expression was expected to have type� 'string array' �but here has type� 'string'

Check failure on line 26 in tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs

View check run for this annotation

Azure Pipelines / fsharp-ci (Build MacOS)

tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs#L26

tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs(26,61): error FS0001: (NETCORE_ENGINEERING_TELEMETRY=Build) This expression was expected to have type� 'string array' �but here has type� 'string'
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

View check run for this annotation

Azure Pipelines / fsharp-ci (Build Linux)

tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs#L29

tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs(29,42): error FS0001: (NETCORE_ENGINEERING_TELEMETRY=Build) This expression was expected to have type� 'string array' �but here has type� 'string'

Check failure on line 29 in tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs

View check run for this annotation

Azure Pipelines / fsharp-ci (Build MacOS)

tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs#L29

tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs(29,42): error FS0001: (NETCORE_ENGINEERING_TELEMETRY=Build) This expression was expected to have type� 'string array' �but here has type� 'string'

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

View check run for this annotation

Azure Pipelines / fsharp-ci (Build Linux)

tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs#L33

tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs(33,53): error FS0001: (NETCORE_ENGINEERING_TELEMETRY=Build) This expression was expected to have type� 'string array' �but here has type� 'string'

Check failure on line 33 in tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs

View check run for this annotation

Azure Pipelines / fsharp-ci (Build Linux)

tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs#L33

tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs(33,95): error FS0001: (NETCORE_ENGINEERING_TELEMETRY=Build) This expression was expected to have type� 'string array' �but here has type� 'string'

Check failure on line 33 in tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs

View check run for this annotation

Azure Pipelines / fsharp-ci (Build MacOS)

tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs#L33

tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs(33,53): error FS0001: (NETCORE_ENGINEERING_TELEMETRY=Build) This expression was expected to have type� 'string array' �but here has type� 'string'

Check failure on line 33 in tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs

View check run for this annotation

Azure Pipelines / fsharp-ci (Build MacOS)

tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs#L33

tests/FSharp.Compiler.ComponentTests/CompilerCompatibilityTests.fs(33,95): error FS0001: (NETCORE_ENGINEERING_TELEMETRY=Build) This expression was expected to have type� 'string array' �but here has type� 'string'

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
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
Expand Up @@ -352,6 +352,7 @@
<Compile Include="FSharpChecker\SymbolUse.fs" />
<Compile Include="FSharpChecker\FindReferences.fs" />
<Compile Include="Attributes\AttributeCtorSetPropAccess.fs" />
<Compile Include="CompilerCompatibilityTests.fs" />
</ItemGroup>

<ItemGroup>
Expand Down
6 changes: 6 additions & 0 deletions tests/projects/CompilerCompat/.gitignore
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('&quot;', ''))</CleanDotnetFscCompilerPath>
<CleanDotnetFscCompilerPath Condition="'$(DotnetFscCompilerPath)' == ''">N/A</CleanDotnetFscCompilerPath>
</PropertyGroup>
<WriteLinesToFile
File="AppBuildInfo.fs"
Overwrite="true"
Lines="namespace CompilerCompatApp
module AppBuildInfo =
let sdkVersion = &quot;$(NETCoreSdkVersion)&quot;
let fsharpCompilerPath = &quot;$(FscToolPath)\$(FscToolExe)&quot;
let dotnetFscCompilerPath = &quot;$(CleanDotnetFscCompilerPath)&quot;
let isLocalBuild = $(IsLocalBuildValue)" />
</Target>

</Project>
52 changes: 52 additions & 0 deletions tests/projects/CompilerCompat/CompilerCompatApp/Program.fs
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('&quot;', ''))</CleanDotnetFscCompilerPath>
<CleanDotnetFscCompilerPath Condition="'$(DotnetFscCompilerPath)' == ''">N/A</CleanDotnetFscCompilerPath>
</PropertyGroup>
<WriteLinesToFile
File="LibBuildInfo.fs"
Overwrite="true"
Lines="namespace CompilerCompatLib
module LibBuildInfo =
let sdkVersion = &quot;$(NETCoreSdkVersion)&quot;
let fsharpCompilerPath = &quot;$(FscToolPath)\$(FscToolExe)&quot;
let dotnetFscCompilerPath = &quot;$(CleanDotnetFscCompilerPath)&quot;
let isLocalBuild = $(IsLocalBuildValue)" />
</Target>

</Project>
17 changes: 17 additions & 0 deletions tests/projects/CompilerCompat/CompilerCompatLib/Library.fs
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
Loading