@@ -685,4 +685,33 @@ module TopLevelDefaults =
685685 /// Path to the f# file the test is defined in
686686 type SourceFilePath = string
687687 type TestLocator = System.Reflection.Assembly -> SourceFilePath -> FlatTest -> SourceLocation option
688- let mutable testLocator : TestLocator = Expecto.Impl.CodeLocation.testLocator
688+ let mutable testLocator : TestLocator = Expecto.Impl.CodeLocation.testLocator
689+
690+ /// Mark the custom TestLocator so the test adapter (i.e. YoloDev.Expecto.TestSdk) can find and use different testLocator implementations
691+ [<AttributeUsage( AttributeTargets.Method ||| AttributeTargets.Property ||| AttributeTargets.Field) >]
692+ type TestLocatorAttribute () = inherit Attribute()
693+
694+ module TestLocatorAttribute =
695+ open System.Reflection
696+
697+ let private methodInfoToTestLocator ( mi : MethodInfo ) : TopLevelDefaults.TestLocator =
698+ ( fun ( assembly : Assembly ) ( sourceFilePath : string ) ( test : FlatTest ) ->
699+ mi.Invoke( null , [| box assembly; box sourceFilePath; box test|]) |> unbox
700+ )
701+
702+ let tryFindLocatorInSingleAssembly ( assembly : Assembly ) : TopLevelDefaults.TestLocator option =
703+ assembly
704+ |> _. GetExportedTypes()
705+ |> Array.collect ( fun t -> t.GetMethods( BindingFlags.Public ||| BindingFlags.Static))
706+ |> Array.filter ( fun m -> m.GetCustomAttributes< TestLocatorAttribute>() |> ( not << Seq.isEmpty))
707+ |> Array.tryHead
708+ |> Option.map methodInfoToTestLocator
709+
710+ let tryFindTestLocator ( assembly : Assembly ) : TopLevelDefaults.TestLocator option =
711+ // Prioritize a test locator found in the top assembly
712+ tryFindLocatorInSingleAssembly assembly
713+ |> Option.orElseWith ( fun () ->
714+ // otherwise, look for it in referenced assemblies
715+ assembly.GetReferencedAssemblies()
716+ |> Array.tryPick ( Assembly.Load >> tryFindLocatorInSingleAssembly)
717+ )
0 commit comments