|
| 1 | +open System |
| 2 | +open System.IO |
| 3 | +open System.Diagnostics |
| 4 | +open System.Reflection |
| 5 | + |
| 6 | +module Helpers = |
| 7 | + |
| 8 | + // runs a program, and exits the script if nonzero exit code is encountered |
| 9 | + let private run exePath args = |
| 10 | + let args = String.concat " " args |
| 11 | + let psi = ProcessStartInfo(FileName = exePath, Arguments = args, CreateNoWindow = true, UseShellExecute = false, RedirectStandardError = true) |
| 12 | + let p = Process.Start(psi) |
| 13 | + match p.WaitForExit(10 * 60 * 1000) with |
| 14 | + | false -> eprintfn "Process timed out"; exit 1 |
| 15 | + | true -> |
| 16 | + if p.ExitCode <> 0 then |
| 17 | + eprintfn "%s %s" exePath args |
| 18 | + eprintfn "%s" (p.StandardError.ReadToEnd()) |
| 19 | + exit p.ExitCode |
| 20 | + |
| 21 | + let private authorCompile compilerPath runtime source = |
| 22 | + run compilerPath ["-a"; "-o:author.dll"; "--noframework"; sprintf "\"-r:%s\"" runtime; source] |
| 23 | + |
| 24 | + let private consumerCompile compilerPath runtime source = |
| 25 | + run compilerPath ["-o:consumer.exe"; "--noframework"; sprintf "\"-r:%s\"" runtime; "-r:author.dll"; source] |
| 26 | + |
| 27 | + let private consumerRunFsi fsiPath source = |
| 28 | + run fsiPath ["--exec"; source] |
| 29 | + |
| 30 | + // runs the consumer EXE, handling binding redirects automatically |
| 31 | + let private consumerRunExe redirectVer = |
| 32 | + if File.Exists("consumer.exe.config") then |
| 33 | + File.Delete("consumer.exe.config") |
| 34 | + |
| 35 | + let content = File.ReadAllText("consumer.exe.config.txt").Replace("{ver}", redirectVer) |
| 36 | + File.WriteAllText("consumer.exe.config", content) |
| 37 | + |
| 38 | + run "consumer.exe" [] |
| 39 | + |
| 40 | + /// gets the version of the assembly at the specified path |
| 41 | + let getVer dllPath = |
| 42 | + let asm = Assembly.ReflectionOnlyLoadFrom(dllPath) |
| 43 | + asm.GetName().Version.ToString() |
| 44 | + |
| 45 | + /// runs through the end-to-end scenario of |
| 46 | + /// - Author uses [authorComiler] to build DLL targeting [authorRuntime] with source [authorSource] |
| 47 | + /// - Consumer uses [consumerCompiler] to build EXE ref'ing above DLL, building EXE targeting [consumerRuntime] with source [consumerSource] |
| 48 | + /// - Run the resulting EXE |
| 49 | + let testExe authorCompiler authorRuntime consumerCompiler consumerRuntime authorSource consumerSource = |
| 50 | + authorCompile authorCompiler authorRuntime authorSource |
| 51 | + consumerCompile consumerCompiler consumerRuntime consumerSource |
| 52 | + consumerRunExe (getVer consumerRuntime) |
| 53 | + |
| 54 | + /// runs through the end-to-end scenario of |
| 55 | + /// - Author uses [authorComiler] to build DLL targeting [authorRuntime] with source [authorSource] |
| 56 | + /// - Consumer uses [consumerFsi] to #r above DLL and run script [consumerSource] |
| 57 | + let testFsi authorCompiler authorRuntime consumerFsi authorSource consumerSource = |
| 58 | + authorCompile authorCompiler authorRuntime authorSource |
| 59 | + consumerRunFsi consumerFsi consumerSource |
| 60 | + |
| 61 | +module Test = |
| 62 | + let private env s = |
| 63 | + match Environment.GetEnvironmentVariable(s) with |
| 64 | + | var when not (String.IsNullOrWhiteSpace(var)) -> var |
| 65 | + | _ -> failwithf "Required env var %s not defined" s |
| 66 | + |
| 67 | + // paths to vPrevious of fsc.exe, fsi.exe, FSharp.Core.dll |
| 68 | + let vPrevCompiler = env "FSCVPREV" |
| 69 | + let vPrevFsi = Path.Combine(env "FSCVPREVBINPATH", "fsi.exe") |
| 70 | + let vPrevRuntime = env "FSCOREDLLVPREVPATH" |
| 71 | + |
| 72 | + // paths to vCurrent of fsc.exe, fsi.exe, FSharp.Core.dll |
| 73 | + let vCurrentCompiler = env "FSC" |
| 74 | + let vCurrentFsi = Path.Combine(env "FSCBINPATH", "fsi.exe") |
| 75 | + let vCurrentRuntime = env "FSCOREDLLPATH" |
| 76 | + |
| 77 | + let cases = |
| 78 | + // compiler/runtime of author | compiler/runtime of consumer |
| 79 | + [ 0, Helpers.testExe vPrevCompiler vPrevRuntime vCurrentCompiler vPrevRuntime |
| 80 | + 1, Helpers.testExe vPrevCompiler vPrevRuntime vCurrentCompiler vCurrentRuntime |
| 81 | + 2, Helpers.testExe vCurrentCompiler vPrevRuntime vPrevCompiler vPrevRuntime |
| 82 | + 3, Helpers.testExe vCurrentCompiler vPrevRuntime vCurrentCompiler vPrevRuntime |
| 83 | + 4, Helpers.testExe vCurrentCompiler vPrevRuntime vCurrentCompiler vCurrentRuntime |
| 84 | + 5, Helpers.testExe vCurrentCompiler vCurrentRuntime vCurrentCompiler vCurrentRuntime |
| 85 | + |
| 86 | + // compiler/runtime of author | fsi of consumer |
| 87 | + 6, Helpers.testFsi vPrevCompiler vPrevRuntime vCurrentFsi |
| 88 | + 7, Helpers.testFsi vCurrentCompiler vPrevRuntime vCurrentFsi |
| 89 | + 8, Helpers.testFsi vCurrentCompiler vPrevRuntime vPrevFsi |
| 90 | + 9, Helpers.testFsi vCurrentCompiler vCurrentRuntime vCurrentFsi |
| 91 | + ] |
| 92 | + |
| 93 | +// parse command line args |
| 94 | +// final 'exclusions' arg allows for certain scenarios to be skipped if they are not expected to work |
| 95 | +let authorSource, consumerSource, exclusions = |
| 96 | + match fsi.CommandLineArgs with |
| 97 | + | [| _; arg1; arg2 |] -> arg1, arg2, [| |] |
| 98 | + | [| _; arg1; arg2; arg3 |] -> arg1, arg2, (arg3.Split(',') |> Array.map int) |
| 99 | + | args -> |
| 100 | + eprintfn "Expecting args <author source> <consumer source> [excluded cases], got args %A" args |
| 101 | + exit 1 |
| 102 | + |
| 103 | +// failsafe to make sure that excluded scenarios are revisited on new versions |
| 104 | +// i.e. exclusions valid for vN/vN-1 will probably no longer be needed for vN+1/vN |
| 105 | +if not ((Helpers.getVer Test.vCurrentRuntime).StartsWith("4.4.0")) then |
| 106 | + eprintfn "Runtime version has changed, review exclusions lists for these tests" |
| 107 | + exit 1 |
| 108 | + |
| 109 | +Test.cases |
| 110 | +|> List.filter (fun (id, _) -> not (Array.contains id exclusions)) |
| 111 | +|> List.iter (fun (id, testCase) -> |
| 112 | + printfn "Case %d" id |
| 113 | + testCase authorSource consumerSource |
| 114 | +) |
0 commit comments