Muex.Coverage (Muex v0.8.1)

View Source

A line-level coverage index: which test files execute which source lines.

Used to run, for each mutant, only the tests that actually exercise the mutated line — and to skip mutants on lines no test covers (:no_coverage) rather than wasting a full test run on a mutant nothing can catch.

The index is a plain map (%{file => %{line => MapSet of test files}}); build it with new/0 + put/4 and query it with tests_for/3 / covered?/3.

Summary

Functions

Builds a coverage index by running each test file under :cover and recording which source lines it executes.

Whether any test covers file:line.

Extracts the executed line numbers from a :cover.analyse(_, :calls, :line) result, i.e. the lines with a non-zero call count.

Returns an empty index.

Records that test_file executes line of file.

Records that line of file is executable (even if no test runs it).

Records that test_file executes every line in lines of file.

Returns the coverage status of file:line

Types

t()

@type t() :: %{required(Path.t()) => %{required(pos_integer()) => MapSet.t(Path.t())}}

Functions

collect(test_files, file_to_module, opts \\ [])

@spec collect([Path.t()], %{required(Path.t()) => module()}, keyword()) :: t()

Builds a coverage index by running each test file under :cover and recording which source lines it executes.

For every file in test_files, runs mix test <file> --cover --export-coverage in a subprocess, then analyses each module in file_to_module and attributes its executed lines to that test file. Returns a t() index.

Options: :cd (project root, default File.cwd!/0).

covered?(index, file, line)

@spec covered?(t(), Path.t(), pos_integer()) :: boolean()

Whether any test covers file:line.

covered_lines(line_analysis)

@spec covered_lines([{{module(), pos_integer()}, non_neg_integer()}]) :: [
  pos_integer()
]

Extracts the executed line numbers from a :cover.analyse(_, :calls, :line) result, i.e. the lines with a non-zero call count.

new()

@spec new() :: t()

Returns an empty index.

put(index, file, line, test_file)

@spec put(t(), Path.t(), pos_integer(), Path.t()) :: t()

Records that test_file executes line of file.

put_executable(index, file, line)

@spec put_executable(t(), Path.t(), pos_integer()) :: t()

Records that line of file is executable (even if no test runs it).

put_lines(index, file, lines, test_file)

@spec put_lines(t(), Path.t(), [pos_integer()], Path.t()) :: t()

Records that test_file executes every line in lines of file.

tests_for(index, file, line)

@spec tests_for(t(), Path.t(), pos_integer()) ::
  {:covered, [Path.t()]} | :no_coverage | :unknown

Returns the coverage status of file:line:

  • {:covered, sorted_test_files} — at least one test executes the line;
  • :no_coverage — the line is executable but no test runs it;
  • :unknown — there is no coverage data for the line (e.g. it is not an executable line, like a defmodule/def header), so coverage can't decide and the caller should run the mutant normally.