defmodule Opencensus.TestSupport.SpanCaptureReporter do @moduledoc """ An `:opencensus.reporter` to capture spans for tests. `:oc_reporter` can't unregister reporters, but `:telemetry` can detach handlers, so we configure `:opencensus` to send spans to use our reporter, in `mix.exs`: ```elixir if Mix.env() == :test do config :opencensus, send_interval_ms: 100, reporters: [{Opencensus.TestSupport.SpanCaptureReporter, []}] end ``` It'll call `:telemetry.execute/3` whenever spans are reported. If you've called `attach/0`, the handler will convert the spans to structs with `Span.from/1` and deliver them to your process inbox. To collect them, call `collect/0`. When you're finished, call `detach/0`: ```elixir defmodule NyApp.SpanCaptureTest do use ExUnit.Case, async: false alias Opencensus.TestSupport.SpanCaptureReporter setup do SpanCaptureReporter.attach() on_exit(make_ref(), &SpanCaptureReporter.detach/0) end test "can gather spans" do :ocp.with_child_span("our span name") :ocp.finish_span() [span] = SpanCaptureReporter.collect() assert span.name == "our span name" end end ``` """ alias Opencensus.Span @behaviour :oc_reporter @impl true def init([]), do: [] @impl true def report(spans, []) do :telemetry.execute([__MODULE__], %{}, %{spans: spans}) :ok end @doc false def handler([__MODULE__], %{}, %{spans: spans}, {pid, attached_monotonic}) do started_after_attach? = fn span -> span |> Span.began_monotonic() >= attached_monotonic end filtered = spans |> Enum.filter(started_after_attach?) send(pid, {:spans, filtered}) end @doc "Attach the reporter to deliver spans to your process inbox." def attach do :telemetry.attach(__MODULE__, [__MODULE__], &handler/4, {self(), :erlang.monotonic_time()}) end @doc """ Detach the reporter to stop delivering spans to your process inbox. If still attached, triggers span delivery before detaching. """ def detach do if handler_attached?(), do: trigger_and_wait_for_span_delivery() :telemetry.detach(__MODULE__) end @doc """ Collect spans from your process inbox. If still attached, triggers span delivery before collection. """ @spec collect() :: list(%Span{}) def collect do if handler_attached?(), do: trigger_and_wait_for_span_delivery() collect_span_records([]) |> Enum.map(&Span.from/1) end defp trigger_and_wait_for_span_delivery do send(:oc_reporter, :report_spans) :timer.sleep(1) end defp handler_attached? do :telemetry.list_handlers([__MODULE__]) != [] end defp collect_span_records(acc) when is_list(acc) do receive do {:spans, spans} -> collect_span_records(acc ++ spans) after 1 -> acc end end end