Skip to content

Commit 33e8be6

Browse files
authored
Run TransparentCompiler unit tests with local response files. (#16609)
1 parent a734882 commit 33e8be6

File tree

2 files changed

+134
-11
lines changed

2 files changed

+134
-11
lines changed

tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ let fuzzingTest seed (project: SyntheticProject) = task {
494494
let checker = builder.Checker
495495

496496
// Force creation and caching of options
497-
do! SaveAndCheckProject project checker |> Async.Ignore
497+
do! SaveAndCheckProject project checker false |> Async.Ignore
498498

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

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

803-
()
803+
()
804+
805+
/// Update these paths to a local response file with compiler arguments of existing F# projects.
806+
/// References projects are expected to have been built.
807+
let localResponseFiles =
808+
[|
809+
@"C:\Projects\fantomas\src\Fantomas.Core.Tests\Fantomas.Core.Tests.rsp"
810+
|]
811+
|> Array.collect (fun f ->
812+
[|
813+
[| true :> obj; f:> obj |]
814+
[| false :> obj; f :> obj|]
815+
|]
816+
)
817+
818+
// Uncomment this attribute if you want run this test against local response files.
819+
// [<Theory>]
820+
[<MemberData(nameof(localResponseFiles))>]
821+
let ``TypeCheck last file in project with transparent compiler`` useTransparentCompiler responseFile =
822+
let responseFile = FileInfo responseFile
823+
let syntheticProject = mkSyntheticProjectForResponseFile responseFile
824+
825+
let workflow =
826+
ProjectWorkflowBuilder(
827+
syntheticProject,
828+
isExistingProject = true,
829+
useTransparentCompiler = useTransparentCompiler
830+
)
831+
832+
let lastFile =
833+
syntheticProject.SourceFiles
834+
|> List.tryLast
835+
|> Option.map (fun sf -> sf.Id)
836+
837+
match lastFile with
838+
| None -> failwithf "Last file of project could not be found"
839+
| Some lastFile ->
840+
841+
workflow {
842+
clearCache
843+
checkFile lastFile expectOk
844+
}

tests/FSharp.Test.Utilities/ProjectGeneration.fs

Lines changed: 91 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,13 @@ type SyntheticSourceFile =
193193
Source: string
194194
ExtraSource: string
195195
EntryPoint: bool
196+
/// Indicates whether this is an existing F# file on disk.
197+
IsPhysicalFile: bool
196198
}
197199

198-
member this.FileName = $"File{this.Id}.fs"
200+
member this.FileName =
201+
if this.IsPhysicalFile then $"%s{this.Id}.fs" else $"File%s{this.Id}.fs"
202+
199203
member this.SignatureFileName = $"{this.FileName}i"
200204
member this.TypeName = $"T{this.Id}V_{this.PublicVersion}"
201205
member this.ModuleName = $"Module{this.Id}"
@@ -216,7 +220,8 @@ let sourceFile fileId deps =
216220
HasErrors = false
217221
Source = ""
218222
ExtraSource = ""
219-
EntryPoint = false }
223+
EntryPoint = false
224+
IsPhysicalFile = false }
220225

221226

222227
let OptionsCache = ConcurrentDictionary()
@@ -468,6 +473,76 @@ let private writeFile (p: SyntheticProject) (f: SyntheticSourceFile) =
468473
let content = renderSourceFile p f
469474
writeFileIfChanged fileName content
470475

