Support .NET versions different than Mutannots

This commit is contained in:
Sven van Heugten 2026-05-12 06:20:26 +02:00
parent 5b7150d297
commit 79a4a9a40f
No known key found for this signature in database
GPG key ID: D612F88666F4F660
2 changed files with 65 additions and 29 deletions

View file

@ -9,4 +9,8 @@
<ItemGroup> <ItemGroup>
<Compile Include="Program.fs" /> <Compile Include="Program.fs" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="9.0.1" />
</ItemGroup>
</Project> </Project>

View file

@ -1,4 +1,5 @@
open System open System
open System.Collections.Generic
open System.Diagnostics open System.Diagnostics
open System.IO open System.IO
open System.Reflection open System.Reflection
@ -197,48 +198,79 @@ let ensureBuilt options project assemblyPath =
if not (File.Exists assemblyPath) then if not (File.Exists assemblyPath) then
fail $"Compiled test assembly not found at {assemblyPath}." fail $"Compiled test assembly not found at {assemblyPath}."
let installAssemblyResolver (assemblyPath: string) = let requireConstructorArgumentString (args: IList<CustomAttributeTypedArgument>) index name =
match args[index].Value with
| :? string as value when not (isNull value) -> value
| null -> fail $"MutationCaseAttribute constructor argument '{name}' must not be null."
| value ->
fail
$"MutationCaseAttribute constructor argument '{name}' had unexpected type '{value.GetType().FullName}'."
let requireConstructorArgumentInt32 (args: IList<CustomAttributeTypedArgument>) index name =
match args[index].Value with
| :? int as value -> value
| null -> fail $"MutationCaseAttribute constructor argument '{name}' must not be null."
| value ->
fail
$"MutationCaseAttribute constructor argument '{name}' had unexpected type '{value.GetType().FullName}'."
let metadataLoadContextPaths (assemblyPath: string) =
let assemblyDir = Path.GetDirectoryName assemblyPath let assemblyDir = Path.GetDirectoryName assemblyPath
AppDomain.CurrentDomain.add_AssemblyResolve ( let runtimeAssemblies =
ResolveEventHandler(fun _ args -> match AppContext.GetData "TRUSTED_PLATFORM_ASSEMBLIES" with
let name = AssemblyName(args.Name).Name + ".dll" | :? string as value when not (String.IsNullOrWhiteSpace value) ->
let candidate = Path.Combine(assemblyDir, name) value.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries)
| _ -> fail "Unable to discover trusted platform assemblies for MetadataLoadContext."
if File.Exists candidate then let localAssemblies =
Assembly.LoadFrom candidate seq {
else yield assemblyPath
null) yield! Directory.EnumerateFiles(assemblyDir, "*.dll")
) yield! Directory.EnumerateFiles(assemblyDir, "*.exe")
}
Seq.append runtimeAssemblies localAssemblies
|> Seq.distinct
|> Seq.toArray
let createMetadataLoadContext (assemblyPath: string) =
let resolver = PathAssemblyResolver(metadataLoadContextPaths assemblyPath)
let coreAssemblyName = typeof<obj>.Assembly.GetName().Name
new MetadataLoadContext(resolver, coreAssemblyName)
let mutationCases options project assemblyPath = let mutationCases options project assemblyPath =
ensureBuilt options project assemblyPath ensureBuilt options project assemblyPath
installAssemblyResolver assemblyPath
let asm = Assembly.LoadFrom assemblyPath use mlc = createMetadataLoadContext assemblyPath
let asm = mlc.LoadFromAssemblyPath assemblyPath
asm.GetTypes() asm.GetTypes()
|> Array.collect (fun t -> |> Array.collect (fun t ->
t.GetMethods(BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.Static ||| BindingFlags.Instance)
|> Array.collect (fun m ->
m.GetCustomAttributesData()
|> Seq.filter (fun attr -> attr.AttributeType.FullName = "Mutannot.MutationCaseAttribute")
|> Seq.map (fun attr ->
let args = attr.ConstructorArguments
if args.Count <> 5 then
failwithf "Unexpected MutationCaseAttribute shape on %s.%s" t.FullName m.Name
let declaringType = let declaringType =
match t.FullName with match t.FullName with
| null -> t.Name | null -> t.Name
| name -> name | name -> name
{ Id = unbox<string> args[0].Value t.GetMethods(BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.Static ||| BindingFlags.Instance)
File = unbox<string> args[1].Value |> Array.collect (fun m ->
Line = unbox<int> args[2].Value m.GetCustomAttributesData()
Find = unbox<string> args[3].Value |> Seq.choose (fun attr ->
Replace = unbox<string> args[4].Value if attr.AttributeType.FullName <> "Mutannot.MutationCaseAttribute" then
None
else
let args = attr.ConstructorArguments
if args.Count <> 5 then
fail $"Unexpected MutationCaseAttribute shape on {declaringType}.{m.Name}"
Some
{ Id = requireConstructorArgumentString args 0 "id"
File = requireConstructorArgumentString args 1 "file"
Line = requireConstructorArgumentInt32 args 2 "line"
Find = requireConstructorArgumentString args 3 "find"
Replace = requireConstructorArgumentString args 4 "replace"
TestName = m.Name TestName = m.Name
DeclaringType = declaringType }) DeclaringType = declaringType })
|> Seq.toArray)) |> Seq.toArray))