# `Mutare.Score`
[🔗](https://github.com/foxbenjaminfox/mutare/blob/v0.2.1/lib/mutare/score.ex#L1)

The mutation score and the CI gates computed over a run's results.

The score is `killed / (total − no_coverage − ignored − poisoned − harness_error)`: a
timeout or an atom-table exhaustion counts as a kill, and a mutant that reached no verdict
is left out of the denominator. `Mutare.Result` classifies each status (`kill?/1`,
`scored?/1`, `ran?/1`); this module only counts.

The gates (`gate_failures/2`, `harness_errors_exceed?/2`) are policy over the same tallies:
`:no_coverage`, `:poisoned`, and `:harness_error` stay out of the score, but a CI run can
still fail on them. Rendering the tallies as text is `Mutare.Report`'s job.

# `gate_failures`

```elixir
@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_rate`

```elixir
@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.Score.harness_error_rate(results)
    0.5

# `harness_errors_exceed?`

```elixir
@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`.

# `passes_gate?`

```elixir
@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.Score.passes_gate?(results, 60)
    false
    iex> Mutare.Score.passes_gate?(results, nil)
    true

# `percent`

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

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

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

# `score`

```elixir
@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.Score.score(results)
    50.0

---

*Consult [api-reference.md](api-reference.md) for complete listing*
