Mutare.Report (mutare v0.5.1)

Copy Markdown View Source

Renders mutation results for the human report.

Diffs are patched against the original source via Sourceror.patch_string at the site's recorded range, so untouched source stays byte-identical and a survivor reads as a precise source-range change. A replacement is parenthesized where its position would otherwise reread it (!(a == b) → (a == b) beside a |>, a -1 for the 0.75 of -0.75), on the assumption that binary operators are spaced as mix format writes them: in unformatted a-0, a -1 replacement is patched as a--1.

The mutation score and tallies in the report come from Mutare.Score, which also implements the CI gates; this module only renders.

Summary

Functions

One line for a partition whose environment failed the run's tests with no mutant active (Mutare.Run.BrokenPartition), e.g. partition 2 BROKEN — with no mutant active, the tests that killed mutant 12 failed: ** (RuntimeError) no database.

A -/+ diff of the lines touched by the mutation.

One line for a harness-errored mutant, including a compact diagnostic.

Header line for a surviving mutant, e.g. lib/x.ex:42:7 [relational, in-place] SURVIVED.

One line for an ignored mutant, e.g. lib/x.ex:42:7 [arithmetic] IGNORED — off-by-one is intentional.

The lines holding mutants no test ran, one line per file, with runs of consecutive lines collapsed into a range. Files keep the order of results. Returns "" when every mutant was covered.

Apply a single mutation to the original source string.

Render the whole report from results and a %{file => original_source} map.

Renders the human report with the same arity as machine reporters.

One-line tally, e.g. mutation score: 66.7% (2 killed, 1 survived, 3 total).

Full diff block (header + diff) for one survivor.

Functions

broken_partition(broken)

@spec broken_partition(Mutare.Run.BrokenPartition.t()) :: String.t()

One line for a partition whose environment failed the run's tests with no mutant active (Mutare.Run.BrokenPartition), e.g. partition 2 BROKEN — with no mutant active, the tests that killed mutant 12 failed: ** (RuntimeError) no database.

diff(site, source)

@spec diff(Mutare.Site.t(), String.t()) :: String.t()

A -/+ diff of the lines touched by the mutation.

The diff is line-based within the site's source range. Changed lines are shown as deletions and insertions; unchanged lines inside a multi-line fragment are shown as context. This keeps multi-line replacements aligned when a mutation adds or removes a line in the middle of the fragment.

harness_error(result)

@spec harness_error(Mutare.Result.t()) :: String.t()

One line for a harness-errored mutant, including a compact diagnostic.

header(site)

@spec header(Mutare.Site.t()) :: String.t()

Header line for a surviving mutant, e.g. lib/x.ex:42:7 [relational, in-place] SURVIVED.

A mutation with a note appends it as a trailing — note, matching the ignored mutant reason format.

ignored(site)

@spec ignored(Mutare.Site.t()) :: String.t()

One line for an ignored mutant, e.g. lib/x.ex:42:7 [arithmetic] IGNORED — off-by-one is intentional.

The trailing — reason is present only when the directive carried one, so a bare # mutare:ignore reads as … IGNORED with nothing after it.

no_coverage(results)

@spec no_coverage([Mutare.Result.t()]) :: String.t()

The lines holding mutants no test ran, one line per file, with runs of consecutive lines collapsed into a range. Files keep the order of results. Returns "" when every mutant was covered.

Listing each uncovered mutant would flood a large run, since a line can hold many; collapsing to lines keeps the section proportional to the untested regions. Only strictly consecutive lines merge: a line with no uncovered mutant between two that have one may be covered or outside the run's scope, so a range never spans it.

iex> results = [
...>   %Mutare.Result{status: :no_coverage, site: %Mutare.Site{file: "lib/a.ex", line: 13}},
...>   %Mutare.Result{status: :no_coverage, site: %Mutare.Site{file: "lib/a.ex", line: 12}},
...>   %Mutare.Result{status: :killed, site: %Mutare.Site{file: "lib/a.ex", line: 20}},
...>   %Mutare.Result{status: :no_coverage, site: %Mutare.Site{file: "lib/a.ex", line: 30}},
...>   %Mutare.Result{status: :no_coverage, site: %Mutare.Site{file: "lib/b.ex", line: 7}}
...> ]
iex> Mutare.Report.no_coverage(results)
"no test ran the mutants on:\n  lib/a.ex:12-13, 30\n  lib/b.ex:7"

patch(site, source)

@spec patch(Mutare.Site.t(), String.t()) :: String.t()

Apply a single mutation to the original source string.

render(results, sources)

@spec render([Mutare.Result.t()], %{optional(String.t()) => String.t()}) :: String.t()

Render the whole report from results and a %{file => original_source} map.

The report lists each survivor as a diff, then the lines holding mutants no test ran (no_coverage/1), then ignored mutants and harness errors, one per line, then the partitions whose kills may be false, and ends with the summary/2 tally.

render(results, sources, opts)

@spec render([Mutare.Result.t()], %{optional(String.t()) => String.t()}, keyword()) ::
  String.t()

Renders the human report with the same arity as machine reporters.

opts[:scope] is the summary/2 scope note, and opts[:broken_partitions] (Mutare.Run's) the partitions whose kills may be false; the other options are ignored (score gating is handled by the caller).

summary(results, scope \\ nil)

@spec summary([Mutare.Result.t()], String.t() | nil) :: String.t()

One-line tally, e.g. mutation score: 66.7% (2 killed, 1 survived, 3 total).

A run that tested only part of the project (--since, --line, …) passes the flags that chose that part as scope, appended as a trailing — scoped by …: its score is over those mutants alone, not the project's.

iex> Mutare.Report.summary([
...>   %Mutare.Result{status: :killed},
...>   %Mutare.Result{status: :survived}
...> ])
"mutation score: 50.0%  (1 killed, 1 survived, 2 total)"

iex> Mutare.Report.summary([%Mutare.Result{status: :killed}], "--since main")
"mutation score: 100.0%  (1 killed, 0 survived, 1 total)  — scoped by --since main"

survivor(site, source)

@spec survivor(Mutare.Site.t(), String.t()) :: String.t()

Full diff block (header + diff) for one survivor.