mix mut (Mutalisk v0.1.0)

Copy Markdown View Source

Runs mutation testing.

Options

  • --files "PATTERN" — Only mutate files matching glob pattern. Quote globs so your shell does not expand them first. Repeat the flag, pass multiple path tokens after it, or use comma-separated patterns to mutate several paths.
  • --test-paths "PATH" — Restrict test discovery to these paths (default: test/ for a single app, every child app's <apps_path>/<child>/test/ for an umbrella). Repeat the flag, pass multiple path tokens after it, or use comma-separated paths, exactly like --files. Paths must be project-relative.
  • --mutators NAMES — Comma-separated mutator name list
  • --enable TARGETS — Comma-separated enabled targets. Defaults: dispatch, guard, env_walker (only AtomLiteral active; pass it explicitly to run all env-walker literals), pattern_shape. Opt-in: module_attribute, body_literal, pattern_literal, variable, conditional, statement_delete, clause_delete, guard_boolean, pipeline_drop, map_update_drop, receive_timeout. Passing --enable replaces the default enabled targets and selects the default selectable mutator set gated by the listed targets. Note: an opt-in mutator selected via --mutators ALSO needs its target enabled, and a few experimental mutators require --mutators explicitly (see docs/MUTATORS.md).
  • --fail-at SCORE — Mutation score threshold; exit 1 below (default: 80)
  • --reporters NAMES — Comma-separated: terminal, stryker-json (default: both), html, github-actions (opt-in)
  • --output-path PATH — Report output path (default: stryker.report.json). Absolute paths are honored; relative paths are under the project root. The html reporter writes the same path with a .html extension.
  • --concurrency N — Worker pool size for parallel mutant execution. Default: min(schedulers_online, 4). Use --concurrency 1 for sequential execution. Explicit values are bounded to avoid pathological sandbox startup/teardown costs.
  • --max-mutants N — Cap total mutants (stable-id sorted sample if exceeded). Limits planned mutant executions before coverage collection. Oracle construction and planning still run in full; coverage still collects the selected test files.
  • --debug-plan — Dump plan JSON to plan.debug.json and exit before any mutant runs. It still builds the oracle and baseline plan; it is not a zero-cost parser-only mode.
  • --selection MODE — Test selection mode: static, coverage, coverage_with_static_fallback (default). static is the fully-portable escape hatch — also the fast choice on macro-heavy/generated apps (e.g. Phoenix) where coverage collection is slow before it falls back.
  • --keep-work-copy — Skip cleanup of the run's temporary work copies on exit and print their retained paths (debug aid; default: false)
  • --test-timeout-ms N — Per-test ExUnit timeout in milliseconds. Default 10000. Range 1000..600000.
  • --suite-timeout-ms N — Whole-suite host budget, in milliseconds, for one mutant's selected tests (range 1000..3600000). The host kills the mutant's test port after max(test_timeout_ms, suite_timeout_ms) plus a 10000 ms drain buffer. Unset (the default) it is derived from the measured baseline run: max(test_timeout_ms, min(baseline_wall_ms * 2, 600000)) + 10000. Deriving it matters because several individually valid slow tests can otherwise exceed a budget sized for a single test, and the resulting host timeout counts as a detection and inflates the score.
  • --priv-fingerprint stat|hash — How each sandbox's copied priv/ tree is fingerprinted for the between-mutant reset. stat (the default) compares size + mtime: cheap, but it misses a rewrite that keeps the byte size AND lands in the same mtime second. hash compares content: exact, at the cost of reading every priv/ file on every reset (per mutant, per worker). Use hash when tests rewrite fixtures under priv/ in place.
  • --incremental — Reuse verdicts from a prior run's history for unchanged mutants instead of re-executing them (opt-in; see history_path config). Materially changes execution and report interpretation.
  • --since REF — With --incremental, force execution of mutants whose file changed since git REF (e.g. HEAD~1, main). Other mutants remain eligible only if all digest checks pass. Outside a git repo or with an unknown ref, reuse falls back to digest checks with a warning.

Configuration

Settings can come from four layers, lowest to highest precedence:

.mutalisk.exs project file  <  legacy `config :mut`  <  config :mutalisk  <  CLI flags

A CLI flag always wins; config :mutalisk overrides both config :mut (a deprecated, still-accepted namespace kept for compatibility) and the file; the file is the base. Keys (same names in all layers): files, test_paths, mutators, enabled_targets, selection, fail_at, concurrency, test_timeout_ms, suite_timeout_ms, priv_fingerprint, reporters, output_path, exclude, max_mutants, since, incremental, history_path, and coverage_timeout_ms. exclude, history_path, and coverage_timeout_ms are config-only (no CLI flag); the rest accept a CLI flag that overrides config. history_path controls where every run writes reusable verdict history for future --incremental runs. coverage_timeout_ms is a positive integer in milliseconds for each per-test-file coverage collection attempt. Timed-out or failed files have unknown coverage and are included conservatively in every mutant's selection. Run-scoped switches debug_plan and keep_work_copy are CLI-only.

When test_paths is unset it defaults to test/ for a single app and to every child app's <apps_path>/<child>/test/ for an umbrella. Set it explicitly only to override that — note a bare ["test"] finds no tests in an umbrella.

.mutalisk.exs (in the project root, loaded if present) is a plain keyword-list term — no Config runtime needed:

# .mutalisk.exs
[
  selection: :coverage_with_static_fallback,
  fail_at: 75.0,
  concurrency: 8,
  exclude: [~r"lib/my_app_web/router.ex"]
]

Or via application config:

config :mutalisk,
  exclude: [~r/lib\/my_app_web\/router.ex/],
  fail_at: 80.0,
  selection: :coverage_with_static_fallback,
  concurrency: System.schedulers_online()

Source-level ignores

Add @mutalisk_ignore true to a module to exclude every mutant in that module (generated/DSL modules, intentionally-untested code):

defmodule MyApp.Generated do
  @mutalisk_ignore true
  # ... no mutants are produced for this module ...
end

Run as MIX_ENV=test mix mut.