From dffca7a26fb7fc2ae547bc0d0887eadd2610cc5d Mon Sep 17 00:00:00 2001 From: janusz Date: Mon, 14 Nov 2022 22:43:56 +0000 Subject: [PATCH] Create ArgsGenerator that uses ProjInfo to dump fsc args in a file, then run it in unit tests. Not fully working as ProjInfo is missing some necessary args. --- FSharp.sln | 15 ++++ NuGet.config | 1 + src/Compiler/Driver/ParseAndCheckInputs.fs | 2 - src/Compiler/Driver/ParseAndCheckInputs.fsi | 2 - tests/ArgsGenerator/ArgsGenerator.fsproj | 23 ++++++ tests/ArgsGenerator/Program.fs | 40 ++++++++++ .../ParallelTypeCheckingTests.fsproj | 4 - tests/ParallelTypeCheckingTests/Program.fs | 31 +++----- .../Tests/TestCompilationFromCmdlineArgs.fs | 62 ++++------------ .../Tests/TestDependencyResolution.fs | 33 +-------- .../ParallelTypeCheckingTests/Tests/Utils.fs | 74 +++++++++++++++---- 11 files changed, 169 insertions(+), 118 deletions(-) create mode 100644 tests/ArgsGenerator/ArgsGenerator.fsproj create mode 100644 tests/ArgsGenerator/Program.fs diff --git a/FSharp.sln b/FSharp.sln index 20c74ace2fc..202812347fe 100644 --- a/FSharp.sln +++ b/FSharp.sln @@ -109,6 +109,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ParallelTypeCheckingTests", "tests\ParallelTypeCheckingTests\ParallelTypeCheckingTests.fsproj", "{59C31D40-97E0-4A69-ABD9-D316BD798ED8}" EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ArgsGenerator", "tests\ArgsGenerator\ArgsGenerator.fsproj", "{90F850DD-52AD-40BC-A497-DE0382C1EE72}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -443,6 +445,18 @@ Global {59C31D40-97E0-4A69-ABD9-D316BD798ED8}.Release|Any CPU.Build.0 = Release|Any CPU {59C31D40-97E0-4A69-ABD9-D316BD798ED8}.Release|x86.ActiveCfg = Release|Any CPU {59C31D40-97E0-4A69-ABD9-D316BD798ED8}.Release|x86.Build.0 = Release|Any CPU + {90F850DD-52AD-40BC-A497-DE0382C1EE72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90F850DD-52AD-40BC-A497-DE0382C1EE72}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90F850DD-52AD-40BC-A497-DE0382C1EE72}.Debug|x86.ActiveCfg = Debug|Any CPU + {90F850DD-52AD-40BC-A497-DE0382C1EE72}.Debug|x86.Build.0 = Debug|Any CPU + {90F850DD-52AD-40BC-A497-DE0382C1EE72}.Proto|Any CPU.ActiveCfg = Debug|Any CPU + {90F850DD-52AD-40BC-A497-DE0382C1EE72}.Proto|Any CPU.Build.0 = Debug|Any CPU + {90F850DD-52AD-40BC-A497-DE0382C1EE72}.Proto|x86.ActiveCfg = Debug|Any CPU + {90F850DD-52AD-40BC-A497-DE0382C1EE72}.Proto|x86.Build.0 = Debug|Any CPU + {90F850DD-52AD-40BC-A497-DE0382C1EE72}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90F850DD-52AD-40BC-A497-DE0382C1EE72}.Release|Any CPU.Build.0 = Release|Any CPU + {90F850DD-52AD-40BC-A497-DE0382C1EE72}.Release|x86.ActiveCfg = Release|Any CPU + {90F850DD-52AD-40BC-A497-DE0382C1EE72}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -476,6 +490,7 @@ Global {BEC6E796-7E53-4888-AAFC-B8FD55C425DF} = {CE70D631-C5DC-417E-9CDA-B16097BEF1AC} {9C7523BA-7AB2-4604-A5FD-653E82C2BAD1} = {CE70D631-C5DC-417E-9CDA-B16097BEF1AC} {59C31D40-97E0-4A69-ABD9-D316BD798ED8} = {CFE3259A-2D30-4EB0-80D5-E8B5F3D01449} + {90F850DD-52AD-40BC-A497-DE0382C1EE72} = {CFE3259A-2D30-4EB0-80D5-E8B5F3D01449} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BD5177C7-1380-40E7-94D2-7768E1A8B1B8} diff --git a/NuGet.config b/NuGet.config index 5b0a8ef0a09..ef9293a43ee 100644 --- a/NuGet.config +++ b/NuGet.config @@ -15,6 +15,7 @@ + diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fs b/src/Compiler/Driver/ParseAndCheckInputs.fs index 4d7c15cd367..dd6daec7897 100644 --- a/src/Compiler/Driver/ParseAndCheckInputs.fs +++ b/src/Compiler/Driver/ParseAndCheckInputs.fs @@ -1738,8 +1738,6 @@ let CheckMultipleInputsInParallel let mutable CheckMultipleInputsUsingGraphMode: CheckArgs -> (PartialResult list * TcState) = fun _ -> failwith $"Graph-based type-checking function not set - set CheckMultipleInputsUsingGraphMode before using this mode" -let mutable typeCheckingMode: TypeCheckingMode = TypeCheckingMode.Sequential - let CheckClosedInputSet (ctok, checkForErrors, tcConfig: TcConfig, tcImports, tcGlobals, prefixPathOpt, tcState, eagerFormat, inputs) = // tcEnvAtEndOfLastFile is the environment required by fsi.exe when incrementally adding definitions let results, tcState = diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fsi b/src/Compiler/Driver/ParseAndCheckInputs.fsi index 708111d89ff..0444db365c0 100644 --- a/src/Compiler/Driver/ParseAndCheckInputs.fsi +++ b/src/Compiler/Driver/ParseAndCheckInputs.fsi @@ -202,8 +202,6 @@ val CheckMultipleInputsFinish: /// Finish the checking of a closed set of inputs val CheckClosedInputSetFinish: CheckedImplFile list * TcState -> TcState * CheckedImplFile list * ModuleOrNamespace -val mutable typeCheckingMode: TypeCheckingMode - /// Check a closed set of inputs val CheckClosedInputSet: ctok: CompilationThreadToken * diff --git a/tests/ArgsGenerator/ArgsGenerator.fsproj b/tests/ArgsGenerator/ArgsGenerator.fsproj new file mode 100644 index 00000000000..82ac2ed92c5 --- /dev/null +++ b/tests/ArgsGenerator/ArgsGenerator.fsproj @@ -0,0 +1,23 @@ + + + + net7.0 + true + exe + true + + + + + + + + + + + + + + + + diff --git a/tests/ArgsGenerator/Program.fs b/tests/ArgsGenerator/Program.fs new file mode 100644 index 00000000000..7f7140e61e6 --- /dev/null +++ b/tests/ArgsGenerator/Program.fs @@ -0,0 +1,40 @@ +module ArgsGenerator + +open System.IO +open FSharp.Compiler.CodeAnalysis +open Ionide.ProjInfo + +let optionsToArgs (opts: FSharpProjectOptions) : string[] = + Array.append opts.OtherOptions opts.SourceFiles + +let loadProjectArgs (projectFile: string) = + let dir = Path.GetDirectoryName projectFile + let _ = Init.init (DirectoryInfo dir) None + + let props = + [ + "TargetFramework", "net7.0" + "Configuration", "Release" + ] + let res = + ProjectLoader.getProjectInfo projectFile props (BinaryLogGeneration.Within(DirectoryInfo("c:/projekty/fsharp/cracking_logs"))) + + match res [] with + | Result.Ok _res -> + let fcs = FCS.mapToFSharpProjectOptions _res [] + let args = optionsToArgs fcs + args + | Result.Error err -> failwith $"Failed to crack project: {err}" + +/// Given a project file to analyse, generates fsc commandline arguments for compiling it, then saves them to a file. +[] +let main argv = + match argv with + | [| projectFile; outputFile |] -> + printfn $"Loading project args for project file: {projectFile}" + let args = loadProjectArgs projectFile + printfn $"Loaded {args.Length} args. Saving them to {outputFile}" + File.WriteAllLines(outputFile, args) + 0 + | _ -> + failwith "Invalid args. Usage: 'ArgsGenerator.exe %project_file% %output_file%'" diff --git a/tests/ParallelTypeCheckingTests/ParallelTypeCheckingTests.fsproj b/tests/ParallelTypeCheckingTests/ParallelTypeCheckingTests.fsproj index cb6a1c7ade0..7c53f70eadc 100644 --- a/tests/ParallelTypeCheckingTests/ParallelTypeCheckingTests.fsproj +++ b/tests/ParallelTypeCheckingTests/ParallelTypeCheckingTests.fsproj @@ -72,8 +72,4 @@ - - - - diff --git a/tests/ParallelTypeCheckingTests/Program.fs b/tests/ParallelTypeCheckingTests/Program.fs index 9e0c9dd9617..2b2d68257ae 100644 --- a/tests/ParallelTypeCheckingTests/Program.fs +++ b/tests/ParallelTypeCheckingTests/Program.fs @@ -1,35 +1,28 @@ module internal ParallelTypeCheckingTests.Program -#nowarn "1182" - -open FSharp.Compiler.CompilerConfig open ParallelTypeCheckingTests.TestUtils -let _parse (argv: string[]) : Args = - let parseMode (mode: string) = - match mode.ToLower() with +let parseArgs (argv: string[]) : Args = + let parseMode (method: string) = + match method.ToLower() with | "sequential" -> Method.Sequential | "parallelfs" -> Method.ParallelCheckingOfBackedImplFiles | "graph" -> Method.Graph - | _ -> failwith $"Unrecognised mode: {mode}" + | _ -> failwith $"Unrecognised method: {method}" - let path, mode, workingDir = + let method, path = match argv with - | [| path |] -> path, Method.Sequential, None - | [| path; method |] -> path, parseMode method, None - | [| path; method; workingDir |] -> path, parseMode method, Some workingDir - | _ -> failwith "Invalid args - use 'args_path [method [fs-parallel]]'" + | [| path |] -> Method.Graph, path + | [| method; path |] -> parseMode method, path + | _ -> failwith "Invalid args. Usage: '%method% %project_file%'" { - Path = path - LineLimit = None - Method = mode - WorkingDir = workingDir + Method = method + ProjectFile = path } [] -let main _argv = - let args = _parse _argv - let args = { args with LineLimit = None } +let main argv = + let args = parseArgs argv TestCompilationFromCmdlineArgs.TestCompilerFromArgs args 0 diff --git a/tests/ParallelTypeCheckingTests/Tests/TestCompilationFromCmdlineArgs.fs b/tests/ParallelTypeCheckingTests/Tests/TestCompilationFromCmdlineArgs.fs index ba41fdb3240..da1301567b7 100644 --- a/tests/ParallelTypeCheckingTests/Tests/TestCompilationFromCmdlineArgs.fs +++ b/tests/ParallelTypeCheckingTests/Tests/TestCompilationFromCmdlineArgs.fs @@ -10,55 +10,22 @@ open ParallelTypeCheckingTests.TestUtils type Codebase = { - WorkDir: string - Path: string - Limit: int option + ProjectFile : string } let codebases = [| { - WorkDir = $@"{__SOURCE_DIRECTORY__}\.fcs_test\src\compiler" - Path = $@"{__SOURCE_DIRECTORY__}\FCS.args.txt" - Limit = None + ProjectFile = $@"{__SOURCE_DIRECTORY__}\.fcs_test\src\compiler\FSharp.Compiler.Service.fsproj" } { - WorkDir = $@"{__SOURCE_DIRECTORY__}\.fcs_test\tests\FSharp.Compiler.ComponentTests" - Path = $@"{__SOURCE_DIRECTORY__}\ComponentTests.args.txt" - Limit = None + ProjectFile = $@"{__SOURCE_DIRECTORY__}\.fcs_test\tests\FSharp.Compiler.ComponentTests\FSharp.Compiler.ComponentTests.fsproj" } |] -let internal setupParsed config = - let { - Path = path - LineLimit = lineLimit - Method = method - WorkingDir = workingDir - } = - config - - let args = - System.IO.File.ReadAllLines(path |> replacePaths) - |> fun lines -> - match lineLimit with - | Some limit -> Array.take (Math.Min(limit, lines.Length)) lines - | None -> lines - |> Array.map replacePaths - - setupCompilationMethod method - - printfn $"Method: {method}" - let args = - match method with - | Method.Sequential -> args - | Method.ParallelCheckingOfBackedImplFiles -> - Array.append args [|"--test:ParallelCheckingWithSignatureFilesOn"|] - | Method.Graph -> - Array.append args [|"--test:GraphBasedChecking"|] - - printfn $"WorkingDir = {workingDir}" - workingDir |> Option.iter (fun dir -> Environment.CurrentDirectory <- dir) +let internal setupParsed {Method = method; ProjectFile = path} = + let args = getProjectArgs path + let args = Array.append args (TestCompilation.methodOptions method |> List.toArray) args let internal TestCompilerFromArgs (config: Args) : unit = @@ -75,36 +42,33 @@ let internal TestCompilerFromArgs (config: Args) : unit = } try + printfn $"Type-checking method used: {config.Method}" let args = setupParsed config let exit: int = CommandLineMain.mainAux (args, true, Some exiter) Assert.That(exit, Is.Zero) finally Environment.CurrentDirectory <- oldWorkDir -let internal codebaseToConfig code method = +let internal codebaseToConfig (code : Codebase) method = { - Path = code.Path - LineLimit = code.Limit + ProjectFile = code.ProjectFile Method = method - WorkingDir = Some code.WorkDir } -[] +[] [] let ``1. Test sequential type-checking`` (code: Codebase) = let config = codebaseToConfig code Method.Sequential TestCompilerFromArgs config -/// Before running this test, you must prepare the codebase by running the script 'FCS.prepare.ps1' -[] +[] [] let ``2. Test parallelfs type-checking`` (code: Codebase) = let config = codebaseToConfig code Method.ParallelCheckingOfBackedImplFiles TestCompilerFromArgs config -/// Before running this test, you must prepare the codebase by running the script 'FCS.prepare.ps1' -[] +[] let ``3. Test graph-based type-checking`` (code: Codebase) = let config = codebaseToConfig code Method.Graph + printfn $"Args file generated: {config.ProjectFile}" TestCompilerFromArgs config - diff --git a/tests/ParallelTypeCheckingTests/Tests/TestDependencyResolution.fs b/tests/ParallelTypeCheckingTests/Tests/TestDependencyResolution.fs index 17980c16d4c..c2ad05bf06f 100644 --- a/tests/ParallelTypeCheckingTests/Tests/TestDependencyResolution.fs +++ b/tests/ParallelTypeCheckingTests/Tests/TestDependencyResolution.fs @@ -4,7 +4,6 @@ open FSharp.Compiler.Service.Tests.Common open System.IO -open Buildalyzer open ParallelTypeCheckingTests open ParallelTypeCheckingTests.Types open ParallelTypeCheckingTests.Utils @@ -337,53 +336,31 @@ let analyseResult (result: DepsResult) = v |> Array.map (fun d -> result.Graph[d].Length) |> Array.max) printfn $"TotalDeps: {totalDeps}, topFirstDeps: {topFirstDeps}" -// -// open GiGraph.Dot.Extensions -// open GiGraph.Dot.Output.Options -// let makeDotFile (path : string) (graph : Graph) : unit = -// let g = DotGraph(directed=true) -// g.Layout.Direction <- DotLayoutDirection.LeftToRight -// let name (f : File) = $"{f.QualifiedName}.{Path.GetExtension(f.Name)}" -// graph -// |> Graph.collectEdges -// |> Array.iter (fun (a, b) -> g.Edges.Add(name a, name b) |> ignore) -// let _options = DotFormattingOptions() -// printfn $"{g.Build()}" -// g.SaveToFile(path) [] let ``Analyse hardcoded files`` () = let deps = DependencyResolution.detectFileDependencies sampleFiles printfn "Detected file dependencies:" deps.Graph |> Graph.print - // makeDotFile "graph.dot" deps.Graph let private parseProjectAndGetSourceFiles (projectFile: string) = - //let cacheDir = "." - //let getName projectFile = Path.Combine(Path.GetFileName(projectFile), ".fsharp" - let m = AnalyzerManager() - let analyzer = m.GetProject(projectFile) - let results = analyzer.Build() - // TODO Generalise for multiple TFMs - let res = results.Results |> Seq.head - let files = res.SourceFiles - log "built project using Buildalyzer" - files + projectFile + |> TestUtils.getProjectArgs + |> TestUtils.extractSourceFilesFromArgs [] [] -[] let ``Analyse whole projects and print statistics`` (projectFile: string) = log $"Start finding file dependency graph for {projectFile}" let files = parseProjectAndGetSourceFiles projectFile + log $"{files.Length} source files loaded using ProjInfo" let files = files |> Array.Parallel.mapi (fun i f -> let code = System.IO.File.ReadAllText(f) let ast = parseSourceCode (f, code) - { Idx = FileIdx.make i //Code = code @@ -420,5 +397,3 @@ let ``Analyse whole projects and print statistics`` (projectFile: string) = v |> Array.map (fun d -> graph.Graph[d].Length) |> Array.max) printfn $"TotalDeps: {totalDeps}, topFirstDeps: {topFirstDeps}, diff: {totalDeps - topFirstDeps}" - - // makeDotFile "FCS.deps.dot" graph.Graph diff --git a/tests/ParallelTypeCheckingTests/Tests/Utils.fs b/tests/ParallelTypeCheckingTests/Tests/Utils.fs index 999d5b14bea..c60cf9f5843 100644 --- a/tests/ParallelTypeCheckingTests/Tests/Utils.fs +++ b/tests/ParallelTypeCheckingTests/Tests/Utils.fs @@ -1,15 +1,16 @@ module ParallelTypeCheckingTests.TestUtils open System +open System.IO open FSharp.Compiler open FSharp.Compiler.CompilerConfig -open ParallelTypeCheckingTests open Xunit open FSharp.Test open FSharp.Test.Compiler open OpenTelemetry open OpenTelemetry.Resources open OpenTelemetry.Trace +open System.Diagnostics let packages = // Here we assume that the NuGet packages are located in a certain user folder, @@ -45,14 +46,13 @@ let setupOtel () = c.MaxPayloadSizeInBytes <- Nullable(1000000000)) .Build() -type internal Args = + +type Args = { - Path: string - LineLimit: int option - Method: Method - WorkingDir: string option + Method : Method + ProjectFile : string } - + let makeCompilationUnit (files: (string * string) list) : CompilationUnit = let files = files |> List.map (fun (name, code) -> SourceCodeFileKind.Create(name, code)) @@ -69,9 +69,57 @@ let internal mapMethod (method: Method) = | Method.ParallelCheckingOfBackedImplFiles -> TypeCheckingMode.ParallelCheckingOfBackedImplFiles | Method.Graph -> TypeCheckingMode.Graph -/// Includes mutation of static config. -/// A very hacky way to setup the given type-checking method - mutates static state and returns new args -/// TODO Make the method configurable via proper config passed top-down -let setupCompilationMethod (method: Method) = - let mode = mapMethod method - ParseAndCheckInputs.typeCheckingMode <- mode +let runProcess + (name : string) + (args : string) + workingDir + (envVariables : (string * string) list) + = + let info = ProcessStartInfo () + info.WindowStyle <- ProcessWindowStyle.Hidden + info.Arguments <- args + info.FileName <- name + info.UseShellExecute <- false + info.WorkingDirectory <- workingDir + info.CreateNoWindow <- true + + envVariables |> List.iter (fun (k, v) -> info.EnvironmentVariables[ k ] <- v) + + printfn $"Running '{name} {args}' in '{workingDir}'" + let p = new Process (StartInfo = info) + p.EnableRaisingEvents <- true + p.Start () |> ignore + p.WaitForExit () + + if p.ExitCode <> 0 then + failwith $"Running process '{name} {args}' failed." + +let getProjectArgs (projectFile : string) : string[] = + // TODO Make ArgsGenerator a dotnet tool + let argsGeneratorProjectPath = $"{__SOURCE_DIRECTORY__}/../../ArgsGenerator/ArgsGenerator.fsproj" + let workingDir = "." + let name = "dotnet.exe" // TODO Handle non-Windows OS + let argsFile = Path.GetTempFileName() + let envVariables = [] + + // We run the ArgsGenerator project using 'dotnet run', and specify two arguments: project file to analyse, output file. + let args = + [| + "run" + "--project" + argsGeneratorProjectPath + "--" + $"\"{projectFile}\"" + argsFile + |] + |> fun args -> String.Join(" ", args) + + runProcess name args workingDir envVariables + + let output = File.ReadAllLines argsFile + output + +/// Given FSC arguments, extract those that represent source files +let extractSourceFilesFromArgs (args: string[]): string[] = + args + |> Array.filter (fun arg -> arg.StartsWith("-") |> not) \ No newline at end of file