Opt-in failure-only BEAM message tracing for mix test.json.
Adds a "flight recorder" to a test: while the test runs, the inter-process
send/receive messages of the test process tree are captured into a bounded
ring buffer. If the test fails, the messages (and a best-effort mailbox
snapshot of still-alive processes) are emitted into the JSON output under a
"trace" key; if it passes, the buffer is discarded. This surfaces the
message flow that led to a failure for AI consumers debugging GenServer / Port /
LiveView interactions.
Enabling
Tracing must start inside the test process (the formatter runs separately and
only sees a test once it is already finished), so it is wired via a setup
callback you add once to your shared ExUnit.Case template:
defmodule MyApp.Case do
use ExUnit.CaseTemplate
using do
quote do
setup {ExUnitJSON.Trace, :setup}
end
end
endThen activate it per test or per module with a tag:
@moduletag trace_messages: true # whole module
@tag trace_messages: true # one test
@tag trace_messages: 200 # one test, ring size 200Without the tag the setup callback is a zero-cost no-op, so it is safe to wire
globally.
What it can and cannot capture
The recorded message flow (the ring buffer, with relative timestamps) is the
reliable signal. The mailbox snapshot is best-effort and marked approx:
by the time a failing test's process dies, its own mailbox is gone, and only
processes still alive near the failure can be sampled. There is no authoritative
"what was unread when it crashed" on the BEAM — that is physically unrecoverable.
Requires OTP 27+ (for :trace dynamic sessions), already implied by the library's
use of the built-in :json module.
Summary
Functions
ExUnit setup callback. Use as setup {ExUnitJSON.Trace, :setup}.
Functions
@spec setup(map()) :: :ok
ExUnit setup callback. Use as setup {ExUnitJSON.Trace, :setup}.
Reads :trace_messages from the test context. When falsy/absent it is a no-op.
When truthy it starts a recorder tracing this test process and the ExUnit test
supervisor (so start_supervised/2 children are covered). An integer value sets
the ring-buffer size.