Skip to content

Commit

Permalink
Run TransparentCompiler unit tests with local response files. (#16609)
Browse files Browse the repository at this point in the history
  • Loading branch information
nojaf authored Jan 30, 2024
1 parent a734882 commit 33e8be6
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ let fuzzingTest seed (project: SyntheticProject) = task {
let checker = builder.Checker

// Force creation and caching of options
do! SaveAndCheckProject project checker |> Async.Ignore
do! SaveAndCheckProject project checker false |> Async.Ignore

let projectAgent = MailboxProcessor.Start(fun (inbox: MailboxProcessor<ProjectRequest>) ->
let rec loop project =
Expand Down Expand Up @@ -800,4 +800,45 @@ module Stuff =

//Assert.Equal<string>(hash, hash2)

()
()

/// Update these paths to a local response file with compiler arguments of existing F# projects.
/// References projects are expected to have been built.
let localResponseFiles =
[|
@"C:\Projects\fantomas\src\Fantomas.Core.Tests\Fantomas.Core.Tests.rsp"
|]
|> Array.collect (fun f ->
[|
[| true :> obj; f:> obj |]
[| false :> obj; f :> obj|]
|]
)

// Uncomment this attribute if you want run this test against local response files.
// [<Theory>]
[<MemberData(nameof(localResponseFiles))>]
let ``TypeCheck last file in project with transparent compiler`` useTransparentCompiler responseFile =
let responseFile = FileInfo responseFile
let syntheticProject = mkSyntheticProjectForResponseFile responseFile

let workflow =
ProjectWorkflowBuilder(
syntheticProject,
isExistingProject = true,
useTransparentCompiler = useTransparentCompiler
)

let lastFile =
syntheticProject.SourceFiles
|> List.tryLast
|> Option.map (fun sf -> sf.Id)

match lastFile with
| None -> failwithf "Last file of project could not be found"
| Some lastFile ->

workflow {
clearCache
checkFile lastFile expectOk
}
100 changes: 91 additions & 9 deletions tests/FSharp.Test.Utilities/ProjectGeneration.fs
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,13 @@ type SyntheticSourceFile =
Source: string
ExtraSource: string
EntryPoint: bool
/// Indicates whether this is an existing F# file on disk.
IsPhysicalFile: bool
}

member this.FileName = $"File{this.Id}.fs"
member this.FileName =
if this.IsPhysicalFile then $"%s{this.Id}.fs" else $"File%s{this.Id}.fs"

member this.SignatureFileName = $"{this.FileName}i"
member this.TypeName = $"T{this.Id}V_{this.PublicVersion}"
member this.ModuleName = $"Module{this.Id}"
Expand All @@ -216,7 +220,8 @@ let sourceFile fileId deps =
HasErrors = false
Source = ""
ExtraSource = ""
EntryPoint = false }
EntryPoint = false
IsPhysicalFile = false }


let OptionsCache = ConcurrentDictionary()
Expand Down Expand Up @@ -468,6 +473,76 @@ let private writeFile (p: SyntheticProject) (f: SyntheticSourceFile) =
let content = renderSourceFile p f
writeFileIfChanged fileName content

/// Creates a SyntheticProject from the compiler arguments found in the response file.
let mkSyntheticProjectForResponseFile (responseFile: FileInfo) : SyntheticProject =
if not responseFile.Exists then
failwith $"%s{responseFile.FullName} does not exist"

let compilerArgs = File.ReadAllLines responseFile.FullName

let fsharpFileExtensions = set [| ".fs" ; ".fsi" ; ".fsx" |]

let isFSharpFile (file : string) =
Set.exists (fun (ext : string) -> file.EndsWith (ext, StringComparison.Ordinal)) fsharpFileExtensions

let fsharpFiles =
compilerArgs
|> Array.choose (fun (line : string) ->
if not (isFSharpFile line) then
None
else

let fullPath = Path.Combine (responseFile.DirectoryName, line)
if not (File.Exists fullPath) then
None
else
Some fullPath
)
|> Array.toList

let signatureFiles, implementationFiles =
fsharpFiles |> List.partition (fun path -> path.EndsWith ".fsi")

let signatureFiles = set signatureFiles

let sourceFiles =
implementationFiles
|> List.map (fun implPath ->
let id =
let fileNameWithoutExtension = Path.GetFileNameWithoutExtension implPath
let directoryOfFile = FileInfo(implPath).DirectoryName
let relativeUri = Uri(responseFile.FullName).MakeRelativeUri(Uri(directoryOfFile))
let relativeFolderPath = Uri.UnescapeDataString(relativeUri.ToString()).Replace('/', Path.DirectorySeparatorChar)
Path.Combine(relativeFolderPath, fileNameWithoutExtension)

{
Id = id
PublicVersion = 1
InternalVersion = 1
DependsOn = []
FunctionName = "f"
SignatureFile =
let sigPath = $"%s{implPath}i" in
if signatureFiles.Contains sigPath then Custom(File.ReadAllText sigPath) else No
HasErrors = false
Source = File.ReadAllText implPath
ExtraSource = ""
EntryPoint = false
IsPhysicalFile = true
}
)

let otherOptions =
compilerArgs
|> Array.filter (fun line -> not (isFSharpFile line))
|> Array.toList

{ SyntheticProject.Create(Path.GetFileNameWithoutExtension responseFile.Name) with
ProjectDir = responseFile.DirectoryName
SourceFiles = sourceFiles
OtherOptions = otherOptions
AutoAddModules = false
}

[<AutoOpen>]
module ProjectOperations =
Expand Down Expand Up @@ -792,12 +867,14 @@ type WorkflowContext =
Signatures: Map<string, string>
Cursor: FSharpSymbolUse option }

let SaveAndCheckProject project checker =
let SaveAndCheckProject project checker isExistingProject =
async {
use _ =
Activity.start "SaveAndCheckProject" [ Activity.Tags.project, project.Name ]

do! saveProject project true checker
// Don't save the project if it is a real world project that exists on disk.
if not isExistingProject then
do! saveProject project true checker

let options = project.GetProjectOptions checker
let! snapshot = FSharpProjectSnapshot.FromOptions(options, getFileSnapshot project)
Expand Down Expand Up @@ -834,13 +911,15 @@ type ProjectWorkflowBuilder
?useSyntaxTreeCache,
?useTransparentCompiler,
?runTimeout,
?autoStart
?autoStart,
?isExistingProject
) =

let useTransparentCompiler = defaultArg useTransparentCompiler FSharp.Compiler.CompilerConfig.FSharpExperimentalFeaturesEnabledAutomatically
let useGetSource = not useTransparentCompiler && defaultArg useGetSource false
let useChangeNotifications = not useTransparentCompiler && defaultArg useChangeNotifications false
let autoStart = defaultArg autoStart true
let isExistingProject = defaultArg isExistingProject false

let mutable latestProject = initialProject
let mutable activity = None
Expand Down Expand Up @@ -874,7 +953,7 @@ type ProjectWorkflowBuilder
let getInitialContext() =
match initialContext with
| Some ctx -> async.Return ctx
| None -> SaveAndCheckProject initialProject checker
| None -> SaveAndCheckProject initialProject checker isExistingProject

/// Creates a ProjectWorkflowBuilder which will already have the project
/// saved and checked so time won't be spent on that.
Expand Down Expand Up @@ -915,7 +994,7 @@ type ProjectWorkflowBuilder
try
Async.RunSynchronously(workflow, timeout = defaultArg runTimeout 600_000)
finally
if initialContext.IsNone then
if initialContext.IsNone && not isExistingProject then
this.DeleteProjectDir()
activity |> Option.iter (fun x -> x.Dispose())
tracerProvider |> Option.iter (fun x ->
Expand Down Expand Up @@ -1021,10 +1100,13 @@ type ProjectWorkflowBuilder
async {
let! ctx = workflow

use _ =
use activity =
Activity.start "ProjectWorkflowBuilder.CheckFile" [ Activity.Tags.project, initialProject.Name; "fileId", fileId ]

let! results = checkFile fileId ctx.Project checker
let! results =
checkFile fileId ctx.Project checker

activity.Dispose()

let oldSignature = ctx.Signatures[fileId]
let newSignature = getSignature results
Expand Down

0 comments on commit 33e8be6

Please sign in to comment.