mix qlover (qlover v0.1.2)

Copy Markdown View Source

Proves 100% line coverage from a stale subset plus the full-run baseline.

mix qlover --eligible
mix test --stale --cover --export-coverage .qlover_fresh
mix qlover
mix qlover --write-baseline

The baseline records beam hashes from the last green full run, a hash of the non-test gate inputs (priv/repo, config, mix.exs, mix.lock), and a snapshot of the test files (content hashes plus traced module references). A stale run satisfies the gate when the non-test inputs are unchanged: every test that references a changed beam reruns under --stale, so fresh 100% line coverage for the changed beams plus the untouched baseline equals a full run. Test-only edits gate incrementally too: changed tests re-prove the modules they could have covered (tracked via the reference snapshot) instead of forcing a full run. Anything else must fall back to mix test --no-stale --cover.

Prefer mix test.qlover, which runs the whole flow (eligible → stale → gate, else full → snapshot) in one command.

Options

The task is normally driven by the cover.sh wrapper, but the paths can be overridden for testing or custom layouts:

  • --baseline PATH - baseline file (default: cover/.qlover_baseline)
  • --export NAME - fresh export name under the cover output dir (default: cover/.qlover_fresh.coverdata)
  • --expansion-export PATH - second scratch export for the focused expansion run (default: cover/.qlover_expansion.coverdata)

A successful gate deletes the scratch exports so a later mix test.coverage never unions a stale partial into a full report.

Sharing baselines across worktrees

Every snapshot is also written through to a shared content-addressed cache, and a missing or invalid local baseline is fetched from it:

QLOVER_CACHE_DIR=~/.cache/qlover  # the default (XDG-aware)

The cache key is derived from the beam hashes, gate hash, and test hashes, so a hit means byte-identical content: the gate re-verifies the fetched baseline against the local tree exactly as if it were local, so a cache hit can never pass where a local baseline would fail. Set QLOVER_CACHE_DIR to another directory to share across checkouts, to "" to disable the cache, or keep the default for a personal cross-worktree cache. Tracer records merge the same way (per-file content keys; last-writer-wins races only ever degrade to a full run).

Test attribution

Per-test-file attribution needs reference data from the compiler tracer (Qlover.Tracer). Enable it in the host project:

# mix.exs
def project do
  [...,
   elixirc_options: [tracers: [Qlover.Tracer]],
   test_elixirc_options: [tracers: [Qlover.Tracer]]]
end

Without tracer data every test edit falls back to the full suite, exactly like before — attribution is purely additive and can never pass where the old gate would fail.