Mutare.Report (mutare v0.1.0)

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.

Summary

Functions

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

Human-readable failures for complete-run CI gates.

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

Fraction of launched mutant runs that ended in :harness_error.

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

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

Returns whether results meet a minimum score percentage.

Apply a single mutation to the original source string.

Format a percentage value (already on a 0..100 scale) to one decimal place, without a trailing %.

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

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

Mutation score as a percentage: killed / (total − no_coverage − ignored − poisoned − harness_error). Returns 100.0 when the denominator is zero (nothing to test).

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

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

Functions

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.

gate_failures(results, opts \\ [])

@spec gate_failures([Mutare.Result.t()], keyword() | map()) :: [String.t()]

Human-readable failures for complete-run CI gates.

These gates are separate from score semantics: :no_coverage, :poisoned, and :harness_error stay out of the mutation-score denominator, but a caller can still make them fatal for CI. opts may be a keyword list or an options map carrying:

  • :min_score — minimum mutation score percentage, or nil
  • :max_no_coverage — maximum allowed :no_coverage count, or nil
  • :fail_on_poisoned — fail if any mutant is :poisoned
  • :fail_on_harness_error — fail if any mutant is :harness_error

harness_error(result)

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

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

harness_error_rate(results)

@spec harness_error_rate([Mutare.Result.t()]) :: float()

Fraction of launched mutant runs that ended in :harness_error.

The denominator includes :killed, :survived, :timeout, :atom_exhausted, and :harness_error. It excludes :no_coverage, :ignored, and :poisoned, which never launch a test run. Returns 0.0 when nothing ran.

iex> results = [
...>   %Mutare.Result{status: :killed},
...>   %Mutare.Result{status: :harness_error},
...>   %Mutare.Result{status: :no_coverage}
...> ]
iex> Mutare.Report.harness_error_rate(results)
0.5

harness_errors_exceed?(results, max_rate)

@spec harness_errors_exceed?([Mutare.Result.t()], number() | nil) :: boolean()

Returns whether harness_error_rate/1 exceeds max_rate.

max_rate is a fraction from 0.0 to 1.0. A nil value disables the check and returns false.

header(site)

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

Header line for a surviving mutant, e.g. lib/x.ex:42 [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 [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.

passes_gate?(results, min_score)

@spec passes_gate?([Mutare.Result.t()], number() | nil) :: boolean()

Returns whether results meet a minimum score percentage.

A nil minimum always passes.

iex> results = [%Mutare.Result{status: :killed}, %Mutare.Result{status: :survived}]
iex> Mutare.Report.passes_gate?(results, 60)
false
iex> Mutare.Report.passes_gate?(results, nil)
true

patch(site, source)

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

Apply a single mutation to the original source string.

percent(value)

@spec percent(number()) :: String.t()

Format a percentage value (already on a 0..100 scale) to one decimal place, without a trailing %.

iex> Mutare.Report.percent(2 / 3 * 100)
"66.7"

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.

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 is ignored; score gating is handled by the caller.

score(results)

@spec score([Mutare.Result.t()]) :: float()

Mutation score as a percentage: killed / (total − no_coverage − ignored − poisoned − harness_error). Returns 100.0 when the denominator is zero (nothing to test).

iex> results = [
...>   %Mutare.Result{status: :killed},
...>   %Mutare.Result{status: :survived},
...>   %Mutare.Result{status: :no_coverage}
...> ]
iex> Mutare.Report.score(results)
50.0

summary(results)

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

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

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

survivor(site, source)

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

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