Packages

A compile-once mutation testing tool for Elixir.

Current section

Files

Jump to
mutare CHANGELOG.md
Raw

CHANGELOG.md

# Changelog
All notable changes to this project are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.1.0] - 2026-09-07
Initial release.
### Added
- **Compile-once metamutant.** Source under `lib/` is rewritten into a single
program that embeds every mutant behind a `:persistent_term` runtime switch,
compiled once; the suite then runs once per mutant by flipping
`MUTARE_ACTIVE_MUTANT` — no per-mutant recompilation.
- **A broad built-in mutator set** (all on by default): arithmetic/operator and
operand swaps, relational and logical swaps, strict-equality relaxation,
literals of every kind (integer/float/string/charlist/atom/sigil/regex/
bitstring/date-time), collection/string/map/keyword call rewrites, pattern and
clause restructurings, guard/default/call drops, and more. See `Mutare.Mutators`.
- **Unit-return classification** — a function (or anonymous function) whose every
return path is literally `:ok` or `nil` returns no data, so its tails draw no
return-value constant and no `:ok → :error` swap. Syntactic, and one-sided: it
can miss a unit function, never silence a data-returning one.
- **Coverage-guided test selection** — coverage is self-recorded by the metamutant
at runtime and keyed by mutant id; each mutant runs only the test cases that
cover it (`--per-file` widens that to the covering test *files*, for stateful
`async: false` suites; `--full` runs the whole suite every time). Uncovered
mutants are skipped and excluded from the score.
- **Parallel workers with per-mutant timeouts** — mutants run `:workers` at a
time, each capped by a wall-clock deadline; a mutation that hangs halts itself
and counts as a kill (no process-tree killing).
- **Compile-poison recovery** — a mutant that won't compile is identified from the
compile error, dropped (reported as *poisoned*, excluded from the score), and
the build retried, for a bounded number of rounds; only a compile error that
can't be attributed to any mutant (or that outlasts the bound) aborts the run,
with a copy-pasteable `:skip` snippet.
- **`# mutare:ignore` directive** — suppress a known-equivalent mutant per line,
per span (`-start`/`-end`), or per file (`-file`), with an optional free-text
reason and an optional `[family]` / `[family:label]` filter; ineffective
directives are surfaced (`--strict-ignores` escalates).
- **Reporters** — human (default, survivor diffs + score), plus machine-readable
`json` (Stryker / mutation-testing-elements schema), `html` (interactive
viewer), and `sarif` (GitHub code scanning) via `--report FORMAT[:PATH]`
(repeatable) or `:reporters`.
- **Live progress** on stderr; the detailed report and score print to stdout.
- **CI integration**`--since <ref>` to scope to the lines changed against a
git ref, `--min-score` to gate, `--line` to target lines, and a kept sandbox
(the default) so the
compiled build carries across runs; `--sandbox <path>` pins it at a CI cache,
`--no-keep-sandbox` opts back into a throwaway copy.
- **Umbrella-aware** — target one app, several, or the whole workspace.
- **Extension surface**`Mutare.Mutator` (custom mutators), independent
`Mutare.CallRouting` and `Mutare.UseExpansion` capabilities, `:extensions` for
non-mutating integrations, declarative `:call_routes` configuration
(user-tier treatments only; the adapter-grade treatments must come from a
module implementing `Mutare.CallRouting`), the `Mutare.AST` node
constructors that discharge Sourceror's emission invariants for plugins, and
the `Mutare.Calls` call-resolution readers (`resolved_call_to/3`,
`module_key/1`) so plugins match calls without building core's key
representation, and a declarative environment guard (`required_modules/0`,
on mutators and extensions): the modules a DSL plugin routes are checked
loadable once at startup, aborting with `Mutare.EnvironmentError` instead of
silently registering routes against nothing on an external-source run.
- **`mix igniter.install mutare`** installer that detects frameworks and wires up
the matching companion packages and `.mutare.exs`.
- **Call routes** (`call_routes:` / `--skip-call Module.fun/arity`) — leave a
call alone: `:skip` makes a whole call an inert leaf (functions, macros, and
the construct special forms alike — `Kernel.if/2` or `case` included; a piped receiver and
the enclosing function's return-value mutants are unaffected), `:raw` leaves
an argument as written, `:interior` mutates an argument's contents but never
its own node, and a keyed refinement (`[:expression, timeout: :raw]`) reaches
one option of a literal keyword argument. The forms Mutare analyzes
structurally (`if`, `case`, the boolean operators, …) take `:skip` only, and
definitions (`def`, `defmodule`, …) and literal syntax (`{}`, `%{}`, `=`, …)
take no route. Routes match qualified,
aliased, imported, and piped forms, and an entry that matches no call in a
full scan is warned about.
- **Argument marks** (`argument_marks:`) — extend the built-in timeout table (or
any label a mutator declares) to your own functions, with the mutators'
value-aware reaction: `{MyApp.Http, :get, 2, [{:keyword, :recv_timeout}], :timeout}`.
[Unreleased]: https://github.com/foxbenjaminfox/mutare/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/foxbenjaminfox/mutare/releases/tag/v0.1.0