evilution 0.19.0 → 0.20.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 +22 -22
- data/CHANGELOG.md +18 -0
- data/README.md +22 -3
- data/lib/evilution/equivalent/detector.rb +3 -1
- data/lib/evilution/equivalent/heuristic/alias_swap.rb +2 -1
- data/lib/evilution/equivalent/heuristic/void_context.rb +77 -0
- data/lib/evilution/integration/crash_detector.rb +55 -0
- data/lib/evilution/integration/rspec.rb +32 -5
- data/lib/evilution/mutator/operator/begin_unwrap.rb +21 -0
- data/lib/evilution/mutator/operator/block_param_removal.rb +57 -0
- data/lib/evilution/mutator/operator/case_when.rb +55 -0
- data/lib/evilution/mutator/operator/equality_to_identity.rb +22 -0
- data/lib/evilution/mutator/operator/lambda_body.rb +18 -0
- data/lib/evilution/mutator/operator/loop_flip.rb +27 -0
- data/lib/evilution/mutator/operator/method_body_replacement.rb +10 -6
- data/lib/evilution/mutator/operator/predicate_replacement.rb +27 -0
- data/lib/evilution/mutator/operator/retry_removal.rb +16 -0
- data/lib/evilution/mutator/operator/send_mutation.rb +8 -1
- data/lib/evilution/mutator/operator/string_interpolation.rb +32 -0
- data/lib/evilution/mutator/registry.rb +10 -1
- data/lib/evilution/related_spec_heuristic.rb +63 -0
- data/lib/evilution/reporter/cli.rb +14 -8
- data/lib/evilution/reporter/html.rb +32 -2
- data/lib/evilution/reporter/json.rb +14 -0
- data/lib/evilution/result/coverage_gap.rb +35 -0
- data/lib/evilution/result/coverage_gap_grouper.rb +22 -0
- data/lib/evilution/result/summary.rb +5 -0
- data/lib/evilution/session/store.rb +13 -0
- data/lib/evilution/version.rb +1 -1
- data/lib/evilution.rb +9 -0
- metadata +16 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5a29c1829a6efd420e69c38c337aca7e7c17235100ae7b7fcb01ad73126a18ce
|
|
4
|
+
data.tar.gz: 18aa38d26193200903caf412743c9388ffc5543df0fbbe9b3adad812e7706884
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f3b1afc25478422c42e42494db9ff6fe6319728542443e622454e5474f375a23ab44c74251206e3e45f82bf67b5878a738d24ece0122fc7e40a01c3c95be312f
|
|
7
|
+
data.tar.gz: d6e2c898e9e1da7ebe11bcd75fac31023a898125b0fbb55d0db5899ffbbad92d3e977e9a974ce205d8d75bfac38ae90c01ba68281dfac221203b7b17c1fd2ed3
|
data/.beads/.migration-hint-ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
1775553347
|
data/.beads/issues.jsonl
CHANGED
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
{"id":"EV-196","title":"Show code diffs for survived mutations in text output","description":"Show the exact code diff (original vs mutated) for each survived mutation in the text reporter output, similar to how Mutant displays survived mutants. Currently Evilution shows the mutation description but not the visual diff. This was specifically requested in comparison testing feedback as it helps users quickly understand what test is missing.","status":"in_progress","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:21:02.574157928+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-03T11:00:52.543373742+07:00"}
|
|
115
115
|
{"id":"EV-197","title":"Implement module include/extend removal","description":"Mutate include/extend/prepend statements: remove each one individually. Tests whether the mixin is actually used.","status":"in_progress","priority":3,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:21:13.978482592+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-28T23:57:39.685072781+07:00","dependencies":[{"issue_id":"EV-197","depends_on_id":"EV-194","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
116
116
|
{"id":"EV-198","title":"Add RSpec suggestion templates for class/module mutations","description":"Add concrete suggestion templates for survived class/module mutations.","status":"in_progress","priority":3,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:21:23.284834592+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-28T23:47:56.899533924+07:00","dependencies":[{"issue_id":"EV-198","depends_on_id":"EV-194","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
117
|
-
{"id":"EV-199","title":"Implement keyword argument mutations","description":"Add mutations for keyword arguments and optional keyword argument defaults. Mutations: remove keyword arg default value, swap keyword arg with positional, remove optional keyword. Prism keyword_hash_node, keyword_rest_parameter_node.","notes":"GitHub: #492","status":"
|
|
117
|
+
{"id":"EV-199","title":"Implement keyword argument mutations","description":"Add mutations for keyword arguments and optional keyword argument defaults. Mutations: remove keyword arg default value, swap keyword arg with positional, remove optional keyword. Prism keyword_hash_node, keyword_rest_parameter_node.","notes":"GitHub: #492","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:21:33.74262984+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-07T12:55:11.315819563+07:00","closed_at":"2026-04-07T12:55:11.315819563+07:00","close_reason":"Already implemented in v0.18.0: keyword_argument operator removes defaults, removes optional keywords, removes **kwargs rest parameters. Shipped with spec coverage."}
|
|
118
118
|
{"id":"EV-2","title":"Phase 1: Foundation — End-to-End Single Mutation","description":"Build the core pipeline: parse Ruby with Prism, generate mutations, fork-based isolation, RSpec integration, JSON reporting. Milestone: Runner.new(files: ['lib/user.rb']).call produces JSON output.","status":"closed","priority":2,"issue_type":"epic","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:04:58.737191467+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:02:00.342745637+07:00","closed_at":"2026-03-02T11:02:00.342745637+07:00","close_reason":"Phase 1 Foundation complete: Config, AST::Parser, Subject, SourceSurgeon, Mutation, Mutator::Base+Registry, ComparisonReplacement, Isolation::Fork, Integration::RSpec, Result objects, Reporter::JSON, Runner — all 13 tasks done"}
|
|
119
119
|
{"id":"EV-2.1","title":"Implement Evilution::Config","description":"Immutable configuration value object. Fields: target_files, jobs (default: Etc.nprocessors), timeout (default: 10s), format (:json/:text), diff_base (nil), min_score (0.0), integration (:rspec), config_file path. Merge from defaults + YAML + CLI flags. File: lib/evilution/config.rb + spec.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:05:50.275297792+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T10:43:14.688620834+07:00","closed_at":"2026-03-02T10:43:14.688620834+07:00","close_reason":"Closed","dependencies":[{"issue_id":"EV-2.1","depends_on_id":"EV-2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
120
120
|
{"id":"EV-2.10","title":"Implement Evilution::Integration::Base and RSpec adapter","description":"Base: abstract adapter with interface #call(test_files) -> {passed: bool, example_count: int, failure_count: int}. RSpec: programmatic runner using RSpec::Core::Runner.run with StringIO for capture. Auto-detects spec/ directory. Files: lib/evilution/integration/base.rb, lib/evilution/integration/rspec.rb + specs.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:05:51.246990324+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T10:58:18.324768622+07:00","closed_at":"2026-03-02T10:58:18.324768622+07:00","close_reason":"Integration::Base and Integration::RSpec implemented with 8 passing specs","dependencies":[{"issue_id":"EV-2.10","depends_on_id":"EV-2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
@@ -130,9 +130,9 @@
|
|
|
130
130
|
{"id":"EV-2.8","title":"Implement Evilution::Result::MutationResult and Summary","description":"MutationResult: value object with fields: mutation, status (:killed/:survived/:timeout/:error), duration, killing_test (optional). Summary: aggregates MutationResult array into total/killed/survived/timed_out/errors/score/duration. Files: lib/evilution/result/mutation_result.rb, lib/evilution/result/summary.rb + specs.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:05:51.022249568+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T10:51:08.482621898+07:00","closed_at":"2026-03-02T10:51:08.482621898+07:00","close_reason":"Closed","dependencies":[{"issue_id":"EV-2.8","depends_on_id":"EV-2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
131
131
|
{"id":"EV-2.9","title":"Implement Evilution::Isolation::Fork","description":"Fork-based process isolation. Method: run(mutation, test_command, timeout:) -> MutationResult. Flow: create pipe, fork child, child applies mutation via eval, child runs test command, marshal result back via pipe, parent reads with IO.select timeout, SIGKILL on deadline. File: lib/evilution/isolation/fork.rb + spec.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:05:51.142167275+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T10:54:08.819310594+07:00","closed_at":"2026-03-02T10:54:08.819310594+07:00","close_reason":"Fork isolation implemented with 6 passing specs","dependencies":[{"issue_id":"EV-2.9","depends_on_id":"EV-2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-2.9","depends_on_id":"EV-2.8","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
132
132
|
{"id":"EV-20","title":"Deprecate coverage filtering","description":"With line-range and method-name targeting, coverage collection adds overhead for little benefit. Deprecate --no-coverage flag, coverage config key, and remove coverage logic from runner.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T00:30:58.837659684+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-10T00:33:06.453669976+07:00","closed_at":"2026-03-10T00:33:06.453669976+07:00","close_reason":"Deprecated coverage filtering: CLI warns on --no-coverage, config warns on coverage key, runner no longer uses coverage logic"}
|
|
133
|
-
{"id":"EV-200","title":"Implement multiple assignment (masgn) mutations","description":"Add mutations for destructuring/parallel assignment (a, b = 1, 2). Mutations: remove individual assignment targets, swap assignment order, reduce to single assignment. Prism multi_write_node.","notes":"GitHub: #493","status":"
|
|
134
|
-
{"id":"EV-201","title":"Implement yield statement mutations","description":"Add mutations for yield statements. Mutations: remove yield, remove yield arguments, replace yield value with nil. Prism yield_node.","notes":"GitHub: #494","status":"
|
|
135
|
-
{"id":"EV-202","title":"Implement splat operator mutations","description":"Add mutations for splat (*) and double-splat (**) operators. Mutations: remove splat (pass array directly), remove double-splat (pass hash directly). Prism splat_node, assoc_splat_node.","notes":"GitHub: #491","status":"
|
|
133
|
+
{"id":"EV-200","title":"Implement multiple assignment (masgn) mutations","description":"Add mutations for destructuring/parallel assignment (a, b = 1, 2). Mutations: remove individual assignment targets, swap assignment order, reduce to single assignment. Prism multi_write_node.","notes":"GitHub: #493","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:21:44.259585319+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-07T13:08:55.884730359+07:00","closed_at":"2026-04-07T13:08:55.884730359+07:00","close_reason":"Already implemented in v0.18.0: multiple_assignment operator removes individual targets and swaps 2-element order. Shipped with spec coverage."}
|
|
134
|
+
{"id":"EV-201","title":"Implement yield statement mutations","description":"Add mutations for yield statements. Mutations: remove yield, remove yield arguments, replace yield value with nil. Prism yield_node.","notes":"GitHub: #494","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:21:53.437749245+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-07T13:09:26.882361994+07:00","closed_at":"2026-04-07T13:09:26.882361994+07:00","close_reason":"Already implemented in v0.18.0: yield_statement operator removes yield, removes yield arguments, replaces yield value with nil. Shipped with spec coverage."}
|
|
135
|
+
{"id":"EV-202","title":"Implement splat operator mutations","description":"Add mutations for splat (*) and double-splat (**) operators. Mutations: remove splat (pass array directly), remove double-splat (pass hash directly). Prism splat_node, assoc_splat_node.","notes":"GitHub: #491","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:22:01.958187896+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-07T12:13:59.203448703+07:00","closed_at":"2026-04-07T12:13:59.203448703+07:00","close_reason":"Already implemented in v0.18.0: lib/evilution/mutator/operator/splat_operator.rb with spec. Removes splat (*) and double-splat (**) operators. Listed in operator table and changelog."}
|
|
136
136
|
{"id":"EV-203","title":"Epic: Extended equivalent mutation detection","description":"Extend equivalent mutation detection with more heuristics and manual marking support. Evilution's equivalent detection is already praised in feedback; extending it further strengthens a competitive advantage.","status":"open","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:22:11.400012571+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:22:11.400012571+07:00"}
|
|
137
137
|
{"id":"EV-204","title":"Add more automatic equivalence heuristics","description":"Add new equivalence detection strategies: frozen string mutations (frozen strings are immutable, mutation is equivalent), private method rename (if only called internally with same signature), constant folding equivalences.","status":"in_progress","priority":3,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:22:20.07787144+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-28T21:21:19.35465527+07:00","dependencies":[{"issue_id":"EV-204","depends_on_id":"EV-203","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
138
138
|
{"id":"EV-205","title":"Support # evilution:equivalent manual marking","description":"Allow users to mark specific mutations as equivalent using source comments: # evilution:equivalent. These should be excluded from the score denominator like auto-detected equivalents.","status":"in_progress","priority":3,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:22:31.543822152+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-28T22:08:20.967272294+07:00","dependencies":[{"issue_id":"EV-205","depends_on_id":"EV-203","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
@@ -145,18 +145,18 @@
|
|
|
145
145
|
{"id":"EV-211","title":"Implement regex capture reference (, ) mutations","description":"Add mutations for numbered regex capture references ($1, $2, etc.). Mutations: swap capture numbers ($1↔$2), replace with nil. Prism numbered_reference_read_node.","status":"open","priority":4,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:23:30.659728763+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:23:30.659728763+07:00"}
|
|
146
146
|
{"id":"EV-212","title":"Implement self reference mutations","description":"Add mutations for self references. Mutations: remove self where it's used as an explicit receiver (self.foo → foo). Prism self_node.","status":"open","priority":4,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:23:42.173027795+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:23:42.173027795+07:00"}
|
|
147
147
|
{"id":"EV-213","title":"Critical: SourceSurgeon crashes on multi-byte UTF-8 source files","description":"SourceSurgeon.apply uses String#[]= with Prism byte offsets, but Ruby String#[]= interprets indices as character offsets for UTF-8 strings. This causes IndexError on files containing multi-byte characters (e.g. Cyrillic, CJK, emoji). Fix: use source.b to force ASCII-8BIT before byte-offset operations, or use byteslice-based replacement. Reported by end user on 2026-03-29 against v0.16.0.","status":"closed","priority":0,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-29T23:19:47.803570297+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-04T11:14:32.115438439+07:00","closed_at":"2026-04-04T11:14:32.115438439+07:00","close_reason":"Fixed in commit e973a13, released in v0.17.0. SourceSurgeon now uses binary encoding for byte-offset operations."}
|
|
148
|
-
{"id":"EV-214","title":"While/until flip mutation","description":"Add mutation that swaps while cond to until cond and vice versa. Prism: while_node, until_node. Small operator.","notes":"GitHub: #495","status":"
|
|
149
|
-
{"id":"EV-215","title":"defined? -> true mutation","description":"Add mutation that replaces defined?(expr) with true. Prism: defined_node. Small operator.","notes":"GitHub: #496","status":"
|
|
150
|
-
{"id":"EV-216","title":"String interpolation content mutation","description":"Add mutations for string interpolation content. Replace #{expr} with #{nil} or empty string. Prism: interpolated_string_node, embedded_statements_node. Medium operator.","notes":"GitHub: #497","status":"
|
|
151
|
-
{"id":"EV-217","title":"== -> .equal? identity mutation","description":"Add mutation that replaces a == b with a.equal?(b) (object identity check). Prism: call_node with method ==. Small operator.","notes":"GitHub: #501","status":"
|
|
152
|
-
{"id":"EV-218","title":"case/when branch mutations","description":"Add mutations for case/when statements. Remove individual when-branches, replace when body with nil, remove else branch. Prism: case_node, when_node. Medium operator.","notes":"GitHub: #498","status":"
|
|
153
|
-
{"id":"EV-219","title":"retry removal mutation","description":"Add mutation that removes retry statements in rescue blocks (replace with nil). Prism: retry_node. Small operator.","notes":"GitHub: #499","status":"
|
|
148
|
+
{"id":"EV-214","title":"While/until flip mutation","description":"Add mutation that swaps while cond to until cond and vice versa. Prism: while_node, until_node. Small operator.","notes":"GitHub: #495","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:11:15.904741396+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-07T14:17:43.503188236+07:00","closed_at":"2026-04-07T14:17:43.503188236+07:00","close_reason":"Implemented: loop_flip operator swaps while/until loops (block and modifier forms). 7 tests, 61 operators total. Merged via GH #495.","dependencies":[{"issue_id":"EV-214","depends_on_id":"EV-238","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
149
|
+
{"id":"EV-215","title":"defined? -> true mutation","description":"Add mutation that replaces defined?(expr) with true. Prism: defined_node. Small operator.","notes":"GitHub: #496","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:11:21.037349751+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-07T14:17:56.362814861+07:00","closed_at":"2026-04-07T14:17:56.362814861+07:00","close_reason":"Already implemented in v0.18.0: defined_check operator replaces defined?(expr) with true. Shipped with spec coverage.","dependencies":[{"issue_id":"EV-215","depends_on_id":"EV-238","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
150
|
+
{"id":"EV-216","title":"String interpolation content mutation","description":"Add mutations for string interpolation content. Replace #{expr} with #{nil} or empty string. Prism: interpolated_string_node, embedded_statements_node. Medium operator.","notes":"GitHub: #497","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:11:22.095986111+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-07T18:10:51.520585633+07:00","closed_at":"2026-04-07T18:10:51.520585633+07:00","close_reason":"Implemented: string_interpolation operator replaces #{expr} with #{nil} in interpolated strings and symbols. 10 tests, 62 operators total. Merged via GH #497.","dependencies":[{"issue_id":"EV-216","depends_on_id":"EV-238","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
151
|
+
{"id":"EV-217","title":"== -> .equal? identity mutation","description":"Add mutation that replaces a == b with a.equal?(b) (object identity check). Prism: call_node with method ==. Small operator.","notes":"GitHub: #501","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:11:22.723044425+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-07T21:57:45.261222921+07:00","closed_at":"2026-04-07T21:57:45.261222921+07:00","close_reason":"Implemented: equality_to_identity operator replaces a == b with a.equal?(b). 8 tests, 66 operators total. Merged via GH #501.","dependencies":[{"issue_id":"EV-217","depends_on_id":"EV-238","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
152
|
+
{"id":"EV-218","title":"case/when branch mutations","description":"Add mutations for case/when statements. Remove individual when-branches, replace when body with nil, remove else branch. Prism: case_node, when_node. Medium operator.","notes":"GitHub: #498","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:11:22.912808107+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-07T21:04:32.266231607+07:00","closed_at":"2026-04-07T21:04:32.266231607+07:00","close_reason":"Implemented: case_when operator removes when branches, replaces when body with nil, removes else branch. 9 tests, 64 operators total. Merged via GH #498.","dependencies":[{"issue_id":"EV-218","depends_on_id":"EV-238","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
153
|
+
{"id":"EV-219","title":"retry removal mutation","description":"Add mutation that removes retry statements in rescue blocks (replace with nil). Prism: retry_node. Small operator.","notes":"GitHub: #499","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:11:23.739947781+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-07T20:42:58.086369513+07:00","closed_at":"2026-04-07T20:42:58.086369513+07:00","close_reason":"Implemented: retry_removal operator replaces retry with nil. 6 tests, 63 operators total. Merged via GH #499.","dependencies":[{"issue_id":"EV-219","depends_on_id":"EV-238","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
154
154
|
{"id":"EV-22","title":"Add --fail-fast flag with survivor threshold","description":"Add --fail-fast [N] CLI flag and fail_fast config option to stop mutation testing after N surviving mutants (default N=1 when flag given without value). Agents fix gaps iteratively, so discovering all survivors upfront is wasted work. A threshold gives control over the speed/thoroughness tradeoff: --fail-fast 1 for quick checks, --fail-fast 5 for CI gates, omit for full scans. Runner#call should stop running mutations and return a partial Summary once the threshold is reached. JSON output should include a 'truncated: true' field when fail-fast triggered.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:28.018733235+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T10:11:01.311500554+07:00","closed_at":"2026-03-16T10:11:01.311500554+07:00","close_reason":"Already merged and released in v0.4.0"}
|
|
155
|
-
{"id":"EV-220","title":"Predicate method -> true/false mutation","description":"Add mutation that replaces predicate method calls (methods ending with ?) with true and false literals. Existing NegationInsertion inserts ! before predicates but does not replace with literal boolean. Prism: call_node where method name ends with ?. Small operator.","notes":"GitHub: #500","status":"
|
|
156
|
-
{"id":"EV-221","title":"Method body -> self/super replacement","description":"Extend MethodBodyReplacement to also replace method body with self and super (currently only replaces with nil). Mutant does all three: nil, self, super. Small enhancement to existing operator.","notes":"GitHub: #502","status":"
|
|
157
|
-
{"id":"EV-222","title":"Proc/lambda body mutation","description":"Add mutation that replaces proc/lambda body with nil. ->() { expr } becomes ->() { nil }. Not covered by BlockRemoval which handles do..end/{ } blocks on method calls. Prism: lambda_node. Small operator.","notes":"GitHub: #503","status":"
|
|
158
|
-
{"id":"EV-223","title":"begin..end unwrap mutation","description":"Add mutation that removes begin/end wrapper, leaving just the body statements. begin; expr; end becomes expr. Prism: begin_node. Small operator.","notes":"GitHub: #504","status":"
|
|
159
|
-
{"id":"EV-224","title":"Block argument (&block) removal mutation","description":"Add mutation that removes explicit block parameter from method definitions. def foo(&block) becomes def foo. Prism: block_parameter_node. Small operator.","notes":"GitHub: #505","status":"
|
|
155
|
+
{"id":"EV-220","title":"Predicate method -> true/false mutation","description":"Add mutation that replaces predicate method calls (methods ending with ?) with true and false literals. Existing NegationInsertion inserts ! before predicates but does not replace with literal boolean. Prism: call_node where method name ends with ?. Small operator.","notes":"GitHub: #500","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:11:25.02387319+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-07T21:26:20.744321395+07:00","closed_at":"2026-04-07T21:26:20.744321395+07:00","close_reason":"Implemented: predicate_replacement operator replaces predicate calls with true and false. 8 tests, 65 operators total. Merged via GH #500.","dependencies":[{"issue_id":"EV-220","depends_on_id":"EV-238","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
156
|
+
{"id":"EV-221","title":"Method body -> self/super replacement","description":"Extend MethodBodyReplacement to also replace method body with self and super (currently only replaces with nil). Mutant does all three: nil, self, super. Small enhancement to existing operator.","notes":"GitHub: #502","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:11:36.202930745+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-07T22:16:01.369689765+07:00","closed_at":"2026-04-07T22:16:01.369689765+07:00","close_reason":"Implemented: method_body_replacement now replaces body with nil, self, and super (up from nil only). 7 tests. Merged via GH #502.","dependencies":[{"issue_id":"EV-221","depends_on_id":"EV-238","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
157
|
+
{"id":"EV-222","title":"Proc/lambda body mutation","description":"Add mutation that replaces proc/lambda body with nil. ->() { expr } becomes ->() { nil }. Not covered by BlockRemoval which handles do..end/{ } blocks on method calls. Prism: lambda_node. Small operator.","notes":"GitHub: #503","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:11:38.002122541+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-07T23:35:25.41137258+07:00","closed_at":"2026-04-07T23:35:25.41137258+07:00","close_reason":"Implemented: lambda_body operator replaces lambda body with nil. 7 tests, 67 operators total. Merged via GH #503.","dependencies":[{"issue_id":"EV-222","depends_on_id":"EV-238","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
158
|
+
{"id":"EV-223","title":"begin..end unwrap mutation","description":"Add mutation that removes begin/end wrapper, leaving just the body statements. begin; expr; end becomes expr. Prism: begin_node. Small operator.","notes":"GitHub: #504","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:11:38.269119824+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-08T08:35:47.716813843+07:00","closed_at":"2026-04-08T08:35:47.716813843+07:00","close_reason":"Implemented: begin_unwrap operator removes begin/end wrapper leaving body statements. 7 tests, 68 operators total. Merged via GH #504.","dependencies":[{"issue_id":"EV-223","depends_on_id":"EV-238","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
159
|
+
{"id":"EV-224","title":"Block argument (&block) removal mutation","description":"Add mutation that removes explicit block parameter from method definitions. def foo(&block) becomes def foo. Prism: block_parameter_node. Small operator.","notes":"GitHub: #505","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:11:37.302228714+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-08T08:41:59.58270935+07:00","closed_at":"2026-04-08T08:41:59.58270935+07:00","close_reason":"Implemented: block_param_removal operator removes &block parameter from method definitions. 7 tests, 69 operators total. Merged via GH #505.","dependencies":[{"issue_id":"EV-224","depends_on_id":"EV-238","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
160
160
|
{"id":"EV-225","title":"Bug: Version field in JSON output not updated after version bump","description":"Evilution JSON output (both CLI --format json and MCP tool response) reports version as 0.17.0 when evilution --version says 0.18.0. The MCP server correctly references Evilution::VERSION constant, so the issue is likely that VERSION constant was not bumped to 0.18.0 in lib/evilution/version.rb. Reported across 5 consecutive feedback sessions on 2026-04-04.","notes":"GitHub: #506","status":"closed","priority":0,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:20:30.428077285+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-04T13:15:11.40532407+07:00","closed_at":"2026-04-04T13:15:11.40532407+07:00","close_reason":"Not a bug. MCP server is a long-running process — bundle update changes files on disk but the loaded Ruby process retains the old VERSION constant. Restart the MCP server after gem updates. Generic MCP behavior, not evilution-specific."}
|
|
161
161
|
{"id":"EV-226","title":"Bug: Memory leak: RSS grows linearly per mutation without releasing","description":"During mutation runs, child process RSS grows ~3-8 MB per mutation without releasing. Observed on v0.18.0: RSS went from 190 MB to 730 MB across 159 mutations on a 46-line file (~3.4 MB/mutation average delta). Previous memory fixes (AST node release, StringIO drain) helped but did not fully resolve. The leak appears to be in the fork/child process pool — memory accumulates across mutations within the same worker process. This should be investigated with rake memory:check and profiling tools.","notes":"GitHub: #507","status":"closed","priority":0,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:20:32.021072642+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-05T23:25:48.383886909+07:00","closed_at":"2026-04-05T23:25:48.383886909+07:00","close_reason":"Core leak fixed (EV-256), regression test added (EV-257). Residual ~128 KB/mutation from MRI Class#subclasses tracking — not fixable without Ruby changes. Follow-ups: EV-274, EV-275, EV-276."}
|
|
162
162
|
{"id":"EV-227","title":"Smart spec auto-detection (narrow to matching spec file)","description":"Evilution's spec auto-detection selects too broad a test suite per mutation, causing mass timeouts and excessive memory usage. When --spec override is used to point at the correct spec file, timeouts disappear, speed improves 4-7x, and memory drops 6x (282 MB vs 1709 MB). The auto-detection should narrow to the closest matching spec file based on file path conventions (e.g., app/controllers/foo_controller.rb -> spec/requests/foo_spec.rb or spec/controllers/foo_controller_spec.rb). Reported in 7 separate feedback sessions from v0.12.0 through v0.17.0. This is the single biggest pain point in real-world usage. Evidence: HelpController 9-LOC file had 3/18 timeouts without --spec, 0/15 with --spec. GamesController had 25/246 timeouts (10.2%). HomeController: 110s without -> 29s with --spec, 1709 MB -> 282 MB.","status":"closed","priority":1,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:21:16.691637724+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-07T09:55:50.887685731+07:00","closed_at":"2026-04-07T09:55:50.887685731+07:00","close_reason":"All subtasks complete: controller→request spec mapping, model/service/lib/Avo conventions, fallback chain with warnings, integration tests with fixture layouts. SpecResolver handles all Rails and gem conventions with parent directory fallback and full-suite fallback with user warnings.","comments":[{"id":1,"issue_id":"EV-227","author":"Denis Kiselev","text":"GitHub issue: #508 (https://github.com/marinazzio/evilution/issues/508)","created_at":"2026-04-04T11:21:46.103310023+07:00"}]}
|
|
@@ -167,21 +167,21 @@
|
|
|
167
167
|
{"id":"EV-227.5","title":"Implement Avo resource to spec convention mapping","description":"Map app/avo/resources/foo.rb to spec/avo/resources/foo_spec.rb. Mutant gets this wrong; evilution should get it right. Avo resources follow a predictable path convention.","notes":"GitHub: #553","status":"closed","priority":1,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:31:11.220551992+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-06T23:36:54.160582811+07:00","closed_at":"2026-04-06T23:36:54.160582811+07:00","close_reason":"Already handled by existing app/ prefix-stripping logic. Verified for avo/resources/ and avo/actions/.","dependencies":[{"issue_id":"EV-227.5","depends_on_id":"EV-227","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
168
168
|
{"id":"EV-227.6","title":"Implement fallback chain when primary convention doesn't match","description":"If the convention-mapped spec file doesn't exist, try parent directory patterns, then fall back to full suite. Log a warning when falling back so users know auto-detection couldn't narrow the spec.","notes":"GitHub: #555","status":"closed","priority":1,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:31:33.254382872+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-07T09:54:52.004819913+07:00","closed_at":"2026-04-07T09:54:52.004819913+07:00","close_reason":"Fully implemented: parent_fallback_candidates in SpecResolver walks up directory tree. Warnings logged in both baseline.rb and integration/rspec.rb when falling back to full suite. Covered by unit, integration, baseline, and rspec integration tests. Merged via GH #555.","dependencies":[{"issue_id":"EV-227.6","depends_on_id":"EV-227","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
169
169
|
{"id":"EV-227.7","title":"Add integration tests for spec auto-detection with fixture project layouts","description":"Test Rails conventions (controller, model, service, Avo), non-Rails lib layout, and fallback behavior using fixture directories under spec/support/fixtures/. Cover all convention mappings and the fallback chain.","notes":"GitHub: #556","status":"closed","priority":1,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:31:58.393834666+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-07T09:39:42.378355211+07:00","closed_at":"2026-04-07T09:39:42.378355211+07:00","close_reason":"Integration tests added for all spec auto-detection conventions (Rails controller/model/service/Avo, gem lib/, fallback chain) using fixture project layouts. 15 tests passing. Merged via GH #556.","dependencies":[{"issue_id":"EV-227.7","depends_on_id":"EV-227","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
170
|
-
{"id":"EV-228","title":"Equivalent detection: .count → .length as always-equivalent","description":".count → .length mutations are universally unkillable (both return identical integer results). Evilution should detect this pattern and classify as equivalent automatically. Reported in feedback for HomeController stats block.","notes":"GH #509","status":"
|
|
171
|
-
{"id":"EV-229","title":"Equivalent detection: .each → .map in void context","description":"When .each is called in void context (return value not assigned or passed), replacing with .map or .reverse_each produces equivalent behavior. Evilution should detect void-context method calls and mark these swaps as likely-equivalent. Reported for Avo reset_password action.","notes":"GH #511","status":"
|
|
170
|
+
{"id":"EV-228","title":"Equivalent detection: .count → .length as always-equivalent","description":".count → .length mutations are universally unkillable (both return identical integer results). Evilution should detect this pattern and classify as equivalent automatically. Reported in feedback for HomeController stats block.","notes":"GH #509","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:21:43.587875972+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-08T08:52:50.669215278+07:00","closed_at":"2026-04-08T08:52:50.669215278+07:00","close_reason":"Closed"}
|
|
171
|
+
{"id":"EV-229","title":"Equivalent detection: .each → .map in void context","description":"When .each is called in void context (return value not assigned or passed), replacing with .map or .reverse_each produces equivalent behavior. Evilution should detect void-context method calls and mark these swaps as likely-equivalent. Reported for Avo reset_password action.","notes":"GH #511","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:21:46.8330208+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-08T09:31:47.874981801+07:00","closed_at":"2026-04-08T09:31:47.874981801+07:00","close_reason":"Closed"}
|
|
172
172
|
{"id":"EV-23","title":"Per-mutation spec targeting","description":"Instead of running the full spec suite for every mutation, map each mutated source file to its relevant spec file(s) using convention-based resolution (e.g. lib/foo/bar.rb -> spec/foo/bar_spec.rb) and only run those. This dramatically reduces per-mutation test time. Depends on convention-based spec file resolution being implemented first.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:28.98620973+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T14:49:13.616876819+07:00","closed_at":"2026-03-16T14:49:13.616876819+07:00","close_reason":"Fixed and merged","dependencies":[{"issue_id":"EV-23","depends_on_id":"EV-34","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
173
173
|
{"id":"EV-230","title":"Regex simplification operators","description":"Add mutation operators for regex patterns: /\\s+/ → /\\s/ (remove quantifier), remove anchors (^, $, \\A, \\z), simplify character classes. Mutant's regex mutations caught real test gaps (case sensitivity, whitespace handling) that evilution missed. Reported in 2 sessions on Telegram::NewsScorer.","notes":"GH #514","status":"open","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:21:48.930372762+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-04T11:22:57.239710634+07:00","dependencies":[{"issue_id":"EV-230","depends_on_id":"EV-238","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
174
|
-
{"id":"EV-231","title":".downcase removal operator","description":"Add mutation that removes .downcase calls. This caught real case-sensitivity test gaps in mutant that evilution missed. Useful for testing that code handles mixed-case input. Reported in 2 sessions on NewsScorer.","notes":"GH #516","status":"
|
|
175
|
-
{"id":"EV-232","title":"Method chain permutation operators (strip → lstrip/rstrip)","description":"Add mutations that replace string cleaning methods with partial variants: strip → lstrip/rstrip, chomp → chop, etc. Mutant's chain permutations caught a real test gap in NewsScorer keyword processing. Reported in 1 session.","notes":"GH #518","status":"
|
|
176
|
-
{"id":"EV-233","title":"Related specs heuristic (run association specs when .includes() mutated)","description":"When mutations remove .includes() eager loading calls, the matching unit spec may not catch N+1 regressions. Consider a heuristic that also runs specs for the included associations or integration specs. Would complement the spec auto-detection feature. Reported in 1 session for NewsController.","notes":"GH #519","status":"
|
|
177
|
-
{"id":"EV-234","title":"Conceptual deduplication of survived mutations","description":"Multiple operators can flag the same underlying coverage gap (e.g., .includes removal via method_call_removal and symbol_literal both reveal missing eager-load test). Add a post-processing pass that groups conceptually similar survivors and presents them as a single coverage gap with multiple mutation evidence. Reduces noise in reports.","notes":"GitHub: #510","status":"
|
|
174
|
+
{"id":"EV-231","title":".downcase removal operator","description":"Add mutation that removes .downcase calls. This caught real case-sensitivity test gaps in mutant that evilution missed. Useful for testing that code handles mixed-case input. Reported in 2 sessions on NewsScorer.","notes":"GH #516","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:21:50.682791593+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-08T09:42:19.793202916+07:00","closed_at":"2026-04-08T09:42:19.793202916+07:00","close_reason":"Closed","dependencies":[{"issue_id":"EV-231","depends_on_id":"EV-238","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
175
|
+
{"id":"EV-232","title":"Method chain permutation operators (strip → lstrip/rstrip)","description":"Add mutations that replace string cleaning methods with partial variants: strip → lstrip/rstrip, chomp → chop, etc. Mutant's chain permutations caught a real test gap in NewsScorer keyword processing. Reported in 1 session.","notes":"GH #518","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:21:52.982750285+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-08T11:15:28.398677068+07:00","closed_at":"2026-04-08T11:15:28.398677068+07:00","close_reason":"Closed","dependencies":[{"issue_id":"EV-232","depends_on_id":"EV-238","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
176
|
+
{"id":"EV-233","title":"Related specs heuristic (run association specs when .includes() mutated)","description":"When mutations remove .includes() eager loading calls, the matching unit spec may not catch N+1 regressions. Consider a heuristic that also runs specs for the included associations or integration specs. Would complement the spec auto-detection feature. Reported in 1 session for NewsController.","notes":"GH #519","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:21:56.302076763+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-08T12:16:09.453069408+07:00","closed_at":"2026-04-08T12:16:09.453069408+07:00","close_reason":"Closed"}
|
|
177
|
+
{"id":"EV-234","title":"Conceptual deduplication of survived mutations","description":"Multiple operators can flag the same underlying coverage gap (e.g., .includes removal via method_call_removal and symbol_literal both reveal missing eager-load test). Add a post-processing pass that groups conceptually similar survivors and presents them as a single coverage gap with multiple mutation evidence. Reduces noise in reports.","notes":"GitHub: #510","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:21:58.378005036+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-08T09:21:20.475702136+07:00","closed_at":"2026-04-08T09:21:20.475702136+07:00","close_reason":"Closed"}
|
|
178
178
|
{"id":"EV-235","title":"Bug: Non-deterministic mutation count on same file","description":"Running evilution twice on the same file (HelpController, 9 LOC) produced different mutation counts (18 vs 15). Unclear cause — possibly non-deterministic operator selection or file state difference. Low priority but should be investigated. Reported once in v0.12.0.","notes":"GitHub: #512","status":"closed","priority":4,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:22:01.930671333+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-04T13:20:37.135440498+07:00","closed_at":"2026-04-04T13:20:37.135440498+07:00","close_reason":"Not a bug. The 18 vs 15 difference was exactly the 3 timed-out mutations — counted in run 1 total but excluded in run 2 summary. Mutation generation is fully deterministic (no rand/shuffle/sample in codepath). Reported once in v0.12.0, never reproduced. Reporting has been significantly improved since then."}
|
|
179
179
|
{"id":"EV-236","title":"--spec-dir flag for directory-level spec inclusion","description":"Add a --spec-dir flag that auto-includes all specs in a directory, reducing the chance of missing coverage from adjacent spec files. Useful when a controller has tests split across spec/requests/, spec/controllers/, and spec/features/. Reported once.","notes":"GitHub: #513","status":"closed","priority":4,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:22:04.618160285+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-07T09:55:50.99913701+07:00","closed_at":"2026-04-07T09:55:50.99913701+07:00","close_reason":"--spec-dir CLI flag implemented, composes with --spec, validates directory existence. 3 unit tests passing. Merged via GH #513.","dependencies":[{"issue_id":"EV-236","depends_on_id":"EV-227","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
180
180
|
{"id":"EV-237","title":"Temp-file based mutation (don't modify original source)","description":"Evilution mutates source files in-place on the filesystem, which triggers file watchers, linters, and IDE notifications during runs. Even with the ensure-based restore (fixed earlier), race conditions exist if the process is killed. Write mutated source to a tempfile and point the test runner at it via load path manipulation. Never modify the original source file. Reported in 2 sessions (v0.16.1).","notes":"GH issue: #520 (https://github.com/marinazzio/evilution/issues/520)","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:22:06.770551806+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-04T11:23:18.529285207+07:00","external_ref":"gh-520","labels":["reliability"]}
|
|
181
181
|
{"id":"EV-238","title":"Epic: Research mutation density gap with mutant","description":"Evilution consistently generates 1.8-2.6x fewer mutations than mutant across 25 feedback sessions. While some of mutant's extras are equivalent ([]→fetch, to_i→Integer()), many catch real edge cases. This epic covers: (1) Audit mutant's operator list systematically against evilution's 54 operators, (2) Identify which missing operators catch real bugs vs produce noise, (3) Prioritize operator additions by signal-to-noise ratio, (4) Target closing the gap to <1.5x. Related: existing gap analysis created 15 new operator issues (EV-214 through EV-224, #491-#505).","notes":"GitHub: #515","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:22:11.095318733+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-04T11:22:55.790159971+07:00"}
|
|
182
182
|
{"id":"EV-239","title":"Epic: Research and fix high memory baseline","description":"Evilution's memory baseline is 718+ MB even for tiny files in v0.18.0 sessions, and grows across consecutive runs in the same session (718→763→795→800 MB). Previous fixes addressed AST node retention and StringIO leaks but baseline remains high. Mutant peaks at ~200 MB for comparable workloads. This epic covers: (1) Profile memory allocation during boot/setup phase, (2) Identify what's consuming the 718 MB baseline, (3) Investigate cross-run memory growth (session-level leak?), (4) Target bringing baseline under 300 MB. Related: existing rake memory:check infrastructure exists.","notes":"GitHub: #517","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:22:16.602817355+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-06T00:16:38.665740136+07:00","closed_at":"2026-04-06T00:16:38.665740136+07:00","close_reason":"Premise invalid: 718 MB baseline was the MCP host process, not evilution. Standalone evilution baseline is ~30 MB (confirmed via EV-242/245/246 profiling). No memory fix needed. Sub-issues resolved: EV-242 (30 MB boot baseline), EV-243 (single mutation profiled), EV-245 (no cross-run growth), EV-246 (fork has zero parent-side cost), EV-247 (RSS tracking added)."}
|
|
183
183
|
{"id":"EV-24","title":"Epic: JSON Output Improvements","description":"Make JSON output fully machine-parseable in all scenarios, including errors. Add diagnostic fields that help agents debug failures.","status":"closed","priority":1,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:37.450686472+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T11:15:24.900944562+07:00","closed_at":"2026-03-16T11:15:24.900944562+07:00","close_reason":"All children complete: structured errors, test_command in JSON, noise suppression","dependencies":[{"issue_id":"EV-24","depends_on_id":"EV-25","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-24","depends_on_id":"EV-26","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-24","depends_on_id":"EV-40","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
184
|
-
{"id":"EV-240","title":"Neutral mutation category (test errors vs test failures)","description":"Add a 'neutral' classification for mutations where tests error (crash/exception) rather than fail (assertion). This helps users distinguish test infrastructure problems from real coverage gaps. Mutant has this distinction and it's valuable — e.g., killfork 403 errors in vanilla-mafia are infrastructure noise, not coverage gaps. Currently evilution has neutral detection implemented but feedback suggests it doesn't always classify correctly. Reported in 3 sessions.","notes":"GH issue: #521 (https://github.com/marinazzio/evilution/issues/521)","status":"
|
|
184
|
+
{"id":"EV-240","title":"Neutral mutation category (test errors vs test failures)","description":"Add a 'neutral' classification for mutations where tests error (crash/exception) rather than fail (assertion). This helps users distinguish test infrastructure problems from real coverage gaps. Mutant has this distinction and it's valuable — e.g., killfork 403 errors in vanilla-mafia are infrastructure noise, not coverage gaps. Currently evilution has neutral detection implemented but feedback suggests it doesn't always classify correctly. Reported in 3 sessions.","notes":"GH issue: #521 (https://github.com/marinazzio/evilution/issues/521)","status":"in_progress","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:22:18.548454128+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-08T12:21:16.372929584+07:00","external_ref":"gh-521","labels":["reliability"]}
|
|
185
185
|
{"id":"EV-241","title":"Heredoc-aware string literal mutations","description":"Evilution's string_literal operator generates many false survived mutants on heredoc templates by mutating whitespace and literal text around interpolations. MigrationGenerator scored 63.1% due to template whitespace mutations that don't affect generated code behavior. Should either skip literal text in heredocs, only mutate interpolated expressions, or add a --skip-heredoc-literals flag. Reported in 1 session but caused significant score distortion.","notes":"GH issue: #522 (https://github.com/marinazzio/evilution/issues/522)","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:22:20.534650035+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-04T11:23:19.83846784+07:00","external_ref":"gh-522","labels":["reliability","false-positives"]}
|
|
186
186
|
{"id":"EV-242","title":"Profile memory allocation during boot/setup phase","description":"Use memory_profiler gem or ObjectSpace to identify what objects are allocated during Evilution boot before any mutations run. Identify the top 10 memory consumers. This will help understand where the 718 MB baseline comes from.","notes":"GitHub: #527","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:28:39.583808805+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-05T23:36:18.3122267+07:00","closed_at":"2026-04-05T23:36:18.3122267+07:00","close_reason":"Boot footprint is ~30 MB. The 718 MB baseline was MCP host process, not evilution.","dependencies":[{"issue_id":"EV-242","depends_on_id":"EV-239","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
187
187
|
{"id":"EV-243","title":"Profile memory allocation during single mutation cycle","description":"Measure memory before and after a single mutation+test cycle. Identify what is allocated and not released within one cycle. Compare with mutant's behavior for the same file.","notes":"GitHub: #528","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-04-04T11:28:46.085727121+07:00","created_by":"Denis Kiselev","updated_at":"2026-04-05T23:37:08.936438689+07:00","dependencies":[{"issue_id":"EV-243","depends_on_id":"EV-239","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.20.0] - 2026-04-08
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **New mutation operators (10)** — `loop_flip` swaps `while`↔`until` loops (#581); `string_interpolation` replaces `#{expr}` content with `nil` (#582); `retry_removal` removes `retry` statements from rescue blocks (#583); `case_when` removes `when` branches, replaces bodies with `nil`, removes `else` (#584); `predicate_replacement` replaces predicate method calls (`foo?`) with `true`/`false` (#585); `equality_to_identity` converts `a == b` to `a.equal?(b)` (#586); `lambda_body` replaces lambda/proc bodies with `nil` (#588); `begin_unwrap` removes bare `begin..end` wrappers (#589); `block_param_removal` removes `&block` parameters from method definitions (#590)
|
|
8
|
+
- **Method body replacement expansion** — `method_body_replacement` now generates `self` and `super` replacements alongside `nil` (#587)
|
|
9
|
+
- **SendMutation expansions** — added `downcase`↔`upcase` (#594), `strip`→`lstrip`/`rstrip`, `lstrip`→`strip`, `rstrip`→`strip`, `chomp`↔`chop` (#595) method swap pairs
|
|
10
|
+
- **Coverage gap detection and reporting** — survived mutations are grouped by `(file, subject, line)` into coverage gaps; reported in CLI (`N coverage gaps` header with grouped entries), JSON (`coverage_gaps` key), HTML (grouped gap entries with operator tags), and session data (#592)
|
|
11
|
+
- **VoidContext equivalent heuristic** — detects equivalent mutations where collection methods are swapped in void context (e.g. `each`↔`map`, `each`↔`reverse_each` when return value is unused); uses Prism AST parent-node walking (#593)
|
|
12
|
+
- **AliasSwap heuristic expansion** — added `count`↔`size` and `detect`↔`find` alias pairs for equivalent detection (#591)
|
|
13
|
+
- **Related spec heuristic** — automatically detects mutations involving `.includes()` and finds related request/integration/feature/system specs by domain name, improving test targeting for association mutations (#596)
|
|
14
|
+
- **Crash detection in RSpec integration** — `CrashDetector` formatter distinguishes assertion failures from runtime crashes (e.g. `NoMethodError`, `SystemStackError`); when all test failures are crashes (no assertion failures), the result includes a crash summary in the error field; reuses a single detector instance across mutation runs to avoid formatter accumulation (#597)
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **Operator count** — 69 operators (up from 60), with new loop, string, case/when, predicate, identity, lambda, begin/end, and block parameter operators
|
|
19
|
+
- **Equivalent heuristic count** — 7 heuristics (up from 5), with new void context detection and expanded alias pairs
|
|
20
|
+
|
|
3
21
|
## [0.19.0] - 2026-04-07
|
|
4
22
|
|
|
5
23
|
### Added
|
data/README.md
CHANGED
|
@@ -148,6 +148,16 @@ Use `--format json` for machine-readable output. Schema:
|
|
|
148
148
|
"suggestion": "string — actionable hint for surviving mutants (survived only)"
|
|
149
149
|
}
|
|
150
150
|
],
|
|
151
|
+
"coverage_gaps": [
|
|
152
|
+
{
|
|
153
|
+
"file": "string — relative path to source file",
|
|
154
|
+
"subject": "string — method name (e.g. 'Foo#bar')",
|
|
155
|
+
"line": "integer — line number",
|
|
156
|
+
"operators": ["string — operator names involved"],
|
|
157
|
+
"count": "integer — number of survived mutations in this gap",
|
|
158
|
+
"mutations": ["... same shape as survived entries ..."]
|
|
159
|
+
}
|
|
160
|
+
],
|
|
151
161
|
"killed": ["... same shape as survived entries ..."],
|
|
152
162
|
"timed_out": ["... same shape as survived entries ..."],
|
|
153
163
|
"errors": ["... same shape as survived entries ..."]
|
|
@@ -156,7 +166,7 @@ Use `--format json` for machine-readable output. Schema:
|
|
|
156
166
|
|
|
157
167
|
**Key metric**: `summary.score` — the mutation score. Higher is better. 1.0 means all mutations were caught.
|
|
158
168
|
|
|
159
|
-
## Mutation Operators (
|
|
169
|
+
## Mutation Operators (69 total)
|
|
160
170
|
|
|
161
171
|
Each operator name is stable and appears in JSON output under `survived[].operator`.
|
|
162
172
|
|
|
@@ -177,7 +187,7 @@ Each operator name is stable and appears in JSON output under `survived[].operat
|
|
|
177
187
|
| `conditional_branch` | Remove if/else branch | Deletes branch body |
|
|
178
188
|
| `conditional_flip` | Flip `if` to `unless` and vice versa | `if cond` -> `unless cond` |
|
|
179
189
|
| `statement_deletion` | Remove statements from method bodies | Deletes a statement |
|
|
180
|
-
| `method_body_replacement` | Replace entire method body
|
|
190
|
+
| `method_body_replacement` | Replace entire method body | Method body -> `nil`, `self`, `super` |
|
|
181
191
|
| `negation_insertion` | Negate predicate methods | `x.empty?` -> `!x.empty?` |
|
|
182
192
|
| `return_value_removal` | Strip return values | `return x` -> `return` |
|
|
183
193
|
| `collection_replacement` | Swap collection methods | `map` -> `each`, `select` <-> `reject` |
|
|
@@ -222,6 +232,15 @@ Each operator name is stable and appears in JSON output under `survived[].operat
|
|
|
222
232
|
| `splat_operator` | Remove splat/double-splat | `foo(*args)` -> `foo(args)` |
|
|
223
233
|
| `defined_check` | Replace `defined?` with `true` | `defined?(x)` -> `true` |
|
|
224
234
|
| `regex_capture` | Swap or nil-ify capture refs | `$1` -> `$2`, `$1` -> `nil` |
|
|
235
|
+
| `loop_flip` | Swap while/until loops | `while cond` -> `until cond` |
|
|
236
|
+
| `string_interpolation` | Replace interpolation content with nil | `"hello #{name}"` -> `"hello #{nil}"` |
|
|
237
|
+
| `retry_removal` | Remove retry statements | `retry` -> `nil` |
|
|
238
|
+
| `case_when` | Remove/replace case/when branches | Remove `when` branch, body -> `nil`, remove `else` |
|
|
239
|
+
| `predicate_replacement` | Replace predicate calls with booleans | `x.empty?` -> `true`, `x.empty?` -> `false` |
|
|
240
|
+
| `equality_to_identity` | Replace equality with identity check | `a == b` -> `a.equal?(b)` |
|
|
241
|
+
| `lambda_body` | Replace lambda body with nil | `-> { expr }` -> `-> { nil }` |
|
|
242
|
+
| `begin_unwrap` | Remove begin/end wrapper | `begin; expr; end` -> `expr` |
|
|
243
|
+
| `block_param_removal` | Remove explicit block parameter | `def foo(&block)` -> `def foo` |
|
|
225
244
|
|
|
226
245
|
## MCP Server (AI Agent Integration)
|
|
227
246
|
|
|
@@ -368,7 +387,7 @@ Tests 4 paths (InProcess isolation, Fork isolation, mutation generation + stripp
|
|
|
368
387
|
1. **Parse** — Prism parses Ruby files into ASTs with exact byte offsets
|
|
369
388
|
2. **Extract** — Methods are identified as mutation subjects
|
|
370
389
|
3. **Filter** — Disable comments, Sorbet `sig` blocks, and AST ignore patterns exclude mutations before execution
|
|
371
|
-
4. **Mutate** —
|
|
390
|
+
4. **Mutate** — 69 operators produce text replacements at precise byte offsets (source-level surgery, no AST unparsing)
|
|
372
391
|
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
392
|
6. **Test** — RSpec executes against the mutated source
|
|
374
393
|
7. **Collect** — Source strings and AST nodes are released after use to minimize memory retention
|
|
@@ -6,6 +6,7 @@ require_relative "heuristic/alias_swap"
|
|
|
6
6
|
require_relative "heuristic/dead_code"
|
|
7
7
|
require_relative "heuristic/arithmetic_identity"
|
|
8
8
|
require_relative "heuristic/comment_marking"
|
|
9
|
+
require_relative "heuristic/void_context"
|
|
9
10
|
|
|
10
11
|
require_relative "../equivalent"
|
|
11
12
|
|
|
@@ -38,7 +39,8 @@ class Evilution::Equivalent::Detector
|
|
|
38
39
|
Evilution::Equivalent::Heuristic::AliasSwap.new,
|
|
39
40
|
Evilution::Equivalent::Heuristic::DeadCode.new,
|
|
40
41
|
Evilution::Equivalent::Heuristic::ArithmeticIdentity.new,
|
|
41
|
-
Evilution::Equivalent::Heuristic::CommentMarking.new
|
|
42
|
+
Evilution::Equivalent::Heuristic::CommentMarking.new,
|
|
43
|
+
Evilution::Equivalent::Heuristic::VoidContext.new
|
|
42
44
|
]
|
|
43
45
|
end
|
|
44
46
|
end
|
|
@@ -7,7 +7,8 @@ class Evilution::Equivalent::Heuristic::AliasSwap
|
|
|
7
7
|
Set[:detect, :find],
|
|
8
8
|
Set[:length, :size],
|
|
9
9
|
Set[:collect, :map],
|
|
10
|
-
Set[:count, :length]
|
|
10
|
+
Set[:count, :length],
|
|
11
|
+
Set[:count, :size]
|
|
11
12
|
].freeze
|
|
12
13
|
|
|
13
14
|
MATCHING_OPERATORS = Set["send_mutation", "collection_replacement"].freeze
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../heuristic"
|
|
4
|
+
|
|
5
|
+
class Evilution::Equivalent::Heuristic::VoidContext
|
|
6
|
+
# Method pairs where the only difference is the return value.
|
|
7
|
+
# In void context (return value unused), these are equivalent.
|
|
8
|
+
VOID_EQUIVALENT_PAIRS = Set[
|
|
9
|
+
Set[:each, :map],
|
|
10
|
+
Set[:each, :reverse_each]
|
|
11
|
+
].freeze
|
|
12
|
+
|
|
13
|
+
MATCHING_OPERATORS = Set["send_mutation", "collection_replacement"].freeze
|
|
14
|
+
|
|
15
|
+
def match?(mutation)
|
|
16
|
+
return false unless MATCHING_OPERATORS.include?(mutation.operator_name)
|
|
17
|
+
|
|
18
|
+
pair = extract_method_pair(mutation.diff)
|
|
19
|
+
return false unless pair
|
|
20
|
+
return false unless VOID_EQUIVALENT_PAIRS.include?(pair)
|
|
21
|
+
|
|
22
|
+
void_context?(mutation)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def extract_method_pair(diff)
|
|
28
|
+
removed = extract_method(diff, "- ")
|
|
29
|
+
added = extract_method(diff, "+ ")
|
|
30
|
+
return nil unless removed && added
|
|
31
|
+
|
|
32
|
+
Set[removed.to_sym, added.to_sym]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def extract_method(diff, prefix)
|
|
36
|
+
line = diff.split("\n").find { |l| l.start_with?(prefix) }
|
|
37
|
+
return nil unless line
|
|
38
|
+
|
|
39
|
+
match = line.match(/\.(\w+)(?:[\s({]|$)/)
|
|
40
|
+
match && match[1]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def void_context?(mutation)
|
|
44
|
+
node = mutation.subject.node
|
|
45
|
+
return false unless node
|
|
46
|
+
|
|
47
|
+
body = node.body
|
|
48
|
+
return false unless body.is_a?(Prism::StatementsNode)
|
|
49
|
+
|
|
50
|
+
statements = body.body
|
|
51
|
+
call_node = find_call_at_line(statements, mutation.line)
|
|
52
|
+
return false unless call_node
|
|
53
|
+
|
|
54
|
+
# The call is in void context if:
|
|
55
|
+
# 1. It's a direct statement (not wrapped in assignment)
|
|
56
|
+
# 2. It's not the last statement in the method body
|
|
57
|
+
statement_index = statements.index { |s| contains_line?(s, mutation.line) && direct_call?(s) }
|
|
58
|
+
return false unless statement_index
|
|
59
|
+
|
|
60
|
+
statement_index < statements.length - 1
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def find_call_at_line(statements, line)
|
|
64
|
+
statements.each do |stmt|
|
|
65
|
+
return stmt if stmt.is_a?(Prism::CallNode) && stmt.location.start_line == line
|
|
66
|
+
end
|
|
67
|
+
nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def direct_call?(statement)
|
|
71
|
+
statement.is_a?(Prism::CallNode)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def contains_line?(node, line)
|
|
75
|
+
line.between?(node.location.start_line, node.location.end_line)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../integration"
|
|
4
|
+
|
|
5
|
+
class Evilution::Integration::CrashDetector
|
|
6
|
+
def self.register_with_rspec
|
|
7
|
+
::RSpec::Core::Formatters.register self, :example_failed
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize(_output)
|
|
11
|
+
reset
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def reset
|
|
15
|
+
@assertion_failures = 0
|
|
16
|
+
@crashes = []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def example_failed(notification)
|
|
20
|
+
exception = notification.example.exception
|
|
21
|
+
|
|
22
|
+
if assertion_exception?(exception)
|
|
23
|
+
@assertion_failures += 1
|
|
24
|
+
else
|
|
25
|
+
@crashes << exception
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def has_assertion_failure? # rubocop:disable Naming/PredicatePrefix
|
|
30
|
+
@assertion_failures.positive?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def has_crash? # rubocop:disable Naming/PredicatePrefix
|
|
34
|
+
@crashes.any?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def only_crashes?
|
|
38
|
+
@crashes.any? && @assertion_failures.zero?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def crash_summary
|
|
42
|
+
return nil if @crashes.empty?
|
|
43
|
+
|
|
44
|
+
types = @crashes.map { |e| e.class.name }.uniq
|
|
45
|
+
"#{types.join(", ")} (#{@crashes.length} crash#{"es" unless @crashes.length == 1})"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def assertion_exception?(exception)
|
|
51
|
+
exception.is_a?(::RSpec::Expectations::ExpectationNotMetError) ||
|
|
52
|
+
(defined?(::RSpec::Mocks::MockExpectationError) &&
|
|
53
|
+
exception.is_a?(::RSpec::Mocks::MockExpectationError))
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -4,7 +4,9 @@ require "fileutils"
|
|
|
4
4
|
require "stringio"
|
|
5
5
|
require "tmpdir"
|
|
6
6
|
require_relative "base"
|
|
7
|
+
require_relative "crash_detector"
|
|
7
8
|
require_relative "../spec_resolver"
|
|
9
|
+
require_relative "../related_spec_heuristic"
|
|
8
10
|
|
|
9
11
|
require_relative "../integration"
|
|
10
12
|
|
|
@@ -13,6 +15,8 @@ class Evilution::Integration::RSpec < Evilution::Integration::Base
|
|
|
13
15
|
@test_files = test_files
|
|
14
16
|
@rspec_loaded = false
|
|
15
17
|
@spec_resolver = Evilution::SpecResolver.new
|
|
18
|
+
@related_spec_heuristic = Evilution::RelatedSpecHeuristic.new
|
|
19
|
+
@crash_detector = nil
|
|
16
20
|
@warned_files = Set.new
|
|
17
21
|
super(hooks: hooks)
|
|
18
22
|
end
|
|
@@ -39,6 +43,7 @@ class Evilution::Integration::RSpec < Evilution::Integration::Base
|
|
|
39
43
|
|
|
40
44
|
@hooks.fire(:setup_integration_pre, integration: :rspec) if @hooks
|
|
41
45
|
require "rspec/core"
|
|
46
|
+
Evilution::Integration::CrashDetector.register_with_rspec
|
|
42
47
|
@rspec_loaded = true
|
|
43
48
|
@hooks.fire(:setup_integration_post, integration: :rspec) if @hooks
|
|
44
49
|
rescue LoadError => e
|
|
@@ -115,10 +120,11 @@ class Evilution::Integration::RSpec < Evilution::Integration::Base
|
|
|
115
120
|
args = build_args(mutation)
|
|
116
121
|
command = "rspec #{args.join(" ")}"
|
|
117
122
|
|
|
123
|
+
detector = reset_crash_detector
|
|
118
124
|
eg_before = snapshot_example_groups
|
|
119
125
|
status = ::RSpec::Core::Runner.run(args, out, err)
|
|
120
126
|
|
|
121
|
-
|
|
127
|
+
build_rspec_result(status, command, detector)
|
|
122
128
|
rescue StandardError => e
|
|
123
129
|
{ passed: false, error: e.message, test_command: command }
|
|
124
130
|
ensure
|
|
@@ -171,6 +177,26 @@ class Evilution::Integration::RSpec < Evilution::Integration::Base
|
|
|
171
177
|
world.instance_variable_set(:@sources_by_path, {}) if world.instance_variable_defined?(:@sources_by_path)
|
|
172
178
|
end
|
|
173
179
|
|
|
180
|
+
def reset_crash_detector
|
|
181
|
+
if @crash_detector
|
|
182
|
+
@crash_detector.reset
|
|
183
|
+
else
|
|
184
|
+
@crash_detector = Evilution::Integration::CrashDetector.new(StringIO.new)
|
|
185
|
+
::RSpec.configuration.add_formatter(@crash_detector)
|
|
186
|
+
end
|
|
187
|
+
@crash_detector
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def build_rspec_result(status, command, detector)
|
|
191
|
+
if status.zero?
|
|
192
|
+
{ passed: true, test_command: command }
|
|
193
|
+
elsif detector.only_crashes?
|
|
194
|
+
{ passed: false, error: "test crashes: #{detector.crash_summary}", test_command: command }
|
|
195
|
+
else
|
|
196
|
+
{ passed: false, test_command: command }
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
174
200
|
def build_args(mutation)
|
|
175
201
|
files = resolve_test_files(mutation)
|
|
176
202
|
["--format", "progress", "--no-color", "--order", "defined", *files]
|
|
@@ -180,12 +206,13 @@ class Evilution::Integration::RSpec < Evilution::Integration::Base
|
|
|
180
206
|
return test_files if test_files
|
|
181
207
|
|
|
182
208
|
resolved = @spec_resolver.call(mutation.file_path)
|
|
183
|
-
|
|
184
|
-
[resolved]
|
|
185
|
-
else
|
|
209
|
+
unless resolved
|
|
186
210
|
warn_unresolved_spec(mutation.file_path)
|
|
187
|
-
["spec"]
|
|
211
|
+
return ["spec"]
|
|
188
212
|
end
|
|
213
|
+
|
|
214
|
+
related = @related_spec_heuristic.call(mutation)
|
|
215
|
+
([resolved] + related).uniq
|
|
189
216
|
end
|
|
190
217
|
|
|
191
218
|
def warn_unresolved_spec(file_path)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../operator"
|
|
4
|
+
|
|
5
|
+
class Evilution::Mutator::Operator::BeginUnwrap < Evilution::Mutator::Base
|
|
6
|
+
def visit_begin_node(node)
|
|
7
|
+
return super if node.rescue_clause || node.else_clause || node.ensure_clause
|
|
8
|
+
return super if node.statements.nil?
|
|
9
|
+
return super if node.begin_keyword_loc.nil?
|
|
10
|
+
|
|
11
|
+
body_text = @file_source.byteslice(node.statements.location.start_offset, node.statements.location.length)
|
|
12
|
+
add_mutation(
|
|
13
|
+
offset: node.location.start_offset,
|
|
14
|
+
length: node.location.length,
|
|
15
|
+
replacement: body_text,
|
|
16
|
+
node: node
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
super
|
|
20
|
+
end
|
|
21
|
+
end
|