From 79a4a9a40f9682167ac2fe38f037becbe9f48a0f Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Tue, 12 May 2026 06:20:26 +0200 Subject: [PATCH] Support .NET versions different than Mutannots --- Mutannot/Mutannot.fsproj | 4 ++ Mutannot/Program.fs | 90 +++++++++++++++++++++++++++------------- 2 files changed, 65 insertions(+), 29 deletions(-) diff --git a/Mutannot/Mutannot.fsproj b/Mutannot/Mutannot.fsproj index 3329a09..cd3d012 100644 --- a/Mutannot/Mutannot.fsproj +++ b/Mutannot/Mutannot.fsproj @@ -9,4 +9,8 @@ + + + + diff --git a/Mutannot/Program.fs b/Mutannot/Program.fs index 8342ab2..9baab14 100644 --- a/Mutannot/Program.fs +++ b/Mutannot/Program.fs @@ -1,4 +1,5 @@ open System +open System.Collections.Generic open System.Diagnostics open System.IO open System.Reflection @@ -197,50 +198,81 @@ let ensureBuilt options project assemblyPath = if not (File.Exists assemblyPath) then fail $"Compiled test assembly not found at {assemblyPath}." -let installAssemblyResolver (assemblyPath: string) = +let requireConstructorArgumentString (args: IList) 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) 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 - AppDomain.CurrentDomain.add_AssemblyResolve ( - ResolveEventHandler(fun _ args -> - let name = AssemblyName(args.Name).Name + ".dll" - let candidate = Path.Combine(assemblyDir, name) + let runtimeAssemblies = + match AppContext.GetData "TRUSTED_PLATFORM_ASSEMBLIES" with + | :? string as value when not (String.IsNullOrWhiteSpace value) -> + value.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries) + | _ -> fail "Unable to discover trusted platform assemblies for MetadataLoadContext." - if File.Exists candidate then - Assembly.LoadFrom candidate - else - null) - ) + let localAssemblies = + seq { + yield assemblyPath + 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.Assembly.GetName().Name + + new MetadataLoadContext(resolver, coreAssemblyName) let mutationCases 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() |> Array.collect (fun t -> + let declaringType = + match t.FullName with + | null -> t.Name + | name -> name + 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 + |> Seq.choose (fun attr -> + if attr.AttributeType.FullName <> "Mutannot.MutationCaseAttribute" then + None + else + let args = attr.ConstructorArguments - if args.Count <> 5 then - failwithf "Unexpected MutationCaseAttribute shape on %s.%s" t.FullName m.Name + if args.Count <> 5 then + fail $"Unexpected MutationCaseAttribute shape on {declaringType}.{m.Name}" - let declaringType = - match t.FullName with - | null -> t.Name - | name -> name - - { Id = unbox args[0].Value - File = unbox args[1].Value - Line = unbox args[2].Value - Find = unbox args[3].Value - Replace = unbox args[4].Value - TestName = m.Name - DeclaringType = declaringType }) + 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 + DeclaringType = declaringType }) |> Seq.toArray)) |> Array.sortBy (fun mutation -> mutation.Id)