Skip to content
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

Run TransparentCompiler unit tests with local response files. #16609

Merged
merged 6 commits into from
Jan 30, 2024
Merged
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
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
Loading