Scan assembly for mutations

This commit is contained in:
Sven van Heugten 2026-05-12 07:58:33 +02:00
parent 05deb1f089
commit 10ddbef963
No known key found for this signature in database
GPG key ID: D612F88666F4F660

View file

@ -1,13 +1,77 @@
open System open System
open System.IO open System.IO
open System.Reflection
open System.Runtime.InteropServices
open Fli open Fli
type MutationCase = { TestName: string; Id: string }
let ensureBuilt projectPath =
cli {
Exec "dotnet"
Arguments [ "build"; projectPath ]
Output(new StreamWriter(Console.OpenStandardOutput()))
}
|> Command.execute
|> Output.throwIfErrored
|> ignore
let getAssemblyPath projectPath =
cli {
Exec "dotnet"
Arguments [ "msbuild"; projectPath; "--getProperty:TargetPath" ]
}
|> Command.execute
|> Output.toText
let getMetadataLoadContext (assemblyPath: string) =
// This allows us to inspect assemblies regardless of the platform that they were built for
// https://learn.microsoft.com/en-us/dotnet/standard/assembly/inspect-contents-using-metadataloadcontext
let assemblyDir = Path.GetDirectoryName assemblyPath
let pathAssemblyResolver =
[ yield assemblyPath
yield! Directory.EnumerateFiles(assemblyDir, "*.dll")
yield! Directory.EnumerateFiles(assemblyDir, "*.exe")
yield! Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll") ]
|> PathAssemblyResolver
new MetadataLoadContext(pathAssemblyResolver, typeof<obj>.Assembly.GetName().Name)
let getMutationCases projectPath =
ensureBuilt projectPath
let assemblyPath = getAssemblyPath projectPath
use metadataLoadContext = getMetadataLoadContext assemblyPath
let assemblyTypes =
assemblyPath |> metadataLoadContext.LoadFromAssemblyPath |> _.GetTypes()
let assemblyMethods =
assemblyTypes
|> Seq.collect _.GetMethods(BindingFlags.Public ||| BindingFlags.Instance)
assemblyMethods
|> Seq.collect (fun m ->
m.GetCustomAttributesData()
|> Seq.choose (fun attr ->
match attr.AttributeType.FullName with
| "Mutannot.MutationCaseAttribute" ->
Some
{ TestName = $"{m.DeclaringType.FullName}.{m.Name}"
Id = attr.ConstructorArguments[0].Value :?> string }
| _ -> None))
|> Seq.toList
[<EntryPoint>] [<EntryPoint>]
let main argv = let main argv =
if argv.Length <> 1 then if argv.Length <> 1 then
eprintfn "Usage: mutannot <path/to/project.csproj|fsproj>" eprintfn "Usage: mutannot <path/to/project.csproj|fsproj>"
exit 1 exit 1
let projectPath = argv[0]
let gitState = let gitState =
cli { cli {
Exec "git" Exec "git"
@ -20,13 +84,7 @@ let main argv =
eprintfn "Uncommitted changes. Refusing to run." eprintfn "Uncommitted changes. Refusing to run."
exit 2 exit 2
cli { for mutationCase in getMutationCases projectPath do
Exec "dotnet" printfn "%s" <| mutationCase.ToString()
Arguments [ "build"; argv[0] ]
Output(new StreamWriter(Console.OpenStandardOutput()))
}
|> Command.execute
|> Output.throwIfErrored
|> ignore
0 0