lib/telemetry_test.ex

defmodule TelemetryTest do
  @moduledoc """
  For example usage please refer to [test/examples](https://github.com/highmobility/telemetry_test/tree/main/test/examples) directory
  """

  use ExUnit.Case

  alias TelemetryTest.Server

  defguardp is_event(value) when is_list(value) and is_atom(hd(value))

  @doc """
  ExUnit setup callback function which attaches to telemetry events
  """
  @doc since: "0.1.0"
  def telemetry_listen(%{telemetry_listen: event, test: test_name}) when is_event(event) do
    attach_helper(test_name, event, self(), &__MODULE__.__send_to_self_handler__/4)
  end

  def telemetry_listen(%{telemetry_listen: {event_name, telemetry_listen_fn}, test: test_name})
      when is_function(telemetry_listen_fn) do
    test_ref = make_ref()

    attach_helper(test_name, event_name, test_ref, &__MODULE__.__push_handler__/4)

    on_exit(fn ->
      {:ok, result} = Server.pop(test_ref)
      telemetry_listen_fn.(result)
    end)
  end

  def telemetry_listen(%{telemetry_listen: {event_name, {mod, fun_name, args}}, test: test_name}) do
    test_ref = make_ref()

    attach_helper(test_name, event_name, test_ref, &__MODULE__.__push_handler__/4)

    on_exit(fn ->
      {:ok, result} = Server.pop(test_ref)
      apply(mod, fun_name, [result | args])
    end)
  end

  def telemetry_listen(%{telemetry_listen: matchers} = context) do
    for matcher <- matchers do
      new_context = Map.put(context, :telemetry_listen, matcher)
      telemetry_listen(new_context)
    end

    context
  end

  def telemetry_listen(context), do: context

  defp attach_helper(test_name, event_name, config, callback_fn) do
    handler_name = "#{test_name}-#{:rand.uniform()}-handler"

    :ok = :telemetry.attach(handler_name, event_name, callback_fn, config)

    on_exit(fn ->
      :ok = :telemetry.detach(handler_name)
    end)
  end

  @doc false
  # This function is only public to avoid a warning about optimization
  def __push_handler__(event, measurements, metadata, test_ref) do
    args = %{event: event, measurements: measurements, metadata: metadata}
    :ok = Server.push(test_ref, args)
  end

  @doc false
  # This function is only public to avoid a warning about optimization
  def __send_to_self_handler__(event, measurements, metadata, config_test_pid) do
    args = %{event: event, measurements: measurements, metadata: metadata}
    send(config_test_pid, {:telemetry_event, args})
  end
end