diff --git a/Mutannot/Program.fs b/Mutannot/Program.fs index 98784fb..5c3a1ac 100644 --- a/Mutannot/Program.fs +++ b/Mutannot/Program.fs @@ -1,13 +1,77 @@ open System open System.IO +open System.Reflection +open System.Runtime.InteropServices 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.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 + [] let main argv = if argv.Length <> 1 then eprintfn "Usage: mutannot " exit 1 + let projectPath = argv[0] + let gitState = cli { Exec "git" @@ -20,13 +84,7 @@ let main argv = eprintfn "Uncommitted changes. Refusing to run." exit 2 - cli { - Exec "dotnet" - Arguments [ "build"; argv[0] ] - Output(new StreamWriter(Console.OpenStandardOutput())) - } - |> Command.execute - |> Output.throwIfErrored - |> ignore + for mutationCase in getMutationCases projectPath do + printfn "%s" <| mutationCase.ToString() 0