saboteur 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +280 -1
- data/README.md +635 -77
- data/exe/saboteur +6 -2
- data/lib/generators/saboteur/install/install_generator.rb +21 -0
- data/lib/saboteur/agent_skill.rb +46 -0
- data/lib/saboteur/baseline/acceptance.rb +218 -0
- data/lib/saboteur/baseline/command.rb +80 -0
- data/lib/saboteur/baseline/comparison.rb +105 -0
- data/lib/saboteur/baseline/completeness.rb +53 -0
- data/lib/saboteur/baseline/evidence.rb +114 -0
- data/lib/saboteur/baseline/identity.rb +78 -0
- data/lib/saboteur/baseline/manifest.rb +164 -0
- data/lib/saboteur/baseline/run_evidence.rb +117 -0
- data/lib/saboteur/baseline/workflow.rb +121 -0
- data/lib/saboteur/baseline_health.rb +1 -1
- data/lib/saboteur/cli/exit_status.rb +93 -0
- data/lib/saboteur/cli/run_reporting.rb +75 -0
- data/lib/saboteur/cli.rb +171 -63
- data/lib/saboteur/config.rb +101 -19
- data/lib/saboteur/config_file.rb +97 -0
- data/lib/saboteur/custom_map.rb +170 -0
- data/lib/saboteur/doctor.rb +57 -9
- data/lib/saboteur/engine/baseline_guard.rb +44 -5
- data/lib/saboteur/engine/boot.rb +9 -0
- data/lib/saboteur/engine/bytecode_equivalence.rb +95 -0
- data/lib/saboteur/engine/canary.rb +74 -0
- data/lib/saboteur/engine/candidate_selection.rb +18 -5
- data/lib/saboteur/engine/candidates.rb +59 -7
- data/lib/saboteur/engine/coverage_store.rb +53 -6
- data/lib/saboteur/engine/data_inputs.rb +43 -0
- data/lib/saboteur/engine/diagnostics.rb +176 -2
- data/lib/saboteur/engine/diff_scope.rb +18 -0
- data/lib/saboteur/engine/distribution.rb +54 -13
- data/lib/saboteur/engine/result_cache.rb +10 -5
- data/lib/saboteur/engine/run_fingerprint.rb +40 -10
- data/lib/saboteur/engine/sandbox_policy.rb +108 -0
- data/lib/saboteur/engine/scheduler.rb +99 -36
- data/lib/saboteur/engine/subject_selection.rb +24 -1
- data/lib/saboteur/engine/suite_files.rb +8 -1
- data/lib/saboteur/engine/test_guess.rb +52 -0
- data/lib/saboteur/engine/unchanged_syntax.rb +87 -0
- data/lib/saboteur/engine/verdict_check.rb +7 -6
- data/lib/saboteur/engine.rb +257 -22
- data/lib/saboteur/execution/protocol.rb +2 -1
- data/lib/saboteur/execution/reevaluator.rb +79 -14
- data/lib/saboteur/execution/runner.rb +120 -17
- data/lib/saboteur/execution/sandbox.rb +271 -0
- data/lib/saboteur/execution/stale_captures.rb +43 -0
- data/lib/saboteur/execution/supervisor.rb +113 -9
- data/lib/saboteur/execution/worker_pool.rb +20 -4
- data/lib/saboteur/execution/wrapper.rb +45 -0
- data/lib/saboteur/frameworks/rails.rb +329 -13
- data/lib/saboteur/gates.rb +83 -0
- data/lib/saboteur/help.rb +63 -23
- data/lib/saboteur/ignore_list.rb +76 -17
- data/lib/saboteur/inline_suppression.rb +65 -0
- data/lib/saboteur/install.rb +132 -0
- data/lib/saboteur/integration/base.rb +1 -0
- data/lib/saboteur/integration/focus_guard.rb +83 -0
- data/lib/saboteur/integration/rspec.rb +68 -4
- data/lib/saboteur/mutation_source/argument_removal.rb +86 -0
- data/lib/saboteur/mutation_source/builtin.rb +30 -5
- data/lib/saboteur/mutation_source/chain_removal.rb +80 -0
- data/lib/saboteur/mutation_source/condition_negation.rb +50 -0
- data/lib/saboteur/mutation_source/endpoint_replacement.rb +61 -0
- data/lib/saboteur/mutation_source/hazard_guard.rb +136 -0
- data/lib/saboteur/mutation_source/operator_assignment.rb +52 -0
- data/lib/saboteur/mutation_source/operator_registry.rb +57 -0
- data/lib/saboteur/mutation_source/pair_removal.rb +101 -0
- data/lib/saboteur/mutation_source/range_replacement.rb +43 -0
- data/lib/saboteur/preview.rb +106 -0
- data/lib/saboteur/replay.rb +1 -1
- data/lib/saboteur/reporter/agent.rb +87 -0
- data/lib/saboteur/reporter/agent_clipping.rb +29 -0
- data/lib/saboteur/reporter/agent_commands.rb +67 -0
- data/lib/saboteur/reporter/agent_incomplete.rb +60 -0
- data/lib/saboteur/reporter/agent_summary.rb +137 -0
- data/lib/saboteur/reporter/findings.rb +12 -19
- data/lib/saboteur/reporter/history_paths.rb +38 -0
- data/lib/saboteur/reporter/history_record.rb +132 -0
- data/lib/saboteur/reporter/html.rb +61 -2
- data/lib/saboteur/reporter/mutation_text.rb +36 -0
- data/lib/saboteur/reporter/next_steps.rb +30 -0
- data/lib/saboteur/reporter/palette.rb +37 -0
- data/lib/saboteur/reporter/score_history.rb +123 -0
- data/lib/saboteur/reporter/score_table.rb +57 -0
- data/lib/saboteur/reporter/stryker.rb +25 -5
- data/lib/saboteur/reporter/survivor_hints.rb +68 -0
- data/lib/saboteur/reporter/survivor_list.rb +126 -0
- data/lib/saboteur/reporter/survivor_ranking.rb +34 -0
- data/lib/saboteur/reporter/terminal.rb +157 -42
- data/lib/saboteur/reporter/test_matrix.rb +57 -0
- data/lib/saboteur/result.rb +86 -10
- data/lib/saboteur/score.rb +89 -0
- data/lib/saboteur/selection/coverage_map.rb +119 -13
- data/lib/saboteur/selection/git_diff.rb +28 -1
- data/lib/saboteur/selection/recorder.rb +55 -13
- data/lib/saboteur/subject.rb +40 -4
- data/lib/saboteur/subject_finder.rb +62 -12
- data/lib/saboteur/version.rb +1 -1
- data/lib/saboteur/working_tree.rb +56 -0
- data/lib/saboteur.rb +10 -0
- metadata +58 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a9d8e69906c1a27f726dea87fb72eae3a92a6dfc4e3d10a5fbe903c0f8cd4e38
|
|
4
|
+
data.tar.gz: f6c85551dd523d8926c3ac05313f1dc0edea86aa84d321a8b4e05d7f84ca4706
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8b4bddc7c4b7b621082a6ad986e49e3e4a113ae1100f4cfb18f6a649d6ac8f852da486f110bcb61da0af3d8f588c172970e1526454d857d7ca6fb1411bde2c48
|
|
7
|
+
data.tar.gz: 4b8ab1f9f45dd75506b7e5a388a85af5a2fa46cbe6b70bc885ef1dc615988e4ef01ca4815fe67b2f8fd62cb5aa07511bf733da8e4f21775e6325080f478f2f06
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,285 @@ All notable changes are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.5.0] - 2026-10-14
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Opt-in extended `range`, `operator_assignment` and `condition_negation` operators (#135). They run only
|
|
13
|
+
when selected with `--operators`; the default core set does gain the new `chain_removal`
|
|
14
|
+
operator (below).
|
|
15
|
+
- `whole_file` config: an opt-in list of subject ids or globs (for example
|
|
16
|
+
`"Point#distance"`, `"Geometry::*"`). For those subjects the child evaluates the
|
|
17
|
+
whole mutated file instead of re-evaluating the one method, so methods
|
|
18
|
+
surgical re-evaluation refuses can be mutated. It applies to methods already
|
|
19
|
+
found as subjects. The file's top-level code runs again in the child: load-time
|
|
20
|
+
side effects repeat and constants it assigns are rebuilt. A surviving
|
|
21
|
+
mutant whose definition a later definition in the file replaces is reported as
|
|
22
|
+
unreachable, not survived. Off by default (Refs #138).
|
|
23
|
+
- `--matrix` (#132) runs every covering test per mutant and lists blind and
|
|
24
|
+
redundant tests after the survivors.
|
|
25
|
+
- The 30-second heartbeat of the baseline and coverage phases now shows the examples finished so far, the percentage and a rough time left, read from the phase log, instead of only the elapsed time. (#56)
|
|
26
|
+
- Each finding in `--agent` output carries a `testHint` (`kind`, `text`, and for a
|
|
27
|
+
comparison swap that differs only for equal or unequal operands, a `case`
|
|
28
|
+
such as `left == right`) saying where a test could look (#154). It is a
|
|
29
|
+
suggestion, not a diagnosis: a survivor can also be an equivalent mutant.
|
|
30
|
+
- `--max-timeouts N` and `--max-errors N` CI gates (count or percent of executed
|
|
31
|
+
mutants). Exceeding one exits 1 with `exitCause` `maxTimeouts` or `maxErrors`.
|
|
32
|
+
The covered score is already gated by `--threshold`. No Action input yet.
|
|
33
|
+
- A run that leaves a live survivor ends with a "Next steps" footer of command
|
|
34
|
+
templates: replay (`saboteur run --id DIGEST --seed N`, to be combined with the
|
|
35
|
+
run's own selection options), an ignore entry with the survivor's subject, and
|
|
36
|
+
`saboteur run --agent` (#147).
|
|
37
|
+
- **Safety:** mutant children now run under a Landlock sandbox on Linux
|
|
38
|
+
6.2+ (Landlock ABI 3) (#152): the child cannot create, write, rename,
|
|
39
|
+
unlink or truncate files in the project tree except `tmp/`, the cache dir
|
|
40
|
+
and the system temp dir, and inherited descriptors already open for
|
|
41
|
+
writing elsewhere are closed before the sandbox engages. This is the
|
|
42
|
+
third layer behind the hazard guard and the canary, and the first one
|
|
43
|
+
that stops a destructive call before it acts. Landlock covers the
|
|
44
|
+
filesystem only. On kernels 5.13-6.1 (ABI 1-2) `truncate()` cannot be
|
|
45
|
+
restricted, and on kernels without Landlock there is no sandbox at all:
|
|
46
|
+
both run with the guard and canary only, noted once. `c.sandbox = false`
|
|
47
|
+
opts out (`/dev/shm` and `/dev/tty` are denied like any other
|
|
48
|
+
ungranted path; `/dev/null` stays writable); `c.sandbox = "strict"`
|
|
49
|
+
refuses to run degraded;
|
|
50
|
+
`c.sandbox_writable` grants extra directories (suites writing `db/`,
|
|
51
|
+
`log/`, ... otherwise fail with EACCES in mutant children) and warns when
|
|
52
|
+
a grant contains the project root or another grant. The root check covers
|
|
53
|
+
the effective write set - every grant realpath'd, the cache dir and the
|
|
54
|
+
system temp dir included - so `cache_dir = "."`, a symlinked `tmp`, a
|
|
55
|
+
`/` grant or a checkout inside the system temp dir cannot silently leave
|
|
56
|
+
the tree writable; `c.sandbox = "strict"` refuses to run then. A kernel probe that
|
|
57
|
+
errors (rather than cleanly reporting Landlock absent) refuses the run.
|
|
58
|
+
The descriptor sweep also closes stdio redirected to a writable regular
|
|
59
|
+
file (terminal, pipe and `/dev/null` stdio stays), inherited files
|
|
60
|
+
already deleted from the tree, and any descriptor it cannot inspect -
|
|
61
|
+
fail closed. The C library lookup tolerates musl (soname fallbacks, then
|
|
62
|
+
the process symbol table) and degrades to the one-time unsandboxed note
|
|
63
|
+
when no libc resolves, instead of crashing.
|
|
64
|
+
- Audited inline suppression comments with required reasons (#133). Inspired by mutineer
|
|
65
|
+
(github.com/davidteren/mutineer, MIT, by David Teren).
|
|
66
|
+
- `--dry-run` previews planned mutants per method and the max_mutants_per_method
|
|
67
|
+
omissions without booting the app, running tests, forking or writing reports
|
|
68
|
+
(#134). A file that does not parse marks the total INCOMPLETE. A cached
|
|
69
|
+
estimate is baseline covering-test work, not a bound on mutant runtime.
|
|
70
|
+
Custom sources are still required during a dry run. `--list-operators` and
|
|
71
|
+
`--operators` list and select builtin operators. Inspired by mutineer
|
|
72
|
+
(github.com/davidteren/mutineer, MIT, by David Teren).
|
|
73
|
+
- [docs/minitest.md](docs/minitest.md) records that Minitest is unsupported and how much
|
|
74
|
+
of the engine depends on RSpec (#200).
|
|
75
|
+
- [docs/scores.md](docs/scores.md) defines the covered score, the overall score a
|
|
76
|
+
Stryker dashboard shows, assertion and crash kills, and what makes a run
|
|
77
|
+
incomplete (#198).
|
|
78
|
+
- A startup warning on stderr when the working tree has uncommitted changes,
|
|
79
|
+
because mutants run your code and could alter or delete files. Saboteur's own
|
|
80
|
+
output paths do not count as changes.
|
|
81
|
+
- Terminal report rewrite: survivors grouped by file and method with
|
|
82
|
+
evidence-scoped hints (suggestions, not diagnoses), a score table, a
|
|
83
|
+
progress counter with ETA, `--verbose`, `--[no-]color` and `--fail-fast`
|
|
84
|
+
(serial only; the run is marked incomplete with exit cause `failFast` and
|
|
85
|
+
lists the subjects it never reached). The CI progress line-break can
|
|
86
|
+
misalign by a mark when parallel workers finish at the same moment.
|
|
87
|
+
- Sorbet signature text does not prune nil-producing mutants. Runtime enforcement
|
|
88
|
+
is not verified: checked levels, custom handlers and shims can allow nil to
|
|
89
|
+
reach assertions. Type-aware pruning (#153) is deferred until enforcement can
|
|
90
|
+
be established without guessing from return-type text.
|
|
91
|
+
- `endpoint_replacement` swaps explicit no-argument `first` and `last` calls,
|
|
92
|
+
including safe navigation. Calls with arguments or blocks, bare calls,
|
|
93
|
+
arid statements and nested scopes are excluded.
|
|
94
|
+
- The `chain_removal` operator drops one link from the middle of a method chain:
|
|
95
|
+
`a.b.c` becomes `a.c`. The first receiver and the last call stay, so the result
|
|
96
|
+
is still a call on the original object. It ships in the default core set; links
|
|
97
|
+
that hand back their receiver (`freeze`, `dup`, `clone`, `itself`, `tap`) and
|
|
98
|
+
chains that are assignment targets are left alone (#183).
|
|
99
|
+
- `saboteur baseline check|propose|accept --baseline PATH`: record today's
|
|
100
|
+
survivors as a baseline file and fail only on new ones, so an existing project
|
|
101
|
+
adopts mutation testing without a cleanup week. `check --epsilon POINTS` also
|
|
102
|
+
fails when the score drops more than POINTS percentage points below the
|
|
103
|
+
baseline's, and the failure detail prints inline. The GitHub Action exposes the
|
|
104
|
+
drop as the `baseline-score-drop` output (#131, #148).
|
|
105
|
+
- `--guess-tests` scopes a `--since` run to the specs named after the changed
|
|
106
|
+
files, for suites where full selection costs too much. A spec that reaches a
|
|
107
|
+
changed file only through another file does not run, so a mutant only it
|
|
108
|
+
would kill can survive, and a line no guessed spec executes is reported as
|
|
109
|
+
NoCoverage. A changed source file with no matching spec (or no changed source
|
|
110
|
+
at all) falls back to the whole suite with a warning. It cannot be combined
|
|
111
|
+
with `--test` (#185).
|
|
112
|
+
- `--allow-focus` (#108): without it, a suite narrowed by a stray `fit`,
|
|
113
|
+
`fdescribe` or `:focus` filter stops the run, because mutation testing a
|
|
114
|
+
focused slice would report a score for the wrong suite.
|
|
115
|
+
- Survivor findings are ranked in the JSON and `--agent` findings: coarse
|
|
116
|
+
mutants first, then more covering tests, a larger edited-line span and nearer
|
|
117
|
+
changed lines (#191). The terminal report keeps its file/method/line groups,
|
|
118
|
+
with the ranking applied within each live or accepted line group.
|
|
119
|
+
- `--score-history PATH` appends each run's scores to a JSONL file. Opt-in (#145).
|
|
120
|
+
- [docs/guides/reading-survivors.md](docs/guides/reading-survivors.md) is a guide
|
|
121
|
+
to reading survivors, and [docs/recipes/README.md](docs/recipes/README.md)
|
|
122
|
+
collects setups for factories, fixtures, DatabaseCleaner, Capybara and test
|
|
123
|
+
adapters.
|
|
124
|
+
### Changed
|
|
125
|
+
- The text run prints one `Reports:` line naming the JSON and HTML reports, with
|
|
126
|
+
paths relative to the project root when they are under it, instead of two
|
|
127
|
+
lines with a blank line between them (#150). Scripts that read the old
|
|
128
|
+
`JSON report:` and `HTML report:` lines must read `Reports:` instead.
|
|
129
|
+
- `saboteur doctor` checks the environment only by default (Ruby version,
|
|
130
|
+
coverage setup and SimpleCov conflicts, boot files, the custom map and ignore
|
|
131
|
+
subjects). The two unchanged baseline suite passes are slow on a large suite
|
|
132
|
+
and now run only with `doctor --suite` (or `--test`, which implies it) (#53).
|
|
133
|
+
|
|
134
|
+
### Fixed
|
|
135
|
+
- `--since` with a ref that is not a commit exits 2 as a usage error, naming the
|
|
136
|
+
ref, before the target boots. It used to exit 3 (tool failure) after boot (#115).
|
|
137
|
+
- Diff mode skips a method whose only changes are comments, blank lines or
|
|
138
|
+
spacing, because its syntax tree matches the merge base. The run prints how many
|
|
139
|
+
it skipped. A method that uses `__LINE__` is compared by the line it stands
|
|
140
|
+
for, so moving it or adding a line above that use keeps it in the run.
|
|
141
|
+
Ambiguous cases (a new file or method, a name defined twice, a base
|
|
142
|
+
that does not parse) still run (#115).
|
|
143
|
+
- The default arid logger pattern also matches `@logger`, `self.logger` and
|
|
144
|
+
`::Rails.logger`, so those calls no longer produce noise survivors (#111).
|
|
145
|
+
- The unscoped `--since` warning now says what `--test` drops: the consumer specs of a changed method no longer run, which can turn kills into survivors. (#113)
|
|
146
|
+
- **Safety:** mutants that would leave a directly-written destructive call
|
|
147
|
+
running with altered inputs are suppressed (#102). A syntactic hazard
|
|
148
|
+
guard matches calls written directly - `FileUtils.rm*`/`remove_entry*`
|
|
149
|
+
(bare names included), `File.delete`/`unlink`, `Dir.rmdir`/`delete`/
|
|
150
|
+
`unlink`, `rmtree`, `system`/`exec`/`spawn` (bare or on `Kernel`/
|
|
151
|
+
`Process`), backticks/`%x`, `IO.popen`, `Open3.*`, piped `open` - and
|
|
152
|
+
suppresses mutants inside such a call or elsewhere in a method that still
|
|
153
|
+
holds one. Calls behind aliases, variables or `send` are not recognized.
|
|
154
|
+
A canary re-checks the tracked tree after each method's mutants and aborts
|
|
155
|
+
with exit 3 when a file vanishes: detection after the damage, per method,
|
|
156
|
+
with no restoration. Configure the list via `c.hazard`; `--allow-hazardous`
|
|
157
|
+
opts out.
|
|
158
|
+
- Methods defined inside blocks (`Class.new do`, `Data.define do`, `included do`) are listed as
|
|
159
|
+
skipped ("defined inside a block") instead of being silently absent. They are not mutated yet. `X = Data.define` / `Struct.new` / `Class.new` / `Module.new` with a block name X as the owner; any other block keeps the enclosing owner or "(anonymous block)". (#114)
|
|
160
|
+
- Endless defs (`def x = ...`) are no longer skipped as "no coverage": the
|
|
161
|
+
coverage map now also records per-test method calls, which is how Ruby
|
|
162
|
+
reports endless-def bodies. A whole-suite build that records neither a line
|
|
163
|
+
hit nor a method call still stops with a clearer cause message. (#107)
|
|
164
|
+
- Mutants that compile to the same bytecode as the original method are dropped
|
|
165
|
+
before they run, instead of surviving as untestable noise (#151).
|
|
166
|
+
- Mutants on branch arms that no covering test takes are skipped, using per-test
|
|
167
|
+
branch coverage, instead of surviving as findings no test could kill (#184).
|
|
168
|
+
- Diff mode lists every survivor on a changed line instead of only one (#146).
|
|
169
|
+
- Methods with heredoc bodies select all of the method's tests again, and edits
|
|
170
|
+
to pattern-matching arms map to the line that executes, so `--since` selects
|
|
171
|
+
their killers (#130, #110).
|
|
172
|
+
- The empty-coverage error names Bootsnap only when Bootsnap is loaded (#149).
|
|
173
|
+
|
|
174
|
+
## [0.4.0] - 2026-10-12
|
|
175
|
+
|
|
176
|
+
### Changed
|
|
177
|
+
- The terminal report lists every survivor on a changed line in `--since` mode, instead of one
|
|
178
|
+
plus "N more survivors on this line in the JSON report" (#146).
|
|
179
|
+
- **Breaking:** configuration moved from YAML to Ruby. `.saboteur.yml` becomes
|
|
180
|
+
`config/saboteur.rb` (`Saboteur.configure do |c| ... end`, same key names),
|
|
181
|
+
and `.saboteur-ignore.yml` becomes `config/saboteur_ignore.rb`
|
|
182
|
+
(`Saboteur.ignore do |i| i.mutation "<digest>", subject:, reason: end`,
|
|
183
|
+
reason still mandatory). A legacy YAML file stops the run with a pointer to
|
|
184
|
+
the new file instead of being read. Unknown settings fail with the line
|
|
185
|
+
number and a did-you-mean suggestion. `rails generate saboteur:install`
|
|
186
|
+
writes `config/saboteur.rb` (never overwriting without `--force`);
|
|
187
|
+
`config/saboteur_ignore.rb` and `config/saboteur_custom_map.rb` are optional
|
|
188
|
+
and created by hand when needed.
|
|
189
|
+
- A run in which more than half of the executed mutants errored now exits 3
|
|
190
|
+
(the tool failed). Before, only a run where every executed mutant errored
|
|
191
|
+
did. Errored mutants are outside the score, so such a run tested almost
|
|
192
|
+
nothing. This applies with and without `--threshold`. The JSON report records the reason under
|
|
193
|
+
`saboteur.exitCause` (`ok`, `survivors`, `belowThreshold`, `errorMajority`,
|
|
194
|
+
`incompleteUnderThreshold`; in `--agent` mode `ok`, `survivors`,
|
|
195
|
+
`errorMajority` or `incomplete`).
|
|
196
|
+
- A whole-suite coverage build that records no lines for any included file
|
|
197
|
+
now exits 3 and the empty map is not cached, instead of testing nothing.
|
|
198
|
+
With `--test`, an empty map stays a legitimate "no coverage" outcome.
|
|
199
|
+
- Rails fixtures (`spec/fixtures`, `test/fixtures`), `db/structure.sql`,
|
|
200
|
+
`db/schema.rb` and `config/*.yml` are part of the coverage and verdict
|
|
201
|
+
cache fingerprint, so a changed fixture or schema invalidates cached
|
|
202
|
+
results. The new `fingerprint_extra` setting adds other globs whose
|
|
203
|
+
contents should invalidate caches, for example `data/*.csv`.
|
|
204
|
+
- The baseline and coverage phases show progress and write real logs, so a
|
|
205
|
+
slow or failing phase can be diagnosed from the output.
|
|
206
|
+
- When the baseline child is killed by a signal, the message names it
|
|
207
|
+
(`SIGKILL` adds a possible-OOM hint) instead of reporting a bare failure.
|
|
208
|
+
|
|
209
|
+
### Removed
|
|
210
|
+
- The coarse-survivor gate: a surviving whole-body mutant no longer skips the
|
|
211
|
+
method's fine mutants; every eligible fine mutant runs.
|
|
212
|
+
|
|
213
|
+
### Fixed
|
|
214
|
+
- Mutations inside private `module_function` helpers are preserved when the
|
|
215
|
+
method is re-evaluated; they previously reverted and reported as false
|
|
216
|
+
survivors.
|
|
217
|
+
- Rails apps that test against in-memory SQLite (`:memory:`) no longer fail
|
|
218
|
+
in forked workers with "Could not find table": saboteur never disconnects,
|
|
219
|
+
renames or copies an in-memory pool. Each in-memory database is
|
|
220
|
+
snapshotted to a temporary file after boot, and every forked child
|
|
221
|
+
restores the snapshot into its own fresh database.
|
|
222
|
+
- With a Rails app under Bootsnap, Coverage no longer sees an empty map:
|
|
223
|
+
saboteur sets `DISABLE_BOOTSNAP_COMPILE_CACHE=1` for its own process when
|
|
224
|
+
the variable is unset, because bytecode from Bootsnap's compile cache
|
|
225
|
+
carries no coverage counters.
|
|
226
|
+
- A relative SQLite `database:` path resolves against `Rails.root` (as Rails
|
|
227
|
+
7.1+ does) for the per-worker copy and its cleanup. The copy uses SQLite's
|
|
228
|
+
backup API after a WAL checkpoint, refuses to run when a reader keeps the
|
|
229
|
+
WAL busy, and a missing source file is an error rather than a skipped copy.
|
|
230
|
+
- Per-worker database copies (`jobs` above 1) are dropped when the run ends,
|
|
231
|
+
best effort: a failed drop is a warning and never changes the outcome. A
|
|
232
|
+
copy left by a killed run is reclaimed by the next run. A failed copy
|
|
233
|
+
names the sessions blocking it.
|
|
234
|
+
- For `rails: true`, the app's `spec/` directory is on the load path before
|
|
235
|
+
`require` is processed, as with rspec, so a boot file such as a dummy app's
|
|
236
|
+
`spec/rails_helper.rb` can require its siblings.
|
|
237
|
+
- When Rails did not load, the error points engine users at the dummy app's
|
|
238
|
+
environment file or helper (`DummyApp.setup` in `spec/rails_helper.rb`).
|
|
239
|
+
|
|
240
|
+
### Added
|
|
241
|
+
- `rails generate saboteur:install` and a Rails quickstart in the README. The
|
|
242
|
+
generator writes `config/saboteur.rb` and never overwrites without `--force`.
|
|
243
|
+
- The warning about threads alive when a worker forks names those threads,
|
|
244
|
+
falling back to their backtraces for unnamed ones.
|
|
245
|
+
- `--test` no longer requires source operands when `--since` is given: the
|
|
246
|
+
changed files are the sources, so `saboteur run --since origin/main --test
|
|
247
|
+
spec/billing_spec.rb` scopes a pull-request run to chosen specs. A `--since`
|
|
248
|
+
run without `--test` now warns before it starts that the two baseline passes
|
|
249
|
+
run the loaded suite (a cold coverage cache adds one more pass), with the
|
|
250
|
+
spec-file count and how to scope them, instead of going quiet for hours on a
|
|
251
|
+
large app.
|
|
252
|
+
- `config/saboteur_custom_map.rb` declares explicit source-file to spec-file
|
|
253
|
+
mappings (`SABOTEUR_CUSTOM_MAP`): exact paths, globs (the more specific glob
|
|
254
|
+
wins; equal specificity is an error) and Regexps with capture substitution.
|
|
255
|
+
Invalid maps fail the run with the file and key; stale and duplicate keys
|
|
256
|
+
warn on runs, and `saboteur doctor` fails on stale keys. The map is parsed
|
|
257
|
+
and validated only for now - it does not change which specs run yet.
|
|
258
|
+
|
|
259
|
+
- An integration target can declare a known error profile
|
|
260
|
+
(`allow_error_fraction` per strategy in integration/targets.yml): the
|
|
261
|
+
majority-error exit is accepted only when the run's own fresh report
|
|
262
|
+
carries the CLI's `errorMajority` marker, the errored fraction stays
|
|
263
|
+
within the bound, and no mutant hit a resource limit. The JSON report
|
|
264
|
+
records the run's exit cause under `saboteur.exitCause`.
|
|
265
|
+
- A survivor lists the first five tests that covered its line and still passed
|
|
266
|
+
with the mutant applied, in the terminal and the HTML page.
|
|
267
|
+
- In diff mode (`--since`), each survivor names the changed line it relates to
|
|
268
|
+
(`changed line: path:N` in the terminal, `changedLines` in each JSON finding),
|
|
269
|
+
because a multi-line statement is reported at its first line.
|
|
270
|
+
- The score is split into assertion kills and crash kills, in the terminal,
|
|
271
|
+
the JSON report (`saboteur.scores`) and the HTML page. A second score leaves
|
|
272
|
+
out whole-body mutants (`body_nil`, `body_true`, `body_false`, `body_raise`). The headline score and
|
|
273
|
+
`--threshold` are unchanged.
|
|
274
|
+
- `saboteur run --agent` (`--format agent`): one bounded JSON summary (under 8 KiB) on stdout
|
|
275
|
+
with status, verdict, provenance, the first three findings and the commands for full
|
|
276
|
+
detail. Exit 0 means a complete, clean run only: 1 survivors, 2 usage or config error,
|
|
277
|
+
3 incomplete or failed. `--threshold` cannot be combined with it. The next commands are shell-escaped and keep the
|
|
278
|
+
full selection; the working-tree state is read before the run; target output stays on stderr. `saboteur agent-skill`
|
|
279
|
+
prints an AGENTS.md block for coding agents (also in `docs/agent-skill.md`). Classic
|
|
280
|
+
exit codes are unchanged.
|
|
281
|
+
- Fine `pair_removal` mutants for hash literals and keyword lists, including
|
|
282
|
+
sole associations, with token-based separators and the normal method cap.
|
|
283
|
+
- Fine `argument_removal` mutants: remove one ordinary argument from a
|
|
284
|
+
parenthesized call while preserving keywords and blocks. Token-based comma
|
|
285
|
+
edits respect comments and strings. Existing candidate validation, order and
|
|
286
|
+
per-method caps apply. Unparenthesized calls and heredoc arguments are skipped.
|
|
287
|
+
|
|
9
288
|
## [0.3.0] - 2026-10-11
|
|
10
289
|
|
|
11
290
|
### Added
|
|
@@ -64,7 +343,7 @@ First release that does mutation testing. MIT licensed.
|
|
|
64
343
|
### Added
|
|
65
344
|
- `saboteur run` with `--since`, `--subject`, `--timeout-factor`, `--jobs`,
|
|
66
345
|
`--reeval`, `--json`, `--no-cache`, `--incremental`, `--config`.
|
|
67
|
-
- Subjects found with Prism; mutations are byte-range edits: whole-
|
|
346
|
+
- Subjects found with Prism; mutations are byte-range edits: whole-body
|
|
68
347
|
`nil`/`true`/`false`/`raise` first, then comparison, boolean, arithmetic,
|
|
69
348
|
numeric literal, statement removal, condition, negation, safe navigation
|
|
70
349
|
and `||=`.
|