saboteur 0.6.0 → 0.7.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 +367 -0
- data/README.md +166 -40
- data/exe/saboteur +34 -7
- data/lib/saboteur/baseline/workflow.rb +2 -1
- data/lib/saboteur/baseline_health.rb +40 -6
- data/lib/saboteur/cli/exit_status.rb +49 -4
- data/lib/saboteur/cli/help_options.rb +16 -0
- data/lib/saboteur/cli/presets.rb +36 -0
- data/lib/saboteur/cli/run_budget_options.rb +37 -0
- data/lib/saboteur/cli/run_reporting.rb +74 -5
- data/lib/saboteur/cli.rb +67 -46
- data/lib/saboteur/config.rb +66 -11
- data/lib/saboteur/crash_report.rb +154 -0
- data/lib/saboteur/custom_map.rb +3 -1
- data/lib/saboteur/doctor.rb +40 -1
- data/lib/saboteur/engine/baseline_guard.rb +79 -5
- data/lib/saboteur/engine/boot.rb +12 -1
- data/lib/saboteur/engine/canary.rb +41 -10
- data/lib/saboteur/engine/candidate_selection.rb +9 -1
- data/lib/saboteur/engine/coverage_guard.rb +29 -0
- data/lib/saboteur/engine/coverage_store.rb +48 -6
- data/lib/saboteur/engine/diagnostics.rb +23 -2
- data/lib/saboteur/engine/diff_scope.rb +24 -5
- data/lib/saboteur/engine/distribution.rb +124 -8
- data/lib/saboteur/engine/early_stop_recording.rb +46 -0
- data/lib/saboteur/engine/result_cache.rb +2 -1
- data/lib/saboteur/engine/run_budget.rb +63 -0
- data/lib/saboteur/engine/run_fingerprint.rb +4 -0
- data/lib/saboteur/engine/sandbox_policy.rb +18 -11
- data/lib/saboteur/engine/scheduler.rb +114 -18
- data/lib/saboteur/engine/subject_selection.rb +84 -5
- data/lib/saboteur/engine/suite_files.rb +3 -1
- data/lib/saboteur/engine/test_guess.rb +145 -22
- data/lib/saboteur/engine/thread_warnings.rb +53 -0
- data/lib/saboteur/engine/unscoped_suite_warning.rb +41 -0
- data/lib/saboteur/engine/verdict_check.rb +54 -18
- data/lib/saboteur/engine.rb +166 -95
- data/lib/saboteur/execution/protocol.rb +2 -1
- data/lib/saboteur/execution/runner.rb +179 -17
- data/lib/saboteur/execution/sandbox.rb +6 -1
- data/lib/saboteur/execution/supervisor.rb +18 -3
- data/lib/saboteur/execution/worker_pool.rb +87 -7
- data/lib/saboteur/help.rb +55 -11
- data/lib/saboteur/ignore_list.rb +11 -1
- data/lib/saboteur/install.rb +4 -3
- data/lib/saboteur/integration/base.rb +7 -2
- data/lib/saboteur/integration/rspec.rb +40 -4
- data/lib/saboteur/mutation_source/builtin.rb +20 -11
- data/lib/saboteur/mutation_source/hazard_guard.rb +21 -3
- data/lib/saboteur/mutation_source/interpolation_removal.rb +41 -0
- data/lib/saboteur/mutation_source/method_swap.rb +45 -0
- data/lib/saboteur/mutation_source/mutation.rb +5 -3
- data/lib/saboteur/mutation_source/operand_replacement.rb +54 -0
- data/lib/saboteur/mutation_source/operator_examples.rb +4 -1
- data/lib/saboteur/mutation_source/operator_registry.rb +4 -1
- data/lib/saboteur/mutation_source/operator_visit.rb +39 -0
- data/lib/saboteur/preview.rb +2 -1
- data/lib/saboteur/reporter/agent_commands.rb +38 -13
- data/lib/saboteur/reporter/agent_incomplete.rb +27 -2
- data/lib/saboteur/reporter/next_steps.rb +14 -4
- data/lib/saboteur/reporter/score_history.rb +5 -1
- data/lib/saboteur/reporter/stryker.rb +13 -4
- data/lib/saboteur/reporter/survivor_hints.rb +3 -0
- data/lib/saboteur/reporter/terminal.rb +34 -10
- data/lib/saboteur/result.rb +22 -7
- data/lib/saboteur/selection/coverage_map.rb +2 -1
- data/lib/saboteur/selection/git_diff.rb +15 -5
- data/lib/saboteur/selection/untracked_changes.rb +126 -0
- data/lib/saboteur/subject.rb +11 -0
- data/lib/saboteur/text.rb +42 -0
- data/lib/saboteur/version.rb +1 -1
- data/lib/saboteur.rb +16 -1
- metadata +17 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c6e75e9cdd3caa4691a569db12d33348c09b05076865e1f62a96ce9664333b88
|
|
4
|
+
data.tar.gz: dc677720684aa27b4c24120695339961a7cc23327e67f187948abbde77b93e16
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 183c1c0f548c72bb6d6535eb35c5456a74fa0bf02dbd66dd689d5f87424b91cd048e319acfea75532a50568821ea47168508831e1fd034dc67850c3e8c1bdbfe
|
|
7
|
+
data.tar.gz: c9f52334868d552cdde9e0a404cc0b45fde089dfe77dbe30f0b252e8084cb291e121a6728f267fec98437587321d5f9fff36463bb27b19f0281f3c36e3e69fc1
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,373 @@ All notable changes are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.7.0] - 2026-10-16
|
|
10
|
+
|
|
11
|
+
### Upgrade notes
|
|
12
|
+
- Inferred test selection is now the default. Use `--all` or
|
|
13
|
+
`c.test_selection = :all` to keep the 0.6.0 whole-suite behavior; use
|
|
14
|
+
`--test` to name specs explicitly. Missing inferred specs stop before
|
|
15
|
+
boot with exit 2. `--guess-tests` is now a deprecated alias for the
|
|
16
|
+
default; `--no-guess-tests` warns and is ignored.
|
|
17
|
+
- Three new default-on operators change the mutation set, runtime and
|
|
18
|
+
scores. Review survivor baselines and CI thresholds after upgrading;
|
|
19
|
+
use `--exclude-operators` to omit operators you do not want.
|
|
20
|
+
- Harness integrations should handle budget-exhausted runs (exit 4),
|
|
21
|
+
unverified mutants outside the score, and explicit JSON report write
|
|
22
|
+
failures (exit 3). In agent mode, exit codes remain verdict-driven;
|
|
23
|
+
budget exhaustion appears in `incomplete[]` and `earlyStop` instead.
|
|
24
|
+
`--max-duration-ok` does not certify an unscored run.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- Breaking: inferred test selection is now the default. Without `--test`,
|
|
28
|
+
a run picks the spec named after each mutated file and runs only those
|
|
29
|
+
for the baseline passes, the coverage pass and every mutant, as `--test`
|
|
30
|
+
would; with `--since` only the changed files count. A file that needs a
|
|
31
|
+
spec and has none now stops the run before it boots (exit 2) and names
|
|
32
|
+
both ways out, instead of silently running the whole suite. Use `--all`
|
|
33
|
+
or `c.test_selection = :all` to keep the old whole-suite behavior.
|
|
34
|
+
`--guess-tests` is a deprecated alias for the default and
|
|
35
|
+
`--no-guess-tests` warns and is ignored; both flags disappear in a
|
|
36
|
+
future release (#270). A `--subject` run infers only from the named
|
|
37
|
+
subjects' files, so an unrelated spec-less file no longer stops a
|
|
38
|
+
one-method run. The unmatched-file guidance names the expected specs
|
|
39
|
+
and tells you to re-run the same command with `--test` followed by your
|
|
40
|
+
existing spec paths, or to append `--all`, instead of printing a
|
|
41
|
+
reconstructed command. `--test` may stand alone (the configured include
|
|
42
|
+
names the sources), so agent rerun/replay commands stay executable; a
|
|
43
|
+
whole-suite `--all` run is recorded in the agent provenance only when
|
|
44
|
+
no tests resolved, so a configured `:all` combined with named tests
|
|
45
|
+
still produces runnable follow-up commands. Baseline evidence is
|
|
46
|
+
fingerprinted from the declared config before inference narrows the
|
|
47
|
+
test list, so `baseline propose` and `baseline check` agree again
|
|
48
|
+
(#275). Two stale ignore-list entries for
|
|
49
|
+
`Saboteur::CLI#define_execution_options` are gone: full runs no longer
|
|
50
|
+
generate those description-string mutants, so the entries could never
|
|
51
|
+
match again.
|
|
52
|
+
A `--subject` run combined with `--since` now infers specs only for
|
|
53
|
+
named subjects inside the diff scope: an unchanged subject file, one
|
|
54
|
+
whose changes sit in other methods, or one with a comment-only change
|
|
55
|
+
no longer stops the run before boot. Agent provenance records tests
|
|
56
|
+
defined in the config file, so rerun and replay commands keep their
|
|
57
|
+
scope instead of falling back to inference.
|
|
58
|
+
- Report-artifact write semantics (#296): a failed HTML report or score
|
|
59
|
+
history write now warns and lets the run exit by its results, so a
|
|
60
|
+
read-only tree (a sandboxed child holding the project read-only) no
|
|
61
|
+
longer turns a good run into a tool failure. An explicitly requested
|
|
62
|
+
JSON report (`--json` / `c.json_report`) instead fails the run with
|
|
63
|
+
exit 3 - it is the completion evidence an outer harness trusts, and a
|
|
64
|
+
warning could leave a stale or partial file presented as this run's.
|
|
65
|
+
The default `.saboteur/report.json` stays best-effort. Score-history
|
|
66
|
+
soft failures are narrowed to the write's own IO errors; validation,
|
|
67
|
+
integrity, record-size and lock failures stay hard errors.
|
|
68
|
+
- Crash-log fallback hardening (#296): when the cache dir cannot hold
|
|
69
|
+
`internal-error.log`, the fallback in the system temp dir is created
|
|
70
|
+
privately (`O_EXCL | O_NOFOLLOW`, mode 0600) instead of following or
|
|
71
|
+
overwriting a pre-existing path. The pid-keyed fallback path is only
|
|
72
|
+
computed after the primary write fails. Every crash log is now written private (0600) through a
|
|
73
|
+
no-follow, non-blocking open - a planted symlink cannot redirect the
|
|
74
|
+
write and a planted FIFO cannot block it - and the runner reads it
|
|
75
|
+
back the same way, accepting only a regular file owned by the same uid
|
|
76
|
+
with no group/other bits. Where the platform lacks `O_NOFOLLOW` or
|
|
77
|
+
`O_NONBLOCK`, the primary log and the excerpt are omitted rather than
|
|
78
|
+
opened unsafely; the `O_EXCL`-created temp fallback stays safe and can
|
|
79
|
+
still be written. The appended
|
|
80
|
+
excerpt is scrubbed to valid UTF-8 and labelled as freshness-matched,
|
|
81
|
+
not certainly that run's.
|
|
82
|
+
|
|
83
|
+
### Added
|
|
84
|
+
- Dogfood/mutation diagnostics (#296): a rejected kill now reports why the
|
|
85
|
+
killing test failed with the method unchanged - the exception class, its
|
|
86
|
+
message and up to five backtrace frames, bounded to 2000 chars -
|
|
87
|
+
instead of only the first line ("expected: 0"). Filtering frames whose
|
|
88
|
+
paths contain `/gems/rspec` prioritizes project frames; the message and
|
|
89
|
+
frame limits do not guarantee that the failing spec line survives.
|
|
90
|
+
The terminal report
|
|
91
|
+
prints the detail as indented continuation lines and the JSON report's
|
|
92
|
+
`statusReason` keeps every line; one-line reasons are unchanged. An `unverified` verdict also keeps the runner's own
|
|
93
|
+
reason (for example "the --max-duration budget is spent") rather than a
|
|
94
|
+
generic one.
|
|
95
|
+
- Nested-run crash surfacing (#296): a spec that runs saboteur itself (the
|
|
96
|
+
dogfood's own cli_spec) crashes the nested CLI as exit 3, and the crash
|
|
97
|
+
lands in the run's internal-error.log. A child whose tests wrote a fresh
|
|
98
|
+
log appends its first lines to the failure detail, so a verdict check's
|
|
99
|
+
rejection includes crash context instead of only the exit-code diff.
|
|
100
|
+
The excerpt is freshness-matched and may be from another run; it is
|
|
101
|
+
not proof that this nested run wrote the log.
|
|
102
|
+
- `--max-duration` budget-exhaustion semantics (#296): a verdict check or
|
|
103
|
+
timeout calibration the budget cannot admit is skipped rather than run
|
|
104
|
+
with a zero-length timeout, so genuine kills are no longer misclassified
|
|
105
|
+
as errors and cached. Admission is per check and per sequence: a check
|
|
106
|
+
admitted with time left that hits its budget-shortened cap mid-check is
|
|
107
|
+
`unverified`, not a rejected kill, later sequences never fork once the
|
|
108
|
+
budget is spent, and unverified verdicts are never cached. Affected
|
|
109
|
+
mutants report as `unverified` - outside the score - the early stop
|
|
110
|
+
lists their subjects (`unverifiedSubjects` in the JSON report), and the
|
|
111
|
+
CLI run exits 4 unless `--max-duration-ok` is set. Agent mode remains
|
|
112
|
+
verdict-driven and records the budget in `incomplete[]` and `earlyStop`.
|
|
113
|
+
Baseline checks and the coverage map build are now capped
|
|
114
|
+
at the time left in the budget, and the early-stop reason comes from the
|
|
115
|
+
actual truncation state, so mid-subject budget stops report
|
|
116
|
+
`max_duration` even with `--fail-fast`. Agent output names unverified
|
|
117
|
+
mutants in `incomplete[]`, and `--max-duration-ok` only passes a run
|
|
118
|
+
whose verified verdicts (kills, survivors, timeouts) can carry a score.
|
|
119
|
+
- `saboteur pr [REF]` and `saboteur audit` presets: opinionated bundles of
|
|
120
|
+
existing flags - the PR loop (changed lines since REF, one baseline
|
|
121
|
+
check, a ten-minute budget with `--max-duration-ok`) and the audit
|
|
122
|
+
preset (`--all`, all operators, two baseline checks, JSON and HTML
|
|
123
|
+
reports). The PR preset opts out of the budget-specific exit 4, not
|
|
124
|
+
normal or incomplete verdict failures. The audit keeps configured
|
|
125
|
+
`since`, subjects, exclusions and budgets; explicit tests replace its
|
|
126
|
+
injected `--all`. The run prints the expansion up front and explicit
|
|
127
|
+
flags override it (#244).
|
|
128
|
+
- `--max-duration` (config `max_duration`, e.g. `10m`) budgets the whole
|
|
129
|
+
run, baselines included: past the budget the run stops before the next
|
|
130
|
+
subject, reports which selected subjects never ran, marks the JSON and
|
|
131
|
+
agent output partial, and exits 4 in ordinary CLI mode;
|
|
132
|
+
`--max-duration-ok` leaves the exit to the plain verdict (which can
|
|
133
|
+
still fail the run). Agent exits remain verdict-driven, with budget
|
|
134
|
+
exhaustion in `incomplete[]` and `earlyStop`. Works with
|
|
135
|
+
`--jobs` by halting the worker pool (#245).
|
|
136
|
+
- `baseline_passes` (config and `--baseline-passes`) sets how many times the
|
|
137
|
+
unchanged suite runs before mutation (default 2). `1` is the fast PR
|
|
138
|
+
loop; the run announces that it gives up the order-stability
|
|
139
|
+
comparison between passes, and the whole-suite cost message counts
|
|
140
|
+
the configured checks (#55).
|
|
141
|
+
- Usability fixes from 0.5.0 field feedback: an unscoped whole-suite run
|
|
142
|
+
announces the example count and the real number of full passes before
|
|
143
|
+
the first one (a cached map counts as reused only when it loads clean)
|
|
144
|
+
and estimates the remaining baseline and coverage time once the first
|
|
145
|
+
baseline pass is timed; past 1,000 examples it also names `--test` as
|
|
146
|
+
the way to scope (#260). The Next steps footer prints the complete,
|
|
147
|
+
shell-safe replay command carrying the run's own selection options
|
|
148
|
+
and the CLI-only switches that change candidate availability or boot,
|
|
149
|
+
in full however long it gets, instead of asking you to reassemble
|
|
150
|
+
them (#261). `--verbose` prints one line per finished subject with its
|
|
151
|
+
result marks, so a survivor is attributable to a method before the run
|
|
152
|
+
ends (#262). The threads-alive-at-fork warning says why the two known
|
|
153
|
+
threads are expected harmless, and under which conditions, and what an
|
|
154
|
+
unknown thread can break (#263). A `chain_removal` survivor's hint
|
|
155
|
+
appends a conditional equivalence note: if the shorter chain proves
|
|
156
|
+
equivalent across the intended inputs, simplify or ignore (#264).
|
|
157
|
+
- Three new default-on operators: `method_swap` swaps a method with its
|
|
158
|
+
semantic opposite (`min`/`max`, `any?`/`all?`, `map`/`each`; a receiver
|
|
159
|
+
is required, so bare `max(a, b)` is untouched) (#237),
|
|
160
|
+
`operand_replacement` replaces an arithmetic or comparison operation
|
|
161
|
+
with each operand in turn (`a + b` yields `a` and `b`; boolean
|
|
162
|
+
connectors stay with `boolean_logic`) (#238), and
|
|
163
|
+
`interpolation_removal` removes each `#{expr}` and each literal segment
|
|
164
|
+
of an interpolated string (#239). The hazard guard now also recognizes
|
|
165
|
+
`send`, `public_send` and `__send__` with a literal symbol or string
|
|
166
|
+
selector and applies the same checks as for the direct call to the
|
|
167
|
+
resolved selector, with the arguments shifted past it: a destructive
|
|
168
|
+
method name on any receiver (`helper.send(:rmtree, path)`), the
|
|
169
|
+
configured hazard patterns, `Kernel#system` / `Process#spawn`, and
|
|
170
|
+
`Kernel#open` with a "| command" argument all stay suppressed. Dynamic
|
|
171
|
+
selectors stay out of scope as documented (#240).
|
|
172
|
+
- `--since` warns when a changed Ruby file in scope has no subjects
|
|
173
|
+
covering the changed lines, so nothing is mutated from it (#232). A
|
|
174
|
+
silent zero-subject scope used to pass as a clean run. Comment-only or
|
|
175
|
+
formatting-only changes within discovered methods are skipped without
|
|
176
|
+
this warning; the run reports those skipped methods. `--since` also
|
|
177
|
+
warns when a changed Ruby file is covered by an include pattern but
|
|
178
|
+
removed by `c.exclude`, naming up to five such files, so a too-wide
|
|
179
|
+
exclude can no longer dogfood as a clean empty scope (#282).
|
|
180
|
+
|
|
181
|
+
### Fixed
|
|
182
|
+
- `--since` no longer skips untracked files silently (#295): a never-committed
|
|
183
|
+
source file is invisible to `git diff`, so it used to contribute no changed
|
|
184
|
+
lines and every subject in it was skipped without a word - even the
|
|
185
|
+
changed-files-without-subjects warning could not name it. Untracked files
|
|
186
|
+
now union into the changed-lines map as fully changed, so their subjects
|
|
187
|
+
are selected, and one without subjects triggers the warning like any other
|
|
188
|
+
changed file. Ignored (`.gitignore`), binary and empty files stay out, and
|
|
189
|
+
only files inside the configured `include` set are considered, so a large
|
|
190
|
+
unrelated untracked file is never opened. Each unioned file is opened
|
|
191
|
+
`O_NOFOLLOW`/`O_NONBLOCK` and validated on that descriptor (a swapped-in
|
|
192
|
+
symlink or FIFO is rejected and reported as unreadable), the binary sniff reads only the first
|
|
193
|
+
few kilobytes, and the map holds a range of lines instead of one entry per
|
|
194
|
+
line, keeping memory bounded. On platforms without those open flags the
|
|
195
|
+
union is skipped with a warning.
|
|
196
|
+
- `--since` no longer drops an include-eligible untracked file quietly when
|
|
197
|
+
it cannot be read (vanished, unreadable, or replaced by a link or special
|
|
198
|
+
file between the listing and the open): the run names it in a warning and
|
|
199
|
+
records it as a discovery failure, so an agent run over mixed tested and
|
|
200
|
+
omitted source reports `partial`/`incomplete` with the `unreadable` reason
|
|
201
|
+
instead of a clean result. Files that do not parse (`invalid_file_syntax`)
|
|
202
|
+
surface the same way instead of only warning (#297).
|
|
203
|
+
- Include membership for untracked files is lexical first: an included path
|
|
204
|
+
swapped to a symlink is attempted, rejected by the safe open and reported,
|
|
205
|
+
instead of being filtered out silently on its resolved target. A symlinked
|
|
206
|
+
repository root stays in scope through the canonical fallback (#297).
|
|
207
|
+
- Test inference under `--since` no longer enumerates a whole-file change
|
|
208
|
+
range per method when deciding whether a changed file needs a spec; it
|
|
209
|
+
answers ranges by interval, like the run's own subject selection (#297).
|
|
210
|
+
- The excluded-changes warning now also names untracked files in excluded
|
|
211
|
+
territory: they never appear in the diff, so an exclude that removes one
|
|
212
|
+
used to hide the removal entirely (#297).
|
|
213
|
+
- Distance and overlap checks over the whole-file ranges the untracked
|
|
214
|
+
union produces are now geometric, so subject selection, test guessing and
|
|
215
|
+
survivor ranking answer in O(1) per subject instead of scanning one entry
|
|
216
|
+
per line of a large new file (#297).
|
|
217
|
+
- A baseline check or coverage map build that hit only its
|
|
218
|
+
budget-shortened timeout failed the run as a suite error (exit 3) with
|
|
219
|
+
no partial report; it now reports budget exhaustion with every selected
|
|
220
|
+
subject unreached and exits 4 in ordinary CLI mode unless
|
|
221
|
+
`--max-duration-ok` is set; agent exits remain verdict-driven (#296).
|
|
222
|
+
- The unscoped-suite warning pluralizes zero baseline checks correctly
|
|
223
|
+
("0 baseline checks", not "0 baseline check") (#296).
|
|
224
|
+
- Review hardening for the 0.5.0 loop flags (#245): the `--max-duration`
|
|
225
|
+
clock starts when the run starts (boot, baselines and the coverage map
|
|
226
|
+
all spend the budget) and is checked before dispatching every subject,
|
|
227
|
+
between mutants inside a subject, and between baseline passes, in
|
|
228
|
+
serial and `--jobs` runs alike; a parallel budget halt stops handing
|
|
229
|
+
out new work and lets in-flight subjects finish and report, so their
|
|
230
|
+
completed outcomes are kept instead of abandoned as nil holes
|
|
231
|
+
mislabeled "never ran". `--max-duration-ok`
|
|
232
|
+
no longer exits 0 when the budget stopped the run before any mutant
|
|
233
|
+
was tested - there is no score to judge, so the run is incomplete
|
|
234
|
+
(exit 3), in agent mode too. Agent output records `failFast` only for
|
|
235
|
+
a real `--fail-fast` stop, `--baseline-passes 1` no longer certifies
|
|
236
|
+
the suite stable in the baseline evidence, and the rerun/replay
|
|
237
|
+
commands keep `--max-duration`, `--max-duration-ok` and
|
|
238
|
+
`--baseline-passes`. `saboteur audit` bundles the `--operators all
|
|
239
|
+
--baseline-passes 2` its help advertises, an explicit `--test`
|
|
240
|
+
replaces its injected `--all`, and `--no-max-duration-ok` overrides
|
|
241
|
+
the pr preset's budget pass.
|
|
242
|
+
- macOS CI flakes: the supervisor `on_tick` cleanup spec waits for a fully
|
|
243
|
+
written pid before triggering instead of racing the file's first bytes
|
|
244
|
+
(#277), fixture repositories disable Git background auto-maintenance so
|
|
245
|
+
teardown no longer races its lock deletion (#271), and a baseline child
|
|
246
|
+
that exits without a result now has its exit status or signal in the
|
|
247
|
+
error and its dying exception on stderr, so the next flake names its
|
|
248
|
+
cause (#255). The same error carries a bounded tail of the child's
|
|
249
|
+
redirected log, the dying-child warning names its stage ("baseline
|
|
250
|
+
child died during setup/run", "coverage child died during the map
|
|
251
|
+
build"), and a broken child stderr can no longer swallow the exit
|
|
252
|
+
(#289).
|
|
253
|
+
- `Saboteur::Engine#call` restores the host process's Coverage state when
|
|
254
|
+
it returns or raises: an embedded engine (a spec, the dogfood's own
|
|
255
|
+
coverage-map build) no longer leaves Coverage suspended for everything
|
|
256
|
+
the host runs afterwards, and a resume that fails because the host
|
|
257
|
+
disturbed Coverage mid-run warns instead of masking the run outcome,
|
|
258
|
+
with worker cleanup running either way (#254). A scoped run (`--since`) no longer
|
|
259
|
+
calls an ignore entry stale when its mutation exists but sits on an
|
|
260
|
+
unchanged line and was scoped out; staleness is judged against each
|
|
261
|
+
examined subject's full, pre-scope candidate digests (#284). A worker
|
|
262
|
+
start-failure storm while the pool is already draining no longer raises
|
|
263
|
+
"workers keep failing to start" over the tool errors that carry the
|
|
264
|
+
real abort cause (#286).
|
|
265
|
+
- The canary no longer blames a method's last mutant for deleted files:
|
|
266
|
+
a cheap per-mutant probe (snapshot stats only, about 1.3 ms for this
|
|
267
|
+
repo's 692 watched files against a full inventory's 5 ms) runs after
|
|
268
|
+
every mutant, so the abort names the mutant whose run just ended when
|
|
269
|
+
the loss was detected as the likely cause - with the per-method check
|
|
270
|
+
as the backstop for losses outside a mutant's window (#235).
|
|
271
|
+
- Parallel runs aborted by a wiped log dir now name the culprit: a
|
|
272
|
+
worker that only noticed the damage could report "an unknown mutant"
|
|
273
|
+
before the worker whose mutant did it, and the first report won the
|
|
274
|
+
abort. Tool-error reports now carry whether they name a mutant, the
|
|
275
|
+
pool stops handing out new work but lets in-flight workers finish
|
|
276
|
+
and report, and the abort ranks the reports by attribution quality:
|
|
277
|
+
a report that names its mutant first, then a canary trip, then the
|
|
278
|
+
earliest bare report. A canary trip from the end-of-subject check
|
|
279
|
+
folds into the same choice instead of overriding collected reports,
|
|
280
|
+
and the in-flight drain is bounded (a 30-second default drain
|
|
281
|
+
window, followed by worker cleanup), so a wedged worker cannot
|
|
282
|
+
stall the abort (#235).
|
|
283
|
+
- `saboteur doctor` prints the coverage line once instead of twice (it is
|
|
284
|
+
still verified before and after target boot), and reports the sandbox
|
|
285
|
+
status as a kernel capability probe, not verified isolation: the
|
|
286
|
+
Landlock ABI when the kernel can deliver it, the gap when it cannot -
|
|
287
|
+
told apart as a `c.sandbox = "strict"` refusal versus the permitted
|
|
288
|
+
degradation of a non-strict run - or off when `c.sandbox = false`. A
|
|
289
|
+
probe the kernel refuses (EPERM under a seccomp policy) is an
|
|
290
|
+
actionable tool error with exit 3, not an internal-error crash report,
|
|
291
|
+
and doctor writes nothing (#236). The sandbox policy
|
|
292
|
+
no longer creates the project's `tmp/` directory to grant it: the
|
|
293
|
+
grant applies when `tmp/` already exists and only `c.sandbox_writable`
|
|
294
|
+
entries are created (#236).
|
|
295
|
+
- The mutant sandbox no longer grants the cache dir: a mutant child
|
|
296
|
+
cannot delete the baseline, the reports or other children's logs,
|
|
297
|
+
and its own log is opened before the fork and inherited as a
|
|
298
|
+
descriptor, with stdio kept explicitly through the sandbox sweep.
|
|
299
|
+
A child log that cannot be opened, or that vanishes mid-run with
|
|
300
|
+
the sandbox off or degraded, aborts the run with exit 3 - the
|
|
301
|
+
canary's hazard report names the culprit when tracked files
|
|
302
|
+
vanished too, otherwise the tool error does - never a mutant
|
|
303
|
+
verdict such as "killing test also fails with the method
|
|
304
|
+
unchanged"; in parallel runs the error aborts the whole run
|
|
305
|
+
instead of degrading into per-mutant errors. The grants are
|
|
306
|
+
additive, so a cache dir placed inside
|
|
307
|
+
`tmp/`, the system temp dir or a `c.sandbox_writable` entry stays
|
|
308
|
+
writable. Note the sandbox prevents the damage, not the verdict:
|
|
309
|
+
with `--allow-hazardous`, a destructive mutant whose test swallows
|
|
310
|
+
the denial (EACCES) can still read as a survivor; by default the
|
|
311
|
+
hazard guard suppresses such mutants and reports the hazard (#234).
|
|
312
|
+
- An extensionless `hooks` path now boots: it resolves its `.rb` at load
|
|
313
|
+
time, the way `require` does and matching what doctor accepts (#267).
|
|
314
|
+
- Fixed two encoding failures in mutant children running under a non-UTF-8
|
|
315
|
+
locale such as `LANG=C` (seen on CI, where the Landlock sandbox engages):
|
|
316
|
+
the inherited-fd sweep compared a locale-tagged descriptor target against
|
|
317
|
+
grant patterns with string semantics, raising
|
|
318
|
+
`Encoding::CompatibilityError` before any test ran when an open file's
|
|
319
|
+
path carried non-ASCII bytes; and an exception message embedding such a
|
|
320
|
+
path made failure reporting raise the same way, turning a killed mutant
|
|
321
|
+
into an errored run. The sweep now compares raw bytes, and failure
|
|
322
|
+
messages are scrubbed before their first line is extracted; if the
|
|
323
|
+
scrub itself raises - it is mutable code, and the mutant under test
|
|
324
|
+
may have broken it - an escaped fallback keeps the failure a clean
|
|
325
|
+
kill instead of an errored run (#272).
|
|
326
|
+
- Fixed `--guess-tests` falling back to the whole suite when a changed
|
|
327
|
+
file has no mutation subjects on its changed lines, such as a deletion
|
|
328
|
+
whose remaining context is blank or comment lines or a comment-only
|
|
329
|
+
change: such files no longer require a guessed spec. A changed file
|
|
330
|
+
that does need a spec but has none now stops before boot with exit 2,
|
|
331
|
+
matching the default-inference policy above (#275).
|
|
332
|
+
Guesses come only from files with a changed mutation subject, and when
|
|
333
|
+
no changed file has one the run reports that there is nothing to mutate
|
|
334
|
+
(fixes #268).
|
|
335
|
+
- Fixed failures under non-UTF-8 locales such as `LANG=C` when project
|
|
336
|
+
paths contain non-ASCII characters. Git output, coverage maps, result
|
|
337
|
+
caches, baseline logs and worker messages are now decoded as UTF-8
|
|
338
|
+
regardless of the process locale. This prevents
|
|
339
|
+
`Encoding::CompatibilityError` in the canary, baseline failure excerpts
|
|
340
|
+
and worker results, and fixes `--since` silently skipping changed files
|
|
341
|
+
with non-ASCII names. Paths are identity, not display text: the canary
|
|
342
|
+
watch, `--since` diff parsing, source discovery and the run fingerprint
|
|
343
|
+
retag locale-tagged bytes losslessly, so a tracked file whose name is
|
|
344
|
+
not valid UTF-8 stays watched (its deletion still trips the canary) and
|
|
345
|
+
keeps its exact bytes through selection. The canary and diff parser
|
|
346
|
+
preserve path bytes rather than replacing invalid UTF-8 bytes; the
|
|
347
|
+
cache, coverage-map and worker-message boundaries reject invalid UTF-8
|
|
348
|
+
instead of rewriting it.
|
|
349
|
+
- An unexpected internal error now prints `saboteur: internal error (...)
|
|
350
|
+
- please report it` and exits 3, instead of escaping as a bare stack
|
|
351
|
+
trace with an accidental exit 1. The crash message is normalized to one
|
|
352
|
+
bounded line first, and agent mode reports the same failure as agent
|
|
353
|
+
JSON. The full backtrace is written to
|
|
354
|
+
`<cache_dir>/internal-error.log` (default `.saboteur/`), which the
|
|
355
|
+
message names, and printed to stderr with `--verbose`; an unset
|
|
356
|
+
`cache_dir` or a failed write leaves the one-liner standing alone.
|
|
357
|
+
The executable loads the crash reporter before the CLI,
|
|
358
|
+
so a failure while it is loading, being built, or running gets
|
|
359
|
+
the full report. Coverage setup and the reporter's own load happen before
|
|
360
|
+
it, so those failures get only the bare normalized one-liner plus
|
|
361
|
+
exit 3. When agent mode was requested but the agent reporter never
|
|
362
|
+
loaded, a minimal standalone failure JSON in the agent reporter's
|
|
363
|
+
schema (status `error`, verdict `tool_failure`, exit code 3)
|
|
364
|
+
answers instead (#233).
|
|
365
|
+
- The install spec reads the README as UTF-8, so the suite also passes
|
|
366
|
+
under `LANG=C`.
|
|
367
|
+
- Tool and test reads of files that can carry non-ASCII text are decoded
|
|
368
|
+
as UTF-8 regardless of the process locale: the baseline manifest and
|
|
369
|
+
preview receipt, `.rspec` option files, the custom-map duplicate scan,
|
|
370
|
+
the installer's Gemfile and `.gitignore` probes (`.gitignore` is
|
|
371
|
+
rewritten losslessly), and the JSON reads in the test helpers.
|
|
372
|
+
- CI now runs the suite on Ubuntu Ruby 3.4 with `LANG=C` and `LC_ALL=C`
|
|
373
|
+
to catch locale regressions exercised by the tests. The panel gate's
|
|
374
|
+
required-check policy includes this job (#281).
|
|
375
|
+
|
|
9
376
|
## [0.6.0] - 2026-10-14
|
|
10
377
|
|
|
11
378
|
### Fixed
|