evilution 0.17.0 → 0.19.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/.beads/.migration-hint-ts +1 -1
- data/.beads/issues.jsonl +103 -33
- data/CHANGELOG.md +50 -0
- data/README.md +144 -50
- data/lib/evilution/ast/sorbet_sig_detector.rb +52 -0
- data/lib/evilution/baseline.rb +9 -1
- data/lib/evilution/cli.rb +398 -23
- data/lib/evilution/config.rb +10 -2
- data/lib/evilution/disable_comment.rb +90 -0
- data/lib/evilution/integration/rspec.rb +74 -5
- data/lib/evilution/isolation/fork.rb +10 -6
- data/lib/evilution/isolation/in_process.rb +14 -10
- data/lib/evilution/mcp/session_diff_tool.rb +5 -35
- data/lib/evilution/mutator/operator/collection_return.rb +33 -0
- data/lib/evilution/mutator/operator/defined_check.rb +16 -0
- data/lib/evilution/mutator/operator/keyword_argument.rb +91 -0
- data/lib/evilution/mutator/operator/multiple_assignment.rb +47 -0
- data/lib/evilution/mutator/operator/regex_capture.rb +43 -0
- data/lib/evilution/mutator/operator/scalar_return.rb +37 -0
- data/lib/evilution/mutator/operator/splat_operator.rb +46 -0
- data/lib/evilution/mutator/operator/yield_statement.rb +51 -0
- data/lib/evilution/mutator/registry.rb +9 -1
- data/lib/evilution/parallel/pool.rb +7 -53
- data/lib/evilution/parallel/work_queue.rb +265 -0
- data/lib/evilution/reporter/cli.rb +21 -1
- data/lib/evilution/reporter/html.rb +69 -3
- data/lib/evilution/reporter/json.rb +23 -2
- data/lib/evilution/reporter/suggestion.rb +29 -1
- data/lib/evilution/result/mutation_result.rb +5 -2
- data/lib/evilution/result/summary.rb +19 -2
- data/lib/evilution/runner.rb +123 -12
- data/lib/evilution/session/diff.rb +85 -0
- data/lib/evilution/spec_resolver.rb +13 -1
- data/lib/evilution/version.rb +1 -1
- data/lib/evilution.rb +11 -0
- data/script/memory_check +22 -0
- metadata +14 -2
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.19.0] - 2026-04-07
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Smart spec auto-detection** — `SpecResolver` maps source files to the closest matching spec using Rails conventions: controllers to request specs (`app/controllers/foo_controller.rb` → `spec/requests/foo_spec.rb`), models, services, Avo resources, and lib/ paths; falls back through parent directory patterns when exact match doesn't exist; warns when falling back to full suite so users know to use `--spec` (#530, #555)
|
|
8
|
+
- **`--spec-dir DIR` CLI flag** — include all `*_spec.rb` files in a directory recursively; composable with `--spec` for combining explicit files and directories (#513)
|
|
9
|
+
- **RSS tracking per mutation** — JSON output includes per-mutation RSS memory measurements for profiling memory behavior across mutations (#532)
|
|
10
|
+
- **Memory budget CI gate** — dedicated benchmark workflow with memory check step; uses realistic project classes as fixtures (#533, #567)
|
|
11
|
+
- **Worker hang protection** — `WorkQueue` item timeout prevents indefinite worker hangs; timeout handling for worker timing collection (#558, #559)
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **InProcess `suppress_output` closing `/dev/null` handle** — prevent closing the shared `/dev/null` file descriptor which caused subsequent output suppression to fail (#569)
|
|
16
|
+
- **Double `Process.wait` in Fork isolation** — handle empty child processes without raising (#561)
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **RSpec integration memory management** — use clear hooks to release AST nodes and source strings between mutations, preventing memory retention across mutation runs (#543)
|
|
21
|
+
|
|
22
|
+
## [0.18.0] - 2026-04-03
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **Disable comments** — `# evilution:disable` comments to suppress mutations on specific lines, methods, or regions; inline disable for single lines, standalone disable before `def` for entire methods, range disable/enable pairs for arbitrary regions; `--show-disabled` flag reports skipped mutations in CLI, JSON, and HTML output (#321, #323, #325)
|
|
27
|
+
- **Sorbet `sig` filtering** — automatically detects and excludes mutations inside Sorbet `sig { ... }` blocks; cached per file for performance (#330, #334)
|
|
28
|
+
- **Session diff engine** — `Evilution::Session::Diff` compares two saved sessions, reporting fixed mutations, new survivors, persistent survivors, and score delta; identity matching by `[operator, file, line, subject]` (#333)
|
|
29
|
+
- **`session diff` CLI command** — `evilution session diff <base> <head>` with color-coded text output (green=fixed, red=new survivors, yellow=persistent) and `--format json` support (#336)
|
|
30
|
+
- **HTML report baseline comparison** — `--baseline-session PATH` overlays a saved session on the HTML report, highlighting regressions with badges and showing score delta (#339)
|
|
31
|
+
- **`util mutation` CLI command** — `evilution util mutation [-e CODE | FILE]` previews all mutations for a source file or inline Ruby snippet; supports `--format json` (#328)
|
|
32
|
+
- **`subjects` CLI command** — `evilution subjects [files...]` lists all mutation subjects (methods) with file locations and mutation counts; supports `--stdin` (#322)
|
|
33
|
+
- **`tests list` CLI command** — `evilution tests list [files...]` lists spec files mapped to source files via `SpecResolver` (#326)
|
|
34
|
+
- **`environment show` CLI command** — `evilution environment show` displays runtime environment: version, Ruby version, config path, and all active settings (#319)
|
|
35
|
+
- **Type-aware return mutation operators** — `CollectionReturn` replaces collection return values with type-aware alternatives (`[]`, `{}`); `ScalarReturn` replaces scalar return values with type-aware alternatives (`0`, `""`, `nil`) (#300, #304)
|
|
36
|
+
- **Keyword argument mutations** — `KeywordArgument` operator removes default values, removes optional keywords entirely, and removes `**kwargs` rest parameters (#345)
|
|
37
|
+
- **Multiple assignment mutations** — `MultipleAssignment` operator removes individual assignment targets and swaps 2-element order (#346)
|
|
38
|
+
- **Yield statement mutations** — `YieldStatement` operator removes yield, removes yield arguments, and replaces yield value with `nil` (#347)
|
|
39
|
+
- **Splat operator mutations** — `SplatOperator` operator removes `*` (splat) and `**` (double-splat) from method calls and array literals (#348)
|
|
40
|
+
- **`defined?` check mutations** — `DefinedCheck` operator replaces `defined?(expr)` with `true` (#356)
|
|
41
|
+
- **Regex capture reference mutations** — `RegexCapture` operator swaps numbered capture references (`$1`↔`$2`) and replaces with `nil` (#357)
|
|
42
|
+
- **Suggestion templates** — concrete RSpec suggestions for `collection_return` and `scalar_return` operators (#308)
|
|
43
|
+
- **Efficiency metrics** — summary output includes `efficiency` (killtime/wall-clock ratio), `mutations_per_second` throughput, and `killtime` aggregate; reported in CLI, JSON, and HTML (#313)
|
|
44
|
+
- **Parallel execution metrics** — worker statistics tracking with `busy_time`, `wall_time`, `idle_time`, and `utilization` per worker (#314)
|
|
45
|
+
- **Demand-driven work distribution** — `Parallel::Pool` uses pipe-based shared work queue with demand-driven dispatch and configurable prefetch; replaces batch-based distribution (#303, #307, #311)
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- **Operator count** — 60 operators (up from 52), with new return-type, keyword, assignment, yield, splat, defined?, and regex capture operators
|
|
50
|
+
- **CLI reporter** — survived mutations now include subject name and code diffs (#341)
|
|
51
|
+
- **Dependency updates** — Ruby 3.3.10 → 3.3.11 in CI (#447), ruby/setup-ruby 1.295.0 → 1.299.0, rubygems/release-gem 1.1.4 → 1.2.0
|
|
52
|
+
|
|
3
53
|
## [0.17.0] - 2026-03-30
|
|
4
54
|
|
|
5
55
|
### Added
|
data/README.md
CHANGED
|
@@ -29,27 +29,43 @@ evilution [command] [options] [files...]
|
|
|
29
29
|
|
|
30
30
|
### Commands
|
|
31
31
|
|
|
32
|
-
| Command
|
|
33
|
-
|
|
34
|
-
| `run`
|
|
35
|
-
| `init`
|
|
36
|
-
| `version`
|
|
32
|
+
| Command | Description | Default |
|
|
33
|
+
|----------------------|----------------------------------------------------|---------|
|
|
34
|
+
| `run` | Execute mutation testing against files | Yes |
|
|
35
|
+
| `init` | Generate `.evilution.yml` config file | |
|
|
36
|
+
| `version` | Print version string | |
|
|
37
|
+
| `subjects [files]` | List mutation subjects with locations and counts | |
|
|
38
|
+
| `tests list [files]` | List spec files mapped to source files | |
|
|
39
|
+
| `session list` | List saved session results | |
|
|
40
|
+
| `session show FILE` | Display detailed session results | |
|
|
41
|
+
| `session diff A B` | Compare two sessions (fixed/new/persistent) | |
|
|
42
|
+
| `session gc --older-than D` | Garbage-collect sessions older than D (e.g. 30d) | |
|
|
43
|
+
| `util mutation` | Preview mutations for a file or inline code | |
|
|
44
|
+
| `environment show` | Display runtime environment and settings | |
|
|
37
45
|
|
|
38
46
|
### Options (for `run` command)
|
|
39
47
|
|
|
40
|
-
| Flag
|
|
41
|
-
|
|
42
|
-
| `-t`, `--timeout N`
|
|
43
|
-
| `-f`, `--format FORMAT`
|
|
44
|
-
| `--target
|
|
45
|
-
| `--min-score FLOAT`
|
|
46
|
-
| `--spec FILES`
|
|
47
|
-
|
|
|
48
|
-
| `--
|
|
49
|
-
| `--
|
|
50
|
-
|
|
|
51
|
-
| `--
|
|
52
|
-
|
|
|
48
|
+
| Flag | Type | Default | Description |
|
|
49
|
+
|------------------------------|---------|--------------|---------------------------------------------------|
|
|
50
|
+
| `-t`, `--timeout N` | Integer | 30 | Per-mutation timeout in seconds. |
|
|
51
|
+
| `-f`, `--format FORMAT` | String | `text` | Output format: `text`, `json`, or `html`. |
|
|
52
|
+
| `--target EXPR` | String | _(none)_ | Only mutate matching methods. Supports method name (`Foo::Bar#calculate`), class (`Foo`), namespace wildcards (`Foo::Bar*`), method-type selectors (`Foo#`, `Foo.`), descendants (`descendants:Foo`), and source globs (`source:lib/**/*.rb`). |
|
|
53
|
+
| `--min-score FLOAT` | Float | 0.0 | Minimum mutation score (0.0–1.0) to pass. |
|
|
54
|
+
| `--spec FILES` | Array | _(none)_ | Spec files to run (comma-separated). Defaults to auto-detection via `SpecResolver`. |
|
|
55
|
+
| `--spec-dir DIR` | String | _(none)_ | Include all `*_spec.rb` files in DIR recursively. Composable with `--spec`. |
|
|
56
|
+
| `-j`, `--jobs N` | Integer | 1 | Number of parallel workers. Uses demand-driven work distribution with pipe-based IPC. |
|
|
57
|
+
| `--no-baseline` | Boolean | _(enabled)_ | Skip baseline test suite check. By default, a baseline run detects pre-existing failures and marks those mutations as `neutral`. |
|
|
58
|
+
| `--fail-fast [N]` | Integer | _(none)_ | Stop after N surviving mutants (default 1 if no value given). |
|
|
59
|
+
| `-v`, `--verbose` | Boolean | false | Verbose output with RSS memory and GC stats per phase and per mutation. |
|
|
60
|
+
| `--suggest-tests` | Boolean | false | Generate concrete RSpec test code in suggestions instead of static descriptions. |
|
|
61
|
+
| `-q`, `--quiet` | Boolean | false | Suppress output. |
|
|
62
|
+
| `--stdin` | Boolean | false | Read target file paths from stdin (one per line). |
|
|
63
|
+
| `--incremental` | Boolean | false | Cache killed/timeout results; skip unchanged mutations on re-runs. |
|
|
64
|
+
| `--save-session` | Boolean | false | Persist results as timestamped JSON under `.evilution/results/`. |
|
|
65
|
+
| `--no-progress` | Boolean | _(enabled)_ | Disable the TTY progress bar. |
|
|
66
|
+
| `--show-disabled` | Boolean | false | Report mutations skipped by `# evilution:disable` comments. |
|
|
67
|
+
| `--baseline-session PATH` | String | _(none)_ | Saved session file for HTML report comparison. |
|
|
68
|
+
| `-e CODE`, `--eval CODE` | String | _(none)_ | Inline Ruby code for `util mutation` command. |
|
|
53
69
|
|
|
54
70
|
### Exit Codes
|
|
55
71
|
|
|
@@ -66,15 +82,43 @@ Generate default config: `bundle exec evilution init`
|
|
|
66
82
|
Creates `.evilution.yml`:
|
|
67
83
|
|
|
68
84
|
```yaml
|
|
69
|
-
# timeout:
|
|
70
|
-
# format: text
|
|
71
|
-
# min_score: 0.0
|
|
72
|
-
# integration: rspec
|
|
73
|
-
# suggest_tests: false
|
|
85
|
+
# timeout: 30 # seconds per mutation
|
|
86
|
+
# format: text # text | json | html
|
|
87
|
+
# min_score: 0.0 # 0.0–1.0
|
|
88
|
+
# integration: rspec # test framework
|
|
89
|
+
# suggest_tests: false # concrete RSpec test code in suggestions
|
|
90
|
+
# save_session: false # persist results under .evilution/results/
|
|
91
|
+
# show_disabled: false # report mutations skipped by disable comments
|
|
92
|
+
# baseline_session: null # path to session file for HTML comparison
|
|
93
|
+
# ignore_patterns: [] # AST patterns to exclude (see docs/ast_pattern_syntax.md)
|
|
94
|
+
# progress: true # TTY progress bar
|
|
74
95
|
```
|
|
75
96
|
|
|
76
97
|
**Precedence**: CLI flags override `.evilution.yml` values.
|
|
77
98
|
|
|
99
|
+
## Disable Comments
|
|
100
|
+
|
|
101
|
+
Suppress mutations on specific code with inline comments:
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
# Disable a single line
|
|
105
|
+
log(message) # evilution:disable
|
|
106
|
+
|
|
107
|
+
# Disable an entire method (place comment immediately before def)
|
|
108
|
+
# evilution:disable
|
|
109
|
+
def infrastructure_method
|
|
110
|
+
# no mutations generated for this method body
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Disable a region
|
|
114
|
+
# evilution:disable
|
|
115
|
+
setup_logging
|
|
116
|
+
configure_metrics
|
|
117
|
+
# evilution:enable
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Use `--show-disabled` to see which mutations were skipped.
|
|
121
|
+
|
|
78
122
|
## JSON Output Schema
|
|
79
123
|
|
|
80
124
|
Use `--format json` for machine-readable output. Schema:
|
|
@@ -112,30 +156,72 @@ Use `--format json` for machine-readable output. Schema:
|
|
|
112
156
|
|
|
113
157
|
**Key metric**: `summary.score` — the mutation score. Higher is better. 1.0 means all mutations were caught.
|
|
114
158
|
|
|
115
|
-
## Mutation Operators (
|
|
159
|
+
## Mutation Operators (60 total)
|
|
116
160
|
|
|
117
161
|
Each operator name is stable and appears in JSON output under `survived[].operator`.
|
|
118
162
|
|
|
119
|
-
| Operator
|
|
120
|
-
|
|
121
|
-
| `arithmetic_replacement`
|
|
122
|
-
| `comparison_replacement`
|
|
123
|
-
| `boolean_operator_replacement` | Swap `&&` / `\|\|`
|
|
124
|
-
| `boolean_literal_replacement`
|
|
125
|
-
| `nil_replacement`
|
|
126
|
-
| `integer_literal`
|
|
127
|
-
| `float_literal`
|
|
128
|
-
| `string_literal`
|
|
129
|
-
| `array_literal`
|
|
130
|
-
| `hash_literal`
|
|
131
|
-
| `symbol_literal`
|
|
132
|
-
| `conditional_negation`
|
|
133
|
-
| `conditional_branch`
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
163
|
+
| Operator | What it does | Example |
|
|
164
|
+
|---|---|---|
|
|
165
|
+
| `arithmetic_replacement` | Swap arithmetic operators | `a + b` -> `a - b` |
|
|
166
|
+
| `comparison_replacement` | Swap comparison operators | `a >= b` -> `a > b` |
|
|
167
|
+
| `boolean_operator_replacement` | Swap `&&` / `\|\|` | `a && b` -> `a \|\| b` |
|
|
168
|
+
| `boolean_literal_replacement` | Flip boolean literals | `true` -> `false` |
|
|
169
|
+
| `nil_replacement` | Replace `nil` with `true`, `false`, `0`, `""` | `nil` -> `true` |
|
|
170
|
+
| `integer_literal` | Boundary-value integer mutations | `n` -> `0`, `1`, `n+1`, `n-1` |
|
|
171
|
+
| `float_literal` | Boundary-value float mutations | `f` -> `0.0`, `1.0` |
|
|
172
|
+
| `string_literal` | Empty the string | `"str"` -> `""` |
|
|
173
|
+
| `array_literal` | Empty the array | `[a, b]` -> `[]` |
|
|
174
|
+
| `hash_literal` | Empty the hash | `{k: v}` -> `{}` |
|
|
175
|
+
| `symbol_literal` | Replace with sentinel symbol | `:foo` -> `:__evilution_mutated__` |
|
|
176
|
+
| `conditional_negation` | Replace condition with `true`/`false` | `if cond` -> `if true` |
|
|
177
|
+
| `conditional_branch` | Remove if/else branch | Deletes branch body |
|
|
178
|
+
| `conditional_flip` | Flip `if` to `unless` and vice versa | `if cond` -> `unless cond` |
|
|
179
|
+
| `statement_deletion` | Remove statements from method bodies | Deletes a statement |
|
|
180
|
+
| `method_body_replacement` | Replace entire method body with `nil` | Method body -> `nil` |
|
|
181
|
+
| `negation_insertion` | Negate predicate methods | `x.empty?` -> `!x.empty?` |
|
|
182
|
+
| `return_value_removal` | Strip return values | `return x` -> `return` |
|
|
183
|
+
| `collection_replacement` | Swap collection methods | `map` -> `each`, `select` <-> `reject` |
|
|
184
|
+
| `collection_return` | Replace collection return values | `return [1]` -> `return []` |
|
|
185
|
+
| `scalar_return` | Replace scalar return values | `return 42` -> `return 0` |
|
|
186
|
+
| `method_call_removal` | Remove method calls, keep receiver | `obj.foo(x)` -> `obj` |
|
|
187
|
+
| `argument_removal` | Remove individual arguments | `foo(a, b)` -> `foo(b)` |
|
|
188
|
+
| `argument_nil_substitution` | Replace arguments with `nil` | `foo(a, b)` -> `foo(nil, b)` |
|
|
189
|
+
| `keyword_argument` | Remove keyword defaults/params | `def foo(bar: 42)` -> `def foo(bar:)` |
|
|
190
|
+
| `multiple_assignment` | Remove targets or swap order | `a, b = 1, 2` -> `b, a = 1, 2` |
|
|
191
|
+
| `block_removal` | Remove blocks from method calls | `items.map { \|x\| x * 2 }` -> `items.map` |
|
|
192
|
+
| `range_replacement` | Swap inclusive/exclusive ranges | `1..10` -> `1...10` |
|
|
193
|
+
| `regexp_mutation` | Replace regexp with always/never matching | `/pat/` -> `/a\A/` |
|
|
194
|
+
| `receiver_replacement` | Drop explicit `self` receiver | `self.foo` -> `foo` |
|
|
195
|
+
| `send_mutation` | Swap semantically related methods | `detect` -> `find`, `map` -> `flat_map` |
|
|
196
|
+
| `compound_assignment` | Swap compound assignment operators | `+=` -> `-=`, `&&=` -> `\|\|=` |
|
|
197
|
+
| `local_variable_assignment` | Replace variable assignment with `nil` | `x = expr` -> `x = nil` |
|
|
198
|
+
| `instance_variable_write` | Replace ivar assignment with `nil` | `@x = expr` -> `@x = nil` |
|
|
199
|
+
| `class_variable_write` | Replace cvar assignment with `nil` | `@@x = expr` -> `@@x = nil` |
|
|
200
|
+
| `global_variable_write` | Replace gvar assignment with `nil` | `$x = expr` -> `$x = nil` |
|
|
201
|
+
| `mixin_removal` | Remove include/extend/prepend | `include Foo` -> removed |
|
|
202
|
+
| `superclass_removal` | Remove class inheritance | `class Foo < Bar` -> `class Foo` |
|
|
203
|
+
| `rescue_removal` | Remove rescue clauses | Deletes rescue block |
|
|
204
|
+
| `rescue_body_replacement` | Replace rescue body with `nil` | Rescue body -> `nil` |
|
|
205
|
+
| `inline_rescue` | Remove inline rescue fallback | `expr rescue val` -> `expr` |
|
|
206
|
+
| `ensure_removal` | Remove ensure blocks | Deletes ensure block |
|
|
207
|
+
| `break_statement` | Remove break statements | `break` -> removed |
|
|
208
|
+
| `next_statement` | Remove next statements | `next` -> removed |
|
|
209
|
+
| `redo_statement` | Remove redo statements | `redo` -> removed |
|
|
210
|
+
| `bang_method` | Swap bang with non-bang methods | `sort!` -> `sort` |
|
|
211
|
+
| `bitwise_replacement` | Swap bitwise operators | `a & b` -> `a \| b` |
|
|
212
|
+
| `bitwise_complement` | Remove or swap `~` | `~x` -> `x`, `~x` -> `-x` |
|
|
213
|
+
| `zsuper_removal` | Replace implicit `super` with `nil` | `super` -> `nil` |
|
|
214
|
+
| `explicit_super_mutation` | Mutate explicit super arguments | `super(a, b)` -> `super` |
|
|
215
|
+
| `index_to_fetch` | Replace `[]` with `.fetch()` | `h[k]` -> `h.fetch(k)` |
|
|
216
|
+
| `index_to_dig` | Replace `[]` chains with `.dig()` | `h[a][b]` -> `h.dig(a, b)` |
|
|
217
|
+
| `index_assignment_removal` | Remove `[]=` assignments | `h[k] = v` -> removed |
|
|
218
|
+
| `pattern_matching_guard` | Remove/negate pattern guards | `in x if cond` -> `in x` |
|
|
219
|
+
| `pattern_matching_alternative` | Remove/reorder alternatives | `pat1 \| pat2` -> `pat1` |
|
|
220
|
+
| `pattern_matching_array` | Remove/wildcard array elements | `[a, b]` -> `[a, _]` |
|
|
221
|
+
| `yield_statement` | Remove yield or its arguments | `yield(x)` -> `yield` |
|
|
222
|
+
| `splat_operator` | Remove splat/double-splat | `foo(*args)` -> `foo(args)` |
|
|
223
|
+
| `defined_check` | Replace `defined?` with `true` | `defined?(x)` -> `true` |
|
|
224
|
+
| `regex_capture` | Swap or nil-ify capture refs | `$1` -> `$2`, `$1` -> `nil` |
|
|
139
225
|
|
|
140
226
|
## MCP Server (AI Agent Integration)
|
|
141
227
|
|
|
@@ -160,7 +246,14 @@ Create a `.mcp.json` file in your project root:
|
|
|
160
246
|
|
|
161
247
|
If using Bundler, set the command to `bundle` and args to `["exec", "evilution", "mcp"]`.
|
|
162
248
|
|
|
163
|
-
The server exposes
|
|
249
|
+
The server exposes the following tools:
|
|
250
|
+
|
|
251
|
+
| Tool | Description |
|
|
252
|
+
|---|---|
|
|
253
|
+
| `evilution-mutate` | Run mutation testing on target files with structured JSON results |
|
|
254
|
+
| `evilution-session-list` | Browse saved session history |
|
|
255
|
+
| `evilution-session-show` | Display detailed session results |
|
|
256
|
+
| `evilution-session-diff` | Compare two sessions (fixed/new/persistent survivors, score delta) |
|
|
164
257
|
|
|
165
258
|
### Verbosity Control
|
|
166
259
|
|
|
@@ -274,11 +367,12 @@ Tests 4 paths (InProcess isolation, Fork isolation, mutation generation + stripp
|
|
|
274
367
|
|
|
275
368
|
1. **Parse** — Prism parses Ruby files into ASTs with exact byte offsets
|
|
276
369
|
2. **Extract** — Methods are identified as mutation subjects
|
|
277
|
-
3. **
|
|
278
|
-
4. **
|
|
279
|
-
5. **
|
|
280
|
-
6. **
|
|
281
|
-
7. **
|
|
370
|
+
3. **Filter** — Disable comments, Sorbet `sig` blocks, and AST ignore patterns exclude mutations before execution
|
|
371
|
+
4. **Mutate** — 60 operators produce text replacements at precise byte offsets (source-level surgery, no AST unparsing)
|
|
372
|
+
5. **Isolate** — Default isolation is in-process; `--isolation fork` uses forked child processes. Parallel mode (`--jobs N`) always uses in-process isolation inside pool workers to avoid double forking
|
|
373
|
+
6. **Test** — RSpec executes against the mutated source
|
|
374
|
+
7. **Collect** — Source strings and AST nodes are released after use to minimize memory retention
|
|
375
|
+
8. **Report** — Results aggregated into text, JSON, or HTML, including efficiency metrics and peak memory usage
|
|
282
376
|
|
|
283
377
|
## Repository
|
|
284
378
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
class Evilution::AST::SorbetSigDetector
|
|
6
|
+
def call(source)
|
|
7
|
+
return [] if source.empty?
|
|
8
|
+
|
|
9
|
+
result = Prism.parse(source)
|
|
10
|
+
return [] if result.failure?
|
|
11
|
+
|
|
12
|
+
ranges = []
|
|
13
|
+
collect_sig_ranges(result.value, ranges, :byte)
|
|
14
|
+
ranges
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def line_ranges(source)
|
|
18
|
+
return [] if source.empty?
|
|
19
|
+
|
|
20
|
+
result = Prism.parse(source)
|
|
21
|
+
return [] if result.failure?
|
|
22
|
+
|
|
23
|
+
ranges = []
|
|
24
|
+
collect_sig_ranges(result.value, ranges, :line)
|
|
25
|
+
ranges
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def collect_sig_ranges(node, ranges, mode)
|
|
31
|
+
if sig_block?(node)
|
|
32
|
+
loc = node.location
|
|
33
|
+
ranges << if mode == :byte
|
|
34
|
+
(loc.start_offset...loc.end_offset)
|
|
35
|
+
else
|
|
36
|
+
(loc.start_line..loc.end_line)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
node.child_nodes.each do |child|
|
|
41
|
+
collect_sig_ranges(child, ranges, mode) if child
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def sig_block?(node)
|
|
46
|
+
node.is_a?(Prism::CallNode) &&
|
|
47
|
+
node.name == :sig &&
|
|
48
|
+
node.receiver.nil? &&
|
|
49
|
+
node.arguments.nil? &&
|
|
50
|
+
!node.block.nil?
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/evilution/baseline.rb
CHANGED
|
@@ -93,6 +93,14 @@ class Evilution::Baseline
|
|
|
93
93
|
private
|
|
94
94
|
|
|
95
95
|
def resolve_unique_spec_files(subjects)
|
|
96
|
-
|
|
96
|
+
warned = Set.new
|
|
97
|
+
subjects.map do |s|
|
|
98
|
+
resolved = @spec_resolver.call(s.file_path)
|
|
99
|
+
if resolved.nil? && warned.add?(s.file_path)
|
|
100
|
+
warn "[evilution] No matching spec found for #{s.file_path}, running full suite. " \
|
|
101
|
+
"Use --spec to specify the spec file."
|
|
102
|
+
end
|
|
103
|
+
resolved || "spec"
|
|
104
|
+
end.uniq
|
|
97
105
|
end
|
|
98
106
|
end
|