saboteur 0.4.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 +167 -0
- data/README.md +393 -20
- data/exe/saboteur +6 -2
- data/lib/saboteur/agent_skill.rb +6 -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 +101 -121
- data/lib/saboteur/config.rb +76 -6
- data/lib/saboteur/doctor.rb +40 -8
- data/lib/saboteur/engine/baseline_guard.rb +33 -11
- 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 +34 -14
- data/lib/saboteur/engine/diagnostics.rb +150 -4
- data/lib/saboteur/engine/diff_scope.rb +10 -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 +6 -4
- data/lib/saboteur/engine/sandbox_policy.rb +108 -0
- data/lib/saboteur/engine/scheduler.rb +92 -20
- data/lib/saboteur/engine/subject_selection.rb +24 -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 +192 -18
- data/lib/saboteur/execution/protocol.rb +2 -1
- data/lib/saboteur/execution/reevaluator.rb +72 -10
- 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 +101 -9
- data/lib/saboteur/execution/worker_pool.rb +20 -4
- data/lib/saboteur/execution/wrapper.rb +45 -0
- data/lib/saboteur/gates.rb +83 -0
- data/lib/saboteur/help.rb +31 -7
- data/lib/saboteur/ignore_list.rb +5 -0
- data/lib/saboteur/inline_suppression.rb +65 -0
- data/lib/saboteur/integration/base.rb +1 -0
- data/lib/saboteur/integration/focus_guard.rb +83 -0
- data/lib/saboteur/integration/rspec.rb +21 -1
- data/lib/saboteur/mutation_source/builtin.rb +22 -3
- 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/range_replacement.rb +43 -0
- data/lib/saboteur/preview.rb +106 -0
- data/lib/saboteur/reporter/agent_commands.rb +7 -1
- data/lib/saboteur/reporter/agent_incomplete.rb +24 -1
- data/lib/saboteur/reporter/agent_summary.rb +11 -5
- data/lib/saboteur/reporter/findings.rb +4 -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 +12 -1
- 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 +15 -2
- 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 +131 -79
- data/lib/saboteur/reporter/test_matrix.rb +57 -0
- data/lib/saboteur/result.rb +41 -6
- 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 +8 -0
- data/lib/saboteur.rb +6 -0
- metadata +43 -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,9 +6,176 @@ 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
|
+
|
|
9
174
|
## [0.4.0] - 2026-10-12
|
|
10
175
|
|
|
11
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).
|
|
12
179
|
- **Breaking:** configuration moved from YAML to Ruby. `.saboteur.yml` becomes
|
|
13
180
|
`config/saboteur.rb` (`Saboteur.configure do |c| ... end`, same key names),
|
|
14
181
|
and `.saboteur-ignore.yml` becomes `config/saboteur_ignore.rb`
|