mix test.qlover (qlover v0.1.2)

Copy Markdown View Source

Runs the test suite with incremental line-coverage gating in one command:

mix test.qlover

Plain mix test is left untouched, so single-file runs and ad-hoc flags keep working exactly as before. mix test.qlover decides by itself:

  • Baseline exists and non-test gate inputs (priv/repo/, config/, mix.exs, mix.lock) match: runs exactly the affected test files with coverage, then gates (mix test --no-stale <files> --cover --export-coverage .qlover_fresh + mix qlover). Test-only edits stay incremental through per-test attribution.
  • Otherwise — first run, missing/invalid baseline, or changed non-test inputs — runs the full suite with coverage and snapshots a new baseline (mix test --no-stale --cover + mix qlover --write-baseline). An info message says which path was taken.

There is no stale manifest involved: the file list comes from qlover's own content-keyed reference graph, so selection is deterministic across worktrees and machines, and a hostile host test alias injecting --stale cannot shrink it (--no-stale is passed first, which wins the duplicate-flag resolution).

Extra arguments are appended to the selection (mix test.qlover --seed 0); extra file arguments widen it, which stays sound because all coverage still comes from one code version. The flags --stale, --no-stale, --cover, --no-cover, --export-coverage, --failed, --partitions, --dry-run, --no-compile are managed by the task and rejected when passed explicitly.

Path overrides (mirroring mix qlover):

  • --baseline PATH - baseline file (default: cover/.qlover_baseline)
  • --export PATH - scratch export (default: cover/.qlover_fresh.coverdata); custom paths must live under the test_coverage output dir with a .coverdata suffix.
  • --expansion-export PATH - second scratch export accepted by the gate (default: cover/.qlover_expansion.coverdata).

Test failures abort before gating and never update the baseline. Scratch exports are deleted after gating (and any leftovers removed before each run), so a later mix test.coverage never unions a stale partial into a full report. When test files change but reference data is available, only the affected tests rerun in the focused run instead of the full suite (see Mix.Tasks.Qlover).

Each invocation reports qlover: ran X tests; didn't run Y tests. Counts come from ExUnit (including generated tests and doctests); unchanged files reuse counts stored in the baseline and shared cache. ExUnit skips and exclusions are included in Y and reported separately. Failed runs also print the summary. If a run aborts without reporting counts, unknown values are labelled rather than estimated. Older baselines receive one full run to collect counts.

Coverage percentages are rounded down to two decimals so incomplete coverage never displays as 100.00%. Threshold checks use exact counts.

The --qlover flag spelling

Mix does not let dependencies add flags to mix test, but the same spelling is one alias away in your own mix.exs:

defp aliases do
  [
    test: fn
      args ->
        if "--qlover" in args do
          Mix.Task.run("test.qlover", args -- ["--qlover"])
        else
          Mix.Tasks.Test.run(args)
        end
    end
  ]
end

With that, mix test --qlover gates while bare mix test (including mix test test/foo_test.exs) stays vanilla.