fallow_api/mutation/mod.rs
1//! The dynamic half of mutation testing: the runner protocol, the embedded
2//! companion, the transport, the scheduler and the result model.
3//!
4//! The static half (selection, the test map, pruning, ranking and the
5//! instrumented code) is `fallow_engine::mutation`. The entry points are
6//! [`crate::run_mutation`] and [`crate::plan_mutation`].
7
8pub(crate) mod companion;
9pub(crate) mod discovery;
10pub(crate) mod last_report;
11pub(crate) mod manifest;
12pub(crate) mod node_args;
13pub(crate) mod protocol;
14pub(crate) mod report;
15pub(crate) mod result_cache;
16pub(crate) mod run_dir;
17pub(crate) mod runner_select;
18pub(crate) mod scheduler;
19pub(crate) mod transport;
20pub(crate) mod typecheck;
21pub(crate) mod verdicts;
22pub(crate) mod worktree;
23
24#[cfg(test)]
25#[expect(clippy::unwrap_used, reason = "tests unwrap to keep assertions short")]
26mod e2e_tests;
27
28use std::path::PathBuf;
29use std::time::Duration;
30
31pub use fallow_engine::mutation::Operator;
32pub use last_report::{last_mutation_report_path, save_last_mutation_report};
33pub use report::baseline_blocked_run_error;
34pub use scheduler::control::split_control_run_failure;
35pub use verdicts::{MAX_VERDICT_INPUT_BYTES, apply_mutation_verdicts, validate_mutation_verdicts};
36
37use crate::AnalysisOptions;
38
39/// The code that a mutation run mutates.
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum MutationRunScope {
42 /// The added lines against a base ref. `None` detects the base ref the
43 /// same way as `audit`. An explicit `analysis.diff_file` wins over the
44 /// base ref.
45 Changed {
46 /// The base ref.
47 base: Option<String>,
48 },
49 /// Each source file of the project.
50 All,
51 /// The files at these paths, or below these directories. A relative
52 /// path is relative to the project root.
53 Paths(Vec<PathBuf>),
54}
55
56impl Default for MutationRunScope {
57 fn default() -> Self {
58 Self::Changed { base: None }
59 }
60}
61
62/// A phase event of a mutation run, for a caller that shows progress.
63///
64/// [`crate::run_mutation_with_progress`] sends the events in run order. A
65/// run with no covered mutant and no test file skips the runner phases.
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67#[non_exhaustive]
68pub enum MutationProgress {
69 /// The plan is ready. The runner setup starts: the runtime, the
70 /// companion install and the runner start.
71 Setup {
72 /// The planned mutants.
73 mutants: usize,
74 },
75 /// The baseline run starts: the test files run with no active mutant.
76 Baseline {
77 /// The test files of the baseline run.
78 test_files: usize,
79 },
80 /// The mutant runs. The first event has `done` 0, and each finished
81 /// mutant sends one event.
82 Mutants {
83 /// The mutants that finished.
84 done: usize,
85 /// The mutants that this phase runs. A cached result is not in it.
86 total: usize,
87 },
88 /// The retries of survivors and timeouts. The first event has `done` 0.
89 Retries {
90 /// The retries that finished.
91 done: usize,
92 /// The retries that this phase runs.
93 total: usize,
94 },
95 /// The tests stopped. The worktree check, the survivor type check and
96 /// the report start.
97 Finalize,
98}
99
100/// Receives the [`MutationProgress`] events of a run. The scheduler sends
101/// the mutant events from its worker threads.
102pub type MutationProgressSink<'a> = &'a (dyn Fn(MutationProgress) + Sync);
103
104/// Options of [`crate::run_mutation`] and [`crate::plan_mutation`].
105#[derive(Debug, Clone, Default)]
106pub struct MutationRunOptions {
107 /// Shared project, config and cache options.
108 pub analysis: AnalysisOptions,
109 /// The code to mutate.
110 pub scope: MutationRunScope,
111 /// The operators to use. `None` uses each operator.
112 pub operators: Option<Vec<Operator>>,
113 /// Keep mutants near a file system, process, network or database sink.
114 /// Only an explicit caller option can set this, never project config,
115 /// because the run has no sandbox copy.
116 pub allow_side_effects: bool,
117 /// The maximum number of covered mutants to run, in rank order. A
118 /// mutant that no dry-run test covers is `no_coverage` and does not
119 /// count. The others become `pending`.
120 pub max_mutants: Option<usize>,
121 /// Run in a dirty git worktree, or outside a git worktree. The run still
122 /// compares the worktree after the run.
123 pub allow_dirty: bool,
124 /// The JavaScript runtime. `None` uses the runtime of the runner from
125 /// `PATH`: `node` for `node:test`, `bun` for `bun test`, and `node`,
126 /// then `bun`, for Vitest.
127 pub runtime: Option<PathBuf>,
128 /// The number of companion processes. `None` uses half the logical
129 /// cores.
130 pub concurrency: Option<usize>,
131 /// The time budget of the run. Mutants that did not start before it
132 /// ends become `pending`.
133 pub time_budget: Option<Duration>,
134 /// The timeout of the companion `init` and the dry run. `None` uses
135 /// 120 seconds.
136 pub runner_timeout: Option<Duration>,
137 /// The config file of the test runner, for example a separate Vitest
138 /// config of the integration tests. A relative path is relative to the
139 /// project root. The file must exist inside the project root. `None`
140 /// uses the `mutation.runnerConfig` config key, else the default config
141 /// discovery of the runner. Only the Vitest runner reads a config file.
142 pub runner_config: Option<PathBuf>,
143 /// The files that the caller writes after the run, for example the
144 /// `--output-file` report. The worktree check leaves them out, so an old
145 /// report does not make the worktree dirty. A relative path is relative
146 /// to the process directory, as the caller writes it.
147 pub output_paths: Vec<PathBuf>,
148 /// Type-check each survived mutant through the TypeScript-Go companion
149 /// of `--type-aware`. A survivor whose mutated file does not type-check
150 /// becomes `compile_error` and leaves the score. The run fails before
151 /// the tests start when no trusted companion exists.
152 pub typecheck: bool,
153}