saboteur 0.2.0 → 0.4.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 +153 -1
- data/README.md +526 -73
- data/lib/generators/saboteur/install/install_generator.rb +21 -0
- data/lib/saboteur/agent_skill.rb +40 -0
- data/lib/saboteur/baseline_health.rb +116 -0
- data/lib/saboteur/cli.rb +305 -30
- data/lib/saboteur/config.rb +65 -19
- data/lib/saboteur/config_file.rb +97 -0
- data/lib/saboteur/custom_map.rb +170 -0
- data/lib/saboteur/doctor.rb +88 -0
- data/lib/saboteur/engine/baseline_guard.rb +116 -0
- data/lib/saboteur/engine/boot.rb +9 -0
- data/lib/saboteur/engine/candidate_selection.rb +35 -0
- data/lib/saboteur/engine/coverage_store.rb +40 -6
- data/lib/saboteur/engine/data_inputs.rb +43 -0
- data/lib/saboteur/engine/diagnostics.rb +28 -0
- data/lib/saboteur/engine/diff_scope.rb +8 -0
- data/lib/saboteur/engine/distribution.rb +4 -2
- data/lib/saboteur/engine/run_fingerprint.rb +344 -0
- data/lib/saboteur/engine/scheduler.rb +7 -16
- data/lib/saboteur/engine/suite_files.rb +8 -1
- data/lib/saboteur/engine/verdict_check.rb +4 -2
- data/lib/saboteur/engine.rb +125 -25
- data/lib/saboteur/execution/fork_readiness.rb +46 -0
- data/lib/saboteur/execution/reevaluator.rb +7 -4
- data/lib/saboteur/execution/supervisor.rb +23 -11
- data/lib/saboteur/frameworks/rails.rb +329 -13
- data/lib/saboteur/help.rb +210 -0
- data/lib/saboteur/ignore_list.rb +71 -17
- data/lib/saboteur/install.rb +132 -0
- data/lib/saboteur/integration/base.rb +8 -0
- data/lib/saboteur/integration/rspec.rb +69 -4
- data/lib/saboteur/mutation_source/argument_removal.rb +86 -0
- data/lib/saboteur/mutation_source/builtin.rb +14 -3
- data/lib/saboteur/mutation_source/filter_replacement.rb +60 -0
- data/lib/saboteur/mutation_source/literal_replacement.rb +74 -0
- data/lib/saboteur/mutation_source/pair_removal.rb +101 -0
- data/lib/saboteur/replay.rb +33 -0
- data/lib/saboteur/reporter/agent.rb +87 -0
- data/lib/saboteur/reporter/agent_clipping.rb +29 -0
- data/lib/saboteur/reporter/agent_commands.rb +61 -0
- data/lib/saboteur/reporter/agent_incomplete.rb +37 -0
- data/lib/saboteur/reporter/agent_summary.rb +131 -0
- data/lib/saboteur/reporter/findings.rb +131 -0
- data/lib/saboteur/reporter/html.rb +213 -0
- data/lib/saboteur/reporter/replay.rb +23 -0
- data/lib/saboteur/reporter/stryker.rb +21 -4
- data/lib/saboteur/reporter/terminal.rb +76 -2
- data/lib/saboteur/result.rb +61 -9
- data/lib/saboteur/score.rb +89 -0
- data/lib/saboteur/version.rb +1 -1
- data/lib/saboteur/working_tree.rb +48 -0
- data/lib/saboteur.rb +7 -0
- metadata +30 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a5c52cb7485073b569359bf940160095370103747d55a6065b7f067726a1a53c
|
|
4
|
+
data.tar.gz: 40fca0c1c0f8391de86d7f17960d908add226682d93be2610775fc888ab36f60
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 061e0b5a97c95048048767d3724ea883ea144e7530b9a52631d52c896f4154e1a63f7b65ef7b5817053a42ab31eb97c168aa3079c87e9d9a3e8be1ae949d7bdf
|
|
7
|
+
data.tar.gz: e29bcfafdaf2fc7ecffbc4f8746349d79c908dfc2de1530998b525c691135c2bfb4ade67575374178742b1fddb29ab96a7d9ff67656d29dbd7c096ba0b50d0dc
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,158 @@ All notable changes are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.4.0] - 2026-10-12
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- **Breaking:** configuration moved from YAML to Ruby. `.saboteur.yml` becomes
|
|
13
|
+
`config/saboteur.rb` (`Saboteur.configure do |c| ... end`, same key names),
|
|
14
|
+
and `.saboteur-ignore.yml` becomes `config/saboteur_ignore.rb`
|
|
15
|
+
(`Saboteur.ignore do |i| i.mutation "<digest>", subject:, reason: end`,
|
|
16
|
+
reason still mandatory). A legacy YAML file stops the run with a pointer to
|
|
17
|
+
the new file instead of being read. Unknown settings fail with the line
|
|
18
|
+
number and a did-you-mean suggestion. `rails generate saboteur:install`
|
|
19
|
+
writes `config/saboteur.rb` (never overwriting without `--force`);
|
|
20
|
+
`config/saboteur_ignore.rb` and `config/saboteur_custom_map.rb` are optional
|
|
21
|
+
and created by hand when needed.
|
|
22
|
+
- A run in which more than half of the executed mutants errored now exits 3
|
|
23
|
+
(the tool failed). Before, only a run where every executed mutant errored
|
|
24
|
+
did. Errored mutants are outside the score, so such a run tested almost
|
|
25
|
+
nothing. This applies with and without `--threshold`. The JSON report records the reason under
|
|
26
|
+
`saboteur.exitCause` (`ok`, `survivors`, `belowThreshold`, `errorMajority`,
|
|
27
|
+
`incompleteUnderThreshold`; in `--agent` mode `ok`, `survivors`,
|
|
28
|
+
`errorMajority` or `incomplete`).
|
|
29
|
+
- A whole-suite coverage build that records no lines for any included file
|
|
30
|
+
now exits 3 and the empty map is not cached, instead of testing nothing.
|
|
31
|
+
With `--test`, an empty map stays a legitimate "no coverage" outcome.
|
|
32
|
+
- Rails fixtures (`spec/fixtures`, `test/fixtures`), `db/structure.sql`,
|
|
33
|
+
`db/schema.rb` and `config/*.yml` are part of the coverage and verdict
|
|
34
|
+
cache fingerprint, so a changed fixture or schema invalidates cached
|
|
35
|
+
results. The new `fingerprint_extra` setting adds other globs whose
|
|
36
|
+
contents should invalidate caches, for example `data/*.csv`.
|
|
37
|
+
- The baseline and coverage phases show progress and write real logs, so a
|
|
38
|
+
slow or failing phase can be diagnosed from the output.
|
|
39
|
+
- When the baseline child is killed by a signal, the message names it
|
|
40
|
+
(`SIGKILL` adds a possible-OOM hint) instead of reporting a bare failure.
|
|
41
|
+
|
|
42
|
+
### Removed
|
|
43
|
+
- The coarse-survivor gate: a surviving whole-body mutant no longer skips the
|
|
44
|
+
method's fine mutants; every eligible fine mutant runs.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
- Mutations inside private `module_function` helpers are preserved when the
|
|
48
|
+
method is re-evaluated; they previously reverted and reported as false
|
|
49
|
+
survivors.
|
|
50
|
+
- Rails apps that test against in-memory SQLite (`:memory:`) no longer fail
|
|
51
|
+
in forked workers with "Could not find table": saboteur never disconnects,
|
|
52
|
+
renames or copies an in-memory pool. Each in-memory database is
|
|
53
|
+
snapshotted to a temporary file after boot, and every forked child
|
|
54
|
+
restores the snapshot into its own fresh database.
|
|
55
|
+
- With a Rails app under Bootsnap, Coverage no longer sees an empty map:
|
|
56
|
+
saboteur sets `DISABLE_BOOTSNAP_COMPILE_CACHE=1` for its own process when
|
|
57
|
+
the variable is unset, because bytecode from Bootsnap's compile cache
|
|
58
|
+
carries no coverage counters.
|
|
59
|
+
- A relative SQLite `database:` path resolves against `Rails.root` (as Rails
|
|
60
|
+
7.1+ does) for the per-worker copy and its cleanup. The copy uses SQLite's
|
|
61
|
+
backup API after a WAL checkpoint, refuses to run when a reader keeps the
|
|
62
|
+
WAL busy, and a missing source file is an error rather than a skipped copy.
|
|
63
|
+
- Per-worker database copies (`jobs` above 1) are dropped when the run ends,
|
|
64
|
+
best effort: a failed drop is a warning and never changes the outcome. A
|
|
65
|
+
copy left by a killed run is reclaimed by the next run. A failed copy
|
|
66
|
+
names the sessions blocking it.
|
|
67
|
+
- For `rails: true`, the app's `spec/` directory is on the load path before
|
|
68
|
+
`require` is processed, as with rspec, so a boot file such as a dummy app's
|
|
69
|
+
`spec/rails_helper.rb` can require its siblings.
|
|
70
|
+
- When Rails did not load, the error points engine users at the dummy app's
|
|
71
|
+
environment file or helper (`DummyApp.setup` in `spec/rails_helper.rb`).
|
|
72
|
+
|
|
73
|
+
### Added
|
|
74
|
+
- `rails generate saboteur:install` and a Rails quickstart in the README. The
|
|
75
|
+
generator writes `config/saboteur.rb` and never overwrites without `--force`.
|
|
76
|
+
- The warning about threads alive when a worker forks names those threads,
|
|
77
|
+
falling back to their backtraces for unnamed ones.
|
|
78
|
+
- `--test` no longer requires source operands when `--since` is given: the
|
|
79
|
+
changed files are the sources, so `saboteur run --since origin/main --test
|
|
80
|
+
spec/billing_spec.rb` scopes a pull-request run to chosen specs. A `--since`
|
|
81
|
+
run without `--test` now warns before it starts that the two baseline passes
|
|
82
|
+
run the loaded suite (a cold coverage cache adds one more pass), with the
|
|
83
|
+
spec-file count and how to scope them, instead of going quiet for hours on a
|
|
84
|
+
large app.
|
|
85
|
+
- `config/saboteur_custom_map.rb` declares explicit source-file to spec-file
|
|
86
|
+
mappings (`SABOTEUR_CUSTOM_MAP`): exact paths, globs (the more specific glob
|
|
87
|
+
wins; equal specificity is an error) and Regexps with capture substitution.
|
|
88
|
+
Invalid maps fail the run with the file and key; stale and duplicate keys
|
|
89
|
+
warn on runs, and `saboteur doctor` fails on stale keys. The map is parsed
|
|
90
|
+
and validated only for now - it does not change which specs run yet.
|
|
91
|
+
|
|
92
|
+
- An integration target can declare a known error profile
|
|
93
|
+
(`allow_error_fraction` per strategy in integration/targets.yml): the
|
|
94
|
+
majority-error exit is accepted only when the run's own fresh report
|
|
95
|
+
carries the CLI's `errorMajority` marker, the errored fraction stays
|
|
96
|
+
within the bound, and no mutant hit a resource limit. The JSON report
|
|
97
|
+
records the run's exit cause under `saboteur.exitCause`.
|
|
98
|
+
- A survivor lists the first five tests that covered its line and still passed
|
|
99
|
+
with the mutant applied, in the terminal and the HTML page.
|
|
100
|
+
- In diff mode (`--since`), each survivor names the changed line it relates to
|
|
101
|
+
(`changed line: path:N` in the terminal, `changedLines` in each JSON finding),
|
|
102
|
+
because a multi-line statement is reported at its first line.
|
|
103
|
+
- The score is split into assertion kills and crash kills, in the terminal,
|
|
104
|
+
the JSON report (`saboteur.scores`) and the HTML page. A second score leaves
|
|
105
|
+
out whole-body mutants (`body_nil`, `body_true`, `body_false`, `body_raise`). The headline score and
|
|
106
|
+
`--threshold` are unchanged.
|
|
107
|
+
- `saboteur run --agent` (`--format agent`): one bounded JSON summary (under 8 KiB) on stdout
|
|
108
|
+
with status, verdict, provenance, the first three findings and the commands for full
|
|
109
|
+
detail. Exit 0 means a complete, clean run only: 1 survivors, 2 usage or config error,
|
|
110
|
+
3 incomplete or failed. `--threshold` cannot be combined with it. The next commands are shell-escaped and keep the
|
|
111
|
+
full selection; the working-tree state is read before the run; target output stays on stderr. `saboteur agent-skill`
|
|
112
|
+
prints an AGENTS.md block for coding agents (also in `docs/agent-skill.md`). Classic
|
|
113
|
+
exit codes are unchanged.
|
|
114
|
+
- Fine `pair_removal` mutants for hash literals and keyword lists, including
|
|
115
|
+
sole associations, with token-based separators and the normal method cap.
|
|
116
|
+
- Fine `argument_removal` mutants: remove one ordinary argument from a
|
|
117
|
+
parenthesized call while preserving keywords and blocks. Token-based comma
|
|
118
|
+
edits respect comments and strings. Existing candidate validation, order and
|
|
119
|
+
per-method caps apply. Unparenthesized calls and heredoc arguments are skipped.
|
|
120
|
+
|
|
121
|
+
## [0.3.0] - 2026-10-11
|
|
122
|
+
|
|
123
|
+
### Added
|
|
124
|
+
- Fine `literal_boolean` and `literal_string` mutants: true/false swaps and
|
|
125
|
+
nonempty static string replacement with `""`. Body-only traversal excludes
|
|
126
|
+
arid/nested scopes, interpolation, heredocs and non-string text literals.
|
|
127
|
+
New proposals can change scores and per-method-cap coverage.
|
|
128
|
+
- `saboteur run --id DIGEST`: replay one exact current mutant with selected tests,
|
|
129
|
+
independent of caps, coarse gating, ignore suppression and incremental reuse.
|
|
130
|
+
Prints the mutation diff and selected test ids for every replay result.
|
|
131
|
+
- Review findings in the JSON report (`saboteur.findings`,
|
|
132
|
+
`Saboteur::Reporter::Findings`): a bounded, deterministically ordered list
|
|
133
|
+
of survivors (coarse first, then file, line and digest) with the file,
|
|
134
|
+
line, digest, subject, operator, original and replacement a note on a
|
|
135
|
+
pull-request diff needs. Survivors accepted on the ignore list are listed
|
|
136
|
+
separately with their reason; `survivorsTruncated` counts the rest, which
|
|
137
|
+
stay in the full `files` data.
|
|
138
|
+
- Self-contained HTML report (`Saboteur::Reporter::Html`): one file with inline
|
|
139
|
+
style and no script or network use, written to `.saboteur/report.html`.
|
|
140
|
+
Change the path with `--html PATH` or `html_report:`. It shows the score,
|
|
141
|
+
per-file mutants, survivors, warnings and skipped subjects.
|
|
142
|
+
- A GitHub Action (`uses: vamsipavanmahesh/saboteur`) that runs diff mode on pull
|
|
143
|
+
requests. Advisory by default, with a job summary, outputs and a cached
|
|
144
|
+
coverage map. See `docs/github-action.md`.
|
|
145
|
+
- Built-in `filter_replacement` mutants: `select` / `reject` and
|
|
146
|
+
`select!` / `reject!`, for explicit-receiver calls with a block and no ordinary
|
|
147
|
+
arguments. For unchanged subjects, existing mutants retain their digests and order.
|
|
148
|
+
Runs may now include more mutants and report a different score.
|
|
149
|
+
- `saboteur doctor [--config PATH] [--seed N]`: environment checks and two fresh
|
|
150
|
+
unchanged-suite checks, without mutation reports or coverage/verdict cache writes.
|
|
151
|
+
- `saboteur run SOURCE... --test TEST...`: mutate the named source files and
|
|
152
|
+
run only the named spec files or directories. Positional sources replace
|
|
153
|
+
`include` and `exclude`; `--test` replaces the spec paths in `rspec_args`.
|
|
154
|
+
- `--threshold PERCENT` (and `threshold:` in `.saboteur.yml`): exit 1 when the
|
|
155
|
+
score is below PERCENT. The score decides the exit code instead of survivors.
|
|
156
|
+
It fails closed: no score exits 1, and errored or resource-limited mutants
|
|
157
|
+
exit 3.
|
|
158
|
+
- Positional sources must exist (exit 2 otherwise), and the selected tests are
|
|
159
|
+
part of the coverage-map and `--incremental` cache keys.
|
|
160
|
+
|
|
9
161
|
## [0.2.0] - 2026-10-10
|
|
10
162
|
|
|
11
163
|
### Added
|
|
@@ -24,7 +176,7 @@ First release that does mutation testing. MIT licensed.
|
|
|
24
176
|
### Added
|
|
25
177
|
- `saboteur run` with `--since`, `--subject`, `--timeout-factor`, `--jobs`,
|
|
26
178
|
`--reeval`, `--json`, `--no-cache`, `--incremental`, `--config`.
|
|
27
|
-
- Subjects found with Prism; mutations are byte-range edits: whole-
|
|
179
|
+
- Subjects found with Prism; mutations are byte-range edits: whole-body
|
|
28
180
|
`nil`/`true`/`false`/`raise` first, then comparison, boolean, arithmetic,
|
|
29
181
|
numeric literal, statement removal, condition, negation, safe navigation
|
|
30
182
|
and `||=`.
|