defmodule ActorSimulation.GeneratorMetadata do @moduledoc """ Utilities for tracking which test file/function generated an HTML report. This helps maintain traceability between generated reports and their source code. """ @doc """ Extracts generator metadata from the current stack trace. Returns a map with `:test_file`, `:test_name`, and `:generator_function` keys, or `nil` if not called from a test. ## Example metadata = GeneratorMetadata.from_stacktrace() # => %{ # test_file: "test/mermaid_report_test.exs", # test_name: "generates pipeline report", # generator_function: "ActorSimulation.MermaidReportGenerator.generate_report/2" # } """ def from_stacktrace do # Get the stack trace stacktrace = Process.info(self(), :current_stacktrace) |> elem(1) # Find test info and generator function from stacktrace test_info = find_test_info(stacktrace) generator_function = find_generator_function(stacktrace) if test_info do Map.put(test_info, :generator_function, generator_function) else nil end end @doc """ Generates an HTML comment with generator metadata. ## Example comment = GeneratorMetadata.to_html_comment(metadata) # => "\\n" """ def to_html_comment(metadata) when is_map(metadata) do generator_line = if metadata[:generator_function] do " Generator: #{metadata.generator_function}\n" else "" end """ """ end def to_html_comment(nil), do: "" # Private functions defp find_test_info(stacktrace) do stacktrace |> Enum.find_value(fn {_module, function, _arity, location} -> # Match ExUnit test functions if is_atom(function) && String.starts_with?(Atom.to_string(function), "test ") do test_file = Keyword.get(location, :file, "unknown") test_name = function |> Atom.to_string() |> String.replace_prefix("test ", "") %{test_file: test_file, test_name: test_name} else nil end end) end defp find_generator_function(stacktrace) do # Look for known generator modules in the stacktrace generator_modules = [ ActorSimulation.MermaidReportGenerator, ActorSimulation.OMNeTPPGenerator, ActorSimulation.CAFGenerator, ActorSimulation.PonyGenerator, ActorSimulation.PhonyGenerator, ActorSimulation.VlingoGenerator ] stacktrace |> Enum.find_value(fn {module, function, arity, _location} -> if module in generator_modules do "#{inspect(module)}.#{function}/#{arity}" else nil end end) || "unknown" end end