# `Mutare.Coverage.Recorder`
[🔗](https://github.com/foxbenjaminfox/mutare/blob/v0.2.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.
    `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.

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.

# `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.

# `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.

This one 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*