476+
/// Creates a SyntheticProject from the compiler arguments found in the response file.
477+
let mkSyntheticProjectForResponseFile (responseFile: FileInfo) : SyntheticProject =
478+
if not responseFile.Exists then
479+
failwith $"%s{responseFile.FullName} does not exist"
480+
481+
let compilerArgs = File.ReadAllLines responseFile.FullName
482+
483+
let fsharpFileExtensions = set [| ".fs" ; ".fsi" ; ".fsx" |]
484+
485+
let isFSharpFile (file : string) =
486+
Set.exists (fun (ext : string) -> file.EndsWith (ext, StringComparison.Ordinal)) fsharpFileExtensions
487+
488+
let fsharpFiles =
489+
compilerArgs
490+
|> Array.choose (fun (line : string) ->
491+
if not (isFSharpFile line) then
492+
None
493+
else
494+
495+
let fullPath = Path.Combine (responseFile.DirectoryName, line)
496+
if not (File.Exists fullPath) then
497+
None
498+
else
499+
Some fullPath
500+
)
501+
|> Array.toList
502+
503+
let signatureFiles, implementationFiles =
504+
fsharpFiles |> List.partition (fun path -> path.EndsWith ".fsi")
505+
506+
let signatureFiles = set signatureFiles
507+
508+
let sourceFiles =
509+
implementationFiles
510+
|> List.map (fun implPath ->
511+
let id =
512+
let fileNameWithoutExtension = Path.GetFileNameWithoutExtension implPath
513+
let directoryOfFile = FileInfo(implPath).DirectoryName
514+
let relativeUri = Uri(responseFile.FullName).MakeRelativeUri(Uri(directoryOfFile))
515+
let relativeFolderPath = Uri.UnescapeDataString(relativeUri.ToString()).Replace('/', Path.DirectorySeparatorChar)
516+
Path.Combine(relativeFolderPath, fileNameWithoutExtension)
517+
518+
{
519+
Id = id
520+
PublicVersion = 1
521+
InternalVersion = 1
522+
DependsOn = []
523+
FunctionName = "f"
524+
SignatureFile =
525+
let sigPath = $"%s{implPath}i" in
526+
if signatureFiles.Contains sigPath then Custom(File.ReadAllText sigPath) else No
527+
HasErrors = false
528+
Source = File.ReadAllText implPath
529+
ExtraSource = ""
530+
EntryPoint = false
531+
IsPhysicalFile = true
532+
}
533+
)
534+
535+
let otherOptions =
536+
compilerArgs
537+
|> Array.filter (fun line -> not (isFSharpFile line))
538+
|> Array.toList
539+
540+
{ SyntheticProject.Create(Path.GetFileNameWithoutExtension responseFile.Name) with
541+
ProjectDir = responseFile.DirectoryName
542+
SourceFiles = sourceFiles
543+
OtherOptions = otherOptions
544+
AutoAddModules = false
545+
}
471546

472547
[<AutoOpen>]
473548
module ProjectOperations =
@@ -792,12 +867,14 @@ type WorkflowContext =
792867
Signatures: Map<string, string>
793868
Cursor: FSharpSymbolUse option }
794869

795-
let SaveAndCheckProject project checker =
870+
let SaveAndCheckProject project checker isExistingProject =
796871
async {
797872
use _ =
798873
Activity.start "SaveAndCheckProject" [ Activity.Tags.project, project.Name ]
799874

800-
do! saveProject project true checker
875+
// Don't save the project if it is a real world project that exists on disk.
876+
if not isExistingProject then
877+
do! saveProject project true checker
801878

802879
let options = project.GetProjectOptions checker
803880
let! snapshot = FSharpProjectSnapshot.FromOptions(options, getFileSnapshot project)
@@ -834,13 +911,15 @@ type ProjectWorkflowBuilder
834911
?useSyntaxTreeCache,
835912
?useTransparentCompiler,
836913
?runTimeout,
837-
?autoStart
914+
?autoStart,
915+
?isExistingProject
838916
) =
839917

840918
let useTransparentCompiler = defaultArg useTransparentCompiler FSharp.Compiler.CompilerConfig.FSharpExperimentalFeaturesEnabledAutomatically
841919
let useGetSource = not useTransparentCompiler && defaultArg useGetSource false
842920
let useChangeNotifications = not useTransparentCompiler && defaultArg useChangeNotifications false
843921
let autoStart = defaultArg autoStart true
922+
let isExistingProject = defaultArg isExistingProject false
844923

845924
let mutable latestProject = initialProject
846925
let mutable activity = None
@@ -874,7 +953,7 @@ type ProjectWorkflowBuilder
874953
let getInitialContext() =
875954
match initialContext with
876955
| Some ctx -> async.Return ctx
877-
| None -> SaveAndCheckProject initialProject checker
956+
| None -> SaveAndCheckProject initialProject checker isExistingProject
878957

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

1024-
use _ =
1103+
use activity =
10251104
Activity.start "ProjectWorkflowBuilder.CheckFile" [ Activity.Tags.project, initialProject.Name; "fileId", fileId ]
10261105

1027-
let! results = checkFile fileId ctx.Project checker
1106+
let! results =
1107+
checkFile fileId ctx.Project checker
1108+
1109+
activity.Dispose()
10281110

10291111
let oldSignature = ctx.Signatures[fileId]
10301112
let newSignature = getSignature results

0 commit comments

Comments
 (0)