Mutare.CoverageAttribution behaviour (mutare v0.5.1)

Copy Markdown View Source

Capability behaviour for telling Mutare which test a process is working for.

Mutare runs each mutant against only the tests that executed its line. The coverage probe finds the test from the process the line ran in: the test process itself, or a process it started, through a Task's $callers or a process's $ancestors. A process with none of those links to a test records coverage no test owns, and each mutant it covers runs the whole suite. A web server's request process in a browser-driven test (Wallaby, Hound, Playwright) is the usual case: the test's browser sent the request, but nothing the probe can read says so.

A library that does know, because the request carries something the test put there, can say so with attribute_to/1. An extension implementing this behaviour installs whatever makes that call — typically :telemetry handlers on events the library already emits — from attach_attribution/1, and is listed under :extensions:

# .mutare.exs
[extensions: [Mutare.Phoenix.Ecto]]

Mutare calls attach_attribution/1 in the coverage probe's test VM, after the project's own test_helper.exs has run and before any test, and nowhere else: a plain mix test, the baseline run, and the per-mutant runs never call it.

The extension module is loaded in the run that reads .mutare.exs and in the probe's test VM alike, so it belongs in a dependency available to both (only: [:dev, :test]), not in the project's test/support.

Summary

Callbacks

Installs the hooks that declare owners with attribute_to/1, then returns :ok.

Functions

Declares that the calling process is doing work for owner, so the coverage it records is attributed to the test owner belongs to. nil withdraws the declaration.

Callbacks

attach_attribution(opts)

@callback attach_attribution(opts :: keyword()) :: :ok

Installs the hooks that declare owners with attribute_to/1, then returns :ok.

Called with the options of the extension's {module, opts} entry ([] for a bare module), so options must be plain data: atoms, numbers, strings, and lists, tuples and maps of them. An umbrella evaluates each app's test helper in one VM, so the call may come once per app; a second call must leave the hooks installed once. A raise, or a return other than :ok, fails the probe run, which Mutare then retries once before degrading to running every mutant against the whole suite.

Functions

attribute_to(owner)

@spec attribute_to(pid() | nil) :: :ok

Declares that the calling process is doing work for owner, so the coverage it records is attributed to the test owner belongs to. nil withdraws the declaration.

owner is resolved the way the calling process would be: its own test label, else the processes it acts for (its declared owner, its $callers, its $ancestors). So it may be the test process or anything that test started — the owner process Ecto.Adapters.SQL.Sandbox.start_owner!/2 starts resolves through its $ancestors — or another process that declared an owner. The attribution holds while that test is alive; after it exits, the process's coverage counts as owned by no test again, which is the conservative side.

It lasts until the next call, so a process that works for one owner after another — a keep-alive connection serving successive requests — declares each one, and withdraws the last (nil) where it starts work no owner is known for; otherwise that work is attributed to the previous owner's test. A process it spawns resolves through it like any caller, taking the declaration in force when the spawned process first records coverage, until the test found through it exits.

Only the probe reads the declaration; everywhere else it is an unread process-dictionary entry. The claim is trusted: it is only safe when the named test can observe the work, as it can a request its own browser sent.