# `Mutare.Coverage.Recorder`
[🔗](https://github.com/foxbenjaminfox/mutare/blob/v0.5.1/lib/mutare/coverage/recorder.ex#L1)

The generated-code side of coverage capture: the contract the metamutant and
the test bootstrap share to record coverage **synchronously, in the test
process**, with no race.

`Mutare.Coverage` reads what this produces; this module defines *how it is
produced*. There are three pieces, all emitted into generated code:

  * `record_ast/1` — spliced by `Mutare.Transform` into every selector
    catch-all. At baseline, under a tracking flag, it records the site's mutant
    ids into shared ETS — in whatever process runs the line, including the test
    process. (`Mutare.Selector` defines the *selection* contract the same way.)
  * `helper_source/0` — a dependency-free helper module
    (`Mutare.Sandbox` writes it into the sandbox) holding the ETS writes and the
    end-of-suite dump, so the per-site code stays a single call and the
    OTP-version-tolerant label read lives in one place.
  * Lifecycle ASTs — `mode_ast/1` initializes probe mode in the `mix.exs`
    prefix, before target project/config/application code. `tables_ast/1`
    creates tables and enables recording before user `test_helper.exs` code.
    `attribution_ast/1` calls the coverage-attribution extensions after the
    user's helper, and `after_suite_ast/0` registers the dump after the helper
    starts ExUnit. Both scoped ASTs take `:harness` or `:fixture` explicitly — there is no default,
    because a bootstrap's scope is a property of where it is written, not of the
    environment of the process that rendered it.

Probe mode (`:mutare_probe`) is immutable after initialization. Recording
readiness (`:mutare_track`) becomes true only after table setup. They have
different lifetimes: a function or closure can span table setup, so only mode
is a candidate for hoisting. Keeping readiness in the per-site gate also keeps
early target code from calling the helper before it is loadable. The helper
checks table existence again for direct calls and fixture table replacement.

A process that selects on a private key (`Mutare.Selector.process_key/0`, which
`Mutare.Test.isolate_selector/0` installs) also records under a private readiness
key derived from it (`track_key/0`): the two switches have the same scope and are
baked into a metamutant together, so an `async: true` test module that turns
recording on reaches only its own metamutants.

A process with no lineage link to a test — a web server's request process in a
browser-driven test — can declare the pid it works for under the descriptor's
`anchor_key` (`Mutare.CoverageAttribution.attribute_to/1` writes the harness key,
typically from hooks an extension installs through `attribution_ast/1`). The
helper resolves that pid as the declaring process would resolve itself, before
`$callers` and `$ancestors`, and guards its memo with the declaration, so a
process re-declared for another test is attributed afresh.

The compiled `HelperTemplate` uses private fixture tables, process-dictionary
caches and dump variables. Under self-hosting, the existing helper-name override
also selects the private recording key for fixture metamutants; generated harness
bootstraps always use the harness descriptor. Fixture setup cannot alter the
outer probe, and its table-owning tests can run during that probe.

## Why this, not `:cover`

`:cover`'s counters live in a single global table keyed `{module, line}` — there
is no per-process partition, so attributing coverage to a test in *one* run
needs a snapshot at each test boundary, and the only global per-test signal
ExUnit emits is an **async cast to formatters** that races test execution
(fast `async: false` modules' coverage is lost). The metamutant, by contrast,
*is* code we generate and that runs synchronously in the test process. So the
capture lives there, accumulate-only (never reset), keyed by mutant id directly.

## The gate (why it is inert outside the probe)

The spliced expression (`record_ast/3`) is two nested short-circuits:

    case (case :erlang."=:="(mutare_active, 0) do
            true -> :persistent_term.get(:mutare_track, false)
            _ -> false
          end) do
      true -> <helper>.hit([<ids>])
      _ -> false
    end

  * per-mutant runs (a local id in the selected file, `:inactive` elsewhere)
    short-circuit on the comparison with zero —
    ~zero hot-loop cost;
  * the baseline green run and Mutare's own unit tests (recording disabled)
    short-circuit on the persistent-term read — the helper is never *called*;
  * only the probe run (`MUTARE_COVERAGE` set → `:mutare_track` true,
    `mutare_active == 0`) records.

They are `case`s, not `and`s: the record is spliced into the target's own modules,
where a narrowed or replaced `Kernel` import would redefine `and` under it, and
`:erlang.andalso` — the form `and` compiles to in a guard — is undefined in a body.
A special form is the one thing no import can redirect. The comparison is an explicit
`:erlang` call for the same reason, and neither `case` can read as a hoisted
selector (`case mutare_active do`).

Schema emission supplies a namespace to `record_ast/3`, producing
`hit(namespace, local_ids)`. The helper keeps a seen-cache per namespace and
qualifies an id as `{namespace, local_id}` only when recording it to ETS, so
local id 1 in two files stays two hits; the dump groups local ids by namespace.
`Mutare.Coverage` flattens them and maps them back to the current run's report
ids before test selection.

# `after_suite_ast`

```elixir
@spec after_suite_ast() :: Macro.t()
```

After-suite AST appended after the target's test helper.

`ExUnit.after_suite/1` requires ExUnit to be started, so the dump callback is
registered after the user's helper even though coverage tracking starts before
it.

# `attribution_ast`

```elixir
@spec attribution_ast([{module(), keyword()}]) :: Macro.t() | nil
```

Attach each coverage-attribution extension's hooks in the probe's test VM.

`attachments` pairs each extension module exporting
`c:Mutare.CoverageAttribution.attach_attribution/1` with its options, in
`:extensions` order. Appended after the target's test helper, where the project's
applications and whatever the helper starts are running, and gated on probe mode,
so no other run calls an extension. An empty list renders nothing.

Each call must return `:ok`; anything else, or a raise, fails the probe run, as
`c:Mutare.CoverageAttribution.attach_attribution/1` documents.

# `catch_all_pattern`

```elixir
@spec catch_all_pattern(atom()) :: Macro.t()
```

Catch-all clause pattern that binds the selector subject to `var`.

`Mutare.Transform` uses this instead of `_` so `record_ast/2` can read the
active id without a second `:persistent_term` lookup. Pass the same per-file
variable to this function and to `record_ast/2`.

# `dump_file`

```elixir
@spec dump_file() :: String.t()
```

The file (relative to the sandbox) the end-of-suite dump is written to.

# `dump_path_env`

```elixir
@spec dump_path_env() :: String.t()
```

Env var the probe sets to the absolute path the dump is written to.

# `env_var`

```elixir
@spec env_var() :: String.t()
```

Env var the probe sets to turn coverage recording on for one run.

# `fixture_module`

```elixir
@spec fixture_module() :: module()
```

The helper used by emitted fixture code and Mutare's `test/support/mutare_cov.ex` stand-in.

`helper_module/0` (`:mutare_cov`) unless `fixture_override_env/0` names another —
the one knob self-hosting needs so the stand-in does not collide with the real
helper the sandbox writes (see the constant's comment above).

# `fixture_override_env`

```elixir
@spec fixture_override_env() :: String.t()
```

Env var a sandbox run sets to give the suite-under-test's stand-in a private module name.

# `gate_ast`

```elixir
@spec gate_ast(atom()) :: Macro.t()
```

The coverage gate, independent of the helper payload and its recognition.

# `helper_module`

```elixir
@spec helper_module() :: module()
```

The dependency-free helper module emitted into the sandbox.

# `helper_source`

```elixir
@spec helper_source() :: String.t()
```

Source for the dependency-free coverage helper written into the sandbox.

The source comes from `Mutare.Coverage.HelperTemplate`, a normal
compile-checked module. Only its `defmodule` line is rewritten to
`helper_module/0` (`:mutare_cov`).

The helper's `hit/1` writes coverage to shared ETS tables. Its `dump/1`
callback, registered with `ExUnit.after_suite/1`, writes `dump_file/0` and
maps each test module back to its source file.

# `hit_ast`

```elixir
@spec hit_ast([pos_integer()], String.t() | nil) :: Macro.t()
```

The helper call carrying this record's literal runtime ids.

# `mode_ast`

```elixir
@spec mode_ast(:harness | :fixture) :: Macro.t()
```

Initialize immutable probe mode before target project code. Repeated project or
umbrella helper evaluation leaves the initialized value intact. Harness code
bakes the harness key; in-process fixture code uses its isolated runtime.
No helper module or ETS table needs to exist yet.

# `no_warn_attr_ast`

```elixir
@spec no_warn_attr_ast(String.t() | nil) :: Macro.t()
```

AST for the `@compile {:no_warn_undefined, {<helper>, :hit, 1}}` attribute.

`Mutare.Transform` prepends this to every metamutant module body. Selector
catch-alls call the generated coverage helper's `hit/1`; in an umbrella that
helper can live in a generated sibling app, so the mutated app may compile before
the helper and trigger a benign xref warning. The call resolves at runtime. This
attribute suppresses only that compile-time warning and is harmless when the
helper is compiled in the same app.

# `record?`

```elixir
@spec record?(Macro.t()) :: boolean()
```

Recognise a gated coverage payload, independently of how its gate is evaluated.

The outer short-circuit `case` and the helper's literal positive-id payload
are the record contract. The condition may read tracking inline or use a bound
boolean. The tuple-selector reader separately verifies the selector identity and
the input/output variables; a coverage record alone never identifies a selector.
Both raw and literal-encoded reparsed ASTs are accepted.

# `record_ast`

```elixir
@spec record_ast([pos_integer()], atom(), String.t() | nil) :: Macro.t()
```

AST that records coverage for this selector's mutant ids.

`Mutare.Transform` prepends this expression to a selector catch-all body. It
records only when the selector is at baseline and the coverage probe has enabled
tracking; see the moduledoc for the full gate.

`var` must be the variable introduced by `catch_all_pattern/1`. The AST is
built directly so it can share that binding and splice `ids` as a literal list
of integers.

`namespace` selects the helper's `hit/2` form; `nil` retains standalone `hit/1`.

Literals use clean metadata (`Mutare.AST.literal/1`); a bare integer can render badly
when this expression is emitted as a statement in a generated function body.

The comparison is an explicit `:erlang` call and both short-circuits use `case`,
so target imports cannot redefine them and no guard-only `:erlang.andalso` call
appears in a body. `record?/1` recognises the outer case and helper payload
without depending on the gate's implementation.

# `recorded_ids`

```elixir
@spec recorded_ids(Macro.t()) :: {:ok, [pos_integer()]} | :error
```

The runtime ids a gated coverage payload records, or `:error` for any other node.

The reader half of `hit_ast/2`, under the same recognition as `record?/1`: a namespaced
payload yields its local ids (the namespace is the file's, which the caller already knows).
`Mutare.Manifest` reads it to check that every emitted mutant has a record.

# `root_env`

```elixir
@spec root_env() :: String.t()
```

Env var the probe sets to the absolute root test-file paths are relative to.

# `runtime`

```elixir
@spec runtime(:harness | :fixture) :: map()
```

Capture state for one scope: `:harness` is what a generated sandbox bootstrap and
the written `:mutare_cov` helper use; `:fixture` is the disjoint set the compiled
`HelperTemplate` and Mutare's own in-process fixtures use, so fixture capture can
run inside an outer dogfood probe without touching it.

# `suite_fixture_module`

```elixir
@spec suite_fixture_module() :: String.t()
```

The private stand-in module name (`fixture_override_env/0`'s value) used under dogfooding.

# `tables_ast`

```elixir
@spec tables_ast(:harness | :fixture) :: Macro.t()
```

Create capture tables in the test-helper process, which owns them through the
suite. Mode is already initialized; this phase never changes it. Umbrella helpers
share one set of tables. Recording readiness is enabled only after creation.
The helper also checks table existence dynamically for direct calls.

# `track_key`

```elixir
@spec track_key() :: atom()
```

The recording-readiness key a *spliced record* reads.

A process holding a private selection key (`Mutare.Selector.process_key/0`) gets a
private readiness key derived from it, so the test module execution that selects on its
own key also turns recording on and off for its own metamutants alone. Only a test holds
one; the harness, `Mutare.Schema`'s workers and a sandbox run fall through to the rule
below.

Otherwise this follows `fixture_module/0` rather than taking a scope, because it must
track whatever the emitter is emitting: a transform run in the harness bakes the
harness key into the metamutant, and a transform run by the suite-under-test (where
`fixture_override_env/0` is set) bakes the fixture key into its fixtures. The
lifecycle ASTs are the other half of that contract and take their scope
**explicitly** (`mode_ast/1`, `tables_ast/1`) — they are written into generated
bootstraps, where the ambient override need not match the VM that will run them.
Don't pair this function with a lifecycle AST; reach for `runtime/1` instead, whose
scope you have then named.

# `var_name`

```elixir
@spec var_name() :: atom()
```

The canonical selector-subject variable name (`:mutare_active`); the default `var`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
