зеркало из https://github.com/dotnet/fsharp.git
190 строки
7.9 KiB
Forth
190 строки
7.9 KiB
Forth
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
|
|
|
|
namespace FSharp.Test
|
|
|
|
open System
|
|
open System.IO
|
|
open System.Text.RegularExpressions
|
|
|
|
open NUnit.Framework
|
|
open TestFramework
|
|
|
|
[<RequireQualifiedAccess>]
|
|
module ILChecker =
|
|
let config = initializeSuite ()
|
|
|
|
let private exec exe args =
|
|
let arguments = args |> String.concat " "
|
|
let timeout = 30000
|
|
let exitCode, _output, errors = Commands.executeProcess (Some exe) arguments "" timeout
|
|
let errors = errors |> String.concat Environment.NewLine
|
|
errors, exitCode
|
|
|
|
/// Filters i.e ['The system type \'System.ReadOnlySpan`1\' was required but no referenced system DLL contained this type']
|
|
let private filterSpecialComment (text: string) =
|
|
let pattern = @"(\[\'(.*?)\'\])"
|
|
Regex.Replace(
|
|
text,
|
|
pattern,
|
|
(fun me -> String.Empty)
|
|
)
|
|
|
|
let private normalizeILText assemblyName (ilCode: string) =
|
|
let blockComments = @"/\*(.*?)\*/"
|
|
let lineComments = @"//(.*?)\r?\n"
|
|
let lineCommentsEof = @"//(.*?)$"
|
|
let strings = @"""((\\[^\n]|[^""\n])*)"""
|
|
let verbatimStrings = @"@(""[^""]*"")+"
|
|
let stripComments (text:string) =
|
|
Regex.Replace(text,
|
|
$"{blockComments}|{lineComments}|{lineCommentsEof}|{strings}|{verbatimStrings}",
|
|
(fun me ->
|
|
if (me.Value.StartsWith("/*") || me.Value.StartsWith("//")) then
|
|
if me.Value.StartsWith("//") then Environment.NewLine else String.Empty
|
|
else
|
|
me.Value), RegexOptions.Singleline)
|
|
|> filterSpecialComment
|
|
|
|
let replace input (pattern, replacement: string) = Regex.Replace(input, pattern, replacement, RegexOptions.Singleline)
|
|
|
|
let unifyRuntimeAssemblyName ilCode =
|
|
List.fold replace ilCode [
|
|
"\[System\.Runtime\]|\[System\.Console\]|\[System\.Runtime\.Extensions\]|\[mscorlib\]|\[System\.Memory\]", "[runtime]"
|
|
"(\.assembly extern (System\.Runtime|System\.Console|System\.Runtime\.Extensions|mscorlib|System\.Memory)){1}([^\}]*)\}", ".assembly extern runtime { }"
|
|
"(\.assembly extern (FSharp.Core)){1}([^\}]*)\}", ".assembly extern FSharp.Core { }" ]
|
|
|
|
let unifyImageBase ilCode = replace ilCode ("\.imagebase\s*0x\d*", ".imagebase {value}")
|
|
|
|
let unifyingAssemblyNames (text: string) =
|
|
match assemblyName with
|
|
| Some name -> text.Replace(name, "assembly")
|
|
| None -> text
|
|
|> unifyRuntimeAssemblyName
|
|
|> unifyImageBase
|
|
|
|
ilCode.Trim() |> stripComments |> unifyingAssemblyNames
|
|
|
|
|
|
let private generateIlFile dllFilePath ildasmArgs =
|
|
let ilFilePath = Path.ChangeExtension(dllFilePath, ".il")
|
|
|
|
let ildasmPath = config.ILDASM
|
|
|
|
let ildasmFullArgs = [ dllFilePath; $"-out=%s{ilFilePath}"; yield! ildasmArgs ]
|
|
|
|
let stdErr, exitCode =
|
|
let ildasmCommandPath = Path.ChangeExtension(dllFilePath, ".ildasmCommandPath")
|
|
File.WriteAllLines(ildasmCommandPath, [| $"{ildasmPath} {ildasmFullArgs}" |] )
|
|
exec ildasmPath ildasmFullArgs
|
|
|
|
if exitCode <> 0 then
|
|
failwith $"ILASM Expected exit code \"0\", got \"%d{exitCode}\"\nSTDERR: %s{stdErr}"
|
|
|
|
if not (String.IsNullOrWhiteSpace stdErr) then
|
|
failwith $"ILASM Stderr is not empty:\n %s{stdErr}"
|
|
|
|
ilFilePath
|
|
|
|
let private generateIL (dllFilePath: string) ildasmArgs =
|
|
let assemblyName = Some (Path.GetFileNameWithoutExtension dllFilePath)
|
|
let ilFilePath = generateIlFile dllFilePath ildasmArgs
|
|
let normalizedText = normalizeILText assemblyName (File.ReadAllText(ilFilePath))
|
|
File.WriteAllText(ilFilePath, normalizedText)
|
|
normalizedText
|
|
|
|
let private compareIL assemblyName (actualIL: string) expectedIL =
|
|
|
|
let mutable errorMsgOpt = None
|
|
|
|
let prepareLines (s: string) =
|
|
s.Split('\n')
|
|
|> Array.map(fun e -> e.Trim('\r'))
|
|
|> Array.skipWhile(String.IsNullOrWhiteSpace)
|
|
|> Array.rev
|
|
|> Array.skipWhile(String.IsNullOrWhiteSpace)
|
|
|> Array.rev
|
|
|
|
expectedIL
|
|
|> List.map (fun (ilCode: string) -> ilCode.Trim())
|
|
|> List.iter (fun (ilCode: string) ->
|
|
let expectedLines = ilCode |> normalizeILText (Some assemblyName) |> prepareLines
|
|
|
|
if expectedLines.Length = 0 then
|
|
errorMsgOpt <- Some("ExpectedLines length invalid: 0")
|
|
else
|
|
let startIndex =
|
|
let index = actualIL.IndexOf(expectedLines[0].Trim())
|
|
if index > 0 then
|
|
index
|
|
else
|
|
0
|
|
let actualLines = actualIL.Substring(startIndex) |> prepareLines
|
|
|
|
let errors = ResizeArray()
|
|
if actualLines.Length < expectedLines.Length then
|
|
let msg = $"\nExpected at least %d{expectedLines.Length} lines but found only %d{actualLines.Length}\n"
|
|
errorMsgOpt <- Some(msg + "\nExpected:\n" + ilCode + "\n")
|
|
else
|
|
for i = 0 to expectedLines.Length - 1 do
|
|
let expected = expectedLines[i].Trim()
|
|
let actual = actualLines[i].Trim()
|
|
if expected <> actual then
|
|
errors.Add $"\n==\nName: '%s{actualLines[0]}'\n\nExpected:\t %s{expected}\nActual:\t\t %s{actual}\n=="
|
|
|
|
if errors.Count > 0 then
|
|
let msg = String.concat "\n" errors + "\n\n\Expected:\n" + ilCode + "\n"
|
|
errorMsgOpt <- Some(msg + "\n\n\nActual:\n" + String.Join("\n", actualLines, 0, expectedLines.Length))
|
|
)
|
|
|
|
if expectedIL.Length = 0 then
|
|
errorMsgOpt <- Some ("No Expected IL")
|
|
|
|
match errorMsgOpt with
|
|
| Some(msg) -> errorMsgOpt <- Some(msg + "\n\n\nEntire actual:\n" + actualIL)
|
|
| _ -> ()
|
|
|
|
match errorMsgOpt with
|
|
| Some(errorMsg) -> (false, errorMsg, actualIL)
|
|
| _ -> (true, String.Empty, String.Empty)
|
|
|
|
let private checkILPrim ildasmArgs dllFilePath =
|
|
let actualIL = generateIL dllFilePath ildasmArgs
|
|
compareIL (Path.GetFileNameWithoutExtension dllFilePath) actualIL
|
|
|
|
let private checkILAux ildasmArgs dllFilePath expectedIL =
|
|
let (success, errorMsg, _) = checkILPrim ildasmArgs dllFilePath expectedIL
|
|
if not success then
|
|
Assert.Fail(errorMsg)
|
|
|
|
// This doesn't work because the '/linenum' is being ignored by
|
|
// the version of ILDASM we are using, which we acquire from a nuget package
|
|
//let checkILWithDebugPoints dllFilePath expectedIL =
|
|
// checkILAux [ "/linenum" ] dllFilePath expectedIL
|
|
|
|
let checkIL dllFilePath expectedIL =
|
|
checkILAux [] dllFilePath expectedIL
|
|
|
|
let verifyIL (dllFilePath: string) (expectedIL: string) =
|
|
checkIL dllFilePath [expectedIL]
|
|
|
|
let verifyILAndReturnActual (dllFilePath: string) (expectedIL: string) =
|
|
checkILPrim [] dllFilePath [expectedIL]
|
|
|
|
let checkILNotPresent dllFilePath unexpectedIL =
|
|
let actualIL = generateIL dllFilePath []
|
|
if unexpectedIL = [] then
|
|
Assert.Fail $"No unexpected IL given. This is actual IL: \n{actualIL}"
|
|
let errors =
|
|
unexpectedIL
|
|
|> Seq.map (normalizeILText None)
|
|
|> Seq.filter actualIL.Contains
|
|
|> Seq.map (sprintf "Found in actual IL: '%s'")
|
|
|> String.concat "\n"
|
|
if errors <> "" then
|
|
Assert.Fail $"{errors}\n\n\nEntire actual:\n{actualIL}"
|
|
|
|
let reassembleIL ilFilePath dllFilePath =
|
|
let ilasmPath = config.ILASM
|
|
let errors, _ = exec ilasmPath [ $"%s{ilFilePath} /output=%s{dllFilePath} /dll" ]
|
|
errors
|