mix test.qlover (qlover v0.2.2)

Copy Markdown View Source

Runs the test suite with runtime-attributed line coverage in one command:

mix test.qlover

Plain mix test is unchanged. A successful full run records executable lines and their runtime test-file owners. A test-only edit reruns the edited file, replaces its old hits, and combines them with other files' unchanged hits. Deleting a file gates directly from surviving evidence. Changed code, helpers, dependencies, and incompatible or missing evidence use a conservative refresh. Passing tests save complete attributed evidence even when coverage is incomplete; the coverage gate still fails. Failed tests never advance a baseline.

Shared setup coverage is refreshed with focused runs. File-loading hits belong to the loaded test file, including its tracked child processes. Deleting files refreshes shared setup without executing remaining tests.

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

Extra arguments are appended to focused selection (mix test.qlover --seed 0). Filtering flags cannot produce reusable coverage, and explicit files cannot restrict a full baseline. --no-stale forces a full suite run and refreshes the baseline. --dry compiles pending changes and prints the files qlover would select, with their selection reasons, without running tests or updating the coverage baseline. The flags --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 and --expansion-export PATH - legacy cover scratch export locations; attributed runs use a private, versioned report instead of .coverdata, which has no file ownership.

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.

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.