evilution 0.27.0 → 0.29.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/interactions.jsonl +65 -0
- data/.rubocop_todo.yml +0 -1
- data/CHANGELOG.md +39 -0
- data/README.md +19 -0
- data/lib/evilution/ast/constant_names.rb +28 -11
- data/lib/evilution/ast/pattern/parser.rb +29 -17
- data/lib/evilution/baseline.rb +5 -4
- data/lib/evilution/cli/commands/session_diff.rb +6 -4
- data/lib/evilution/cli/commands/subjects.rb +6 -3
- data/lib/evilution/cli/commands/util_mutation.rb +24 -19
- data/lib/evilution/cli/parser/command_extractor.rb +9 -11
- data/lib/evilution/cli/parser/file_args.rb +3 -1
- data/lib/evilution/cli/parser/options_builder.rb +36 -1
- data/lib/evilution/cli/parser/stdin_reader.rb +2 -2
- data/lib/evilution/cli/parser.rb +18 -20
- data/lib/evilution/cli/printers/environment.rb +19 -19
- data/lib/evilution/cli/printers/session_diff.rb +8 -8
- data/lib/evilution/compare/diff_extractor/evilution.rb +22 -0
- data/lib/evilution/compare/diff_extractor/mutant.rb +30 -0
- data/lib/evilution/compare/diff_extractor.rb +6 -0
- data/lib/evilution/compare/fingerprint.rb +15 -72
- data/lib/evilution/compare/line_normalizer.rb +72 -0
- data/lib/evilution/compare/normalizer.rb +27 -9
- data/lib/evilution/config/validators/profile.rb +11 -0
- data/lib/evilution/config.rb +49 -32
- data/lib/evilution/disable_comment.rb +21 -12
- data/lib/evilution/integration/crash_detector.rb +2 -2
- data/lib/evilution/integration/loading/mutation_applier.rb +17 -12
- data/lib/evilution/integration/loading/source_evaluator.rb +6 -2
- data/lib/evilution/integration/minitest.rb +25 -16
- data/lib/evilution/integration/minitest_crash_detector.rb +2 -2
- data/lib/evilution/integration/rspec/state_guard/object_space_example_groups.rb +11 -3
- data/lib/evilution/integration/rspec.rb +4 -0
- data/lib/evilution/isolation/fork.rb +43 -28
- data/lib/evilution/isolation/in_process.rb +10 -6
- data/lib/evilution/mcp/info_tool/actions/subjects.rb +32 -23
- data/lib/evilution/mcp/info_tool/actions/tests.rb +22 -12
- data/lib/evilution/mcp/info_tool/request_parser.rb +3 -1
- data/lib/evilution/mcp/info_tool.rb +7 -3
- data/lib/evilution/mcp/mutate_tool/option_parser.rb +3 -1
- data/lib/evilution/mcp/mutate_tool/progress_streamer.rb +5 -1
- data/lib/evilution/mcp/mutate_tool/survived_enricher.rb +19 -9
- data/lib/evilution/mcp/mutate_tool.rb +27 -14
- data/lib/evilution/mcp/session_tool.rb +27 -20
- data/lib/evilution/mutation.rb +60 -42
- data/lib/evilution/mutator/base.rb +23 -21
- data/lib/evilution/mutator/operator/argument_nil_substitution.rb +11 -14
- data/lib/evilution/mutator/operator/argument_removal.rb +11 -14
- data/lib/evilution/mutator/operator/begin_unwrap.rb +17 -5
- data/lib/evilution/mutator/operator/bitwise_complement.rb +26 -19
- data/lib/evilution/mutator/operator/block_param_removal.rb +18 -8
- data/lib/evilution/mutator/operator/block_pass_removal.rb +19 -15
- data/lib/evilution/mutator/operator/case_when.rb +7 -5
- data/lib/evilution/mutator/operator/conditional_branch.rb +22 -22
- data/lib/evilution/mutator/operator/equality_to_identity.rb +8 -3
- data/lib/evilution/mutator/operator/explicit_super_mutation.rb +17 -13
- data/lib/evilution/mutator/operator/index_to_at.rb +5 -4
- data/lib/evilution/mutator/operator/index_to_dig.rb +12 -6
- data/lib/evilution/mutator/operator/index_to_fetch.rb +5 -4
- data/lib/evilution/mutator/operator/keyword_argument.rb +30 -25
- data/lib/evilution/mutator/operator/mixin_removal.rb +20 -14
- data/lib/evilution/mutator/operator/multiple_assignment.rb +12 -13
- data/lib/evilution/mutator/operator/predicate_to_nil.rb +20 -0
- data/lib/evilution/mutator/operator/receiver_replacement.rb +9 -6
- data/lib/evilution/mutator/operator/regex_simplification.rb +62 -67
- data/lib/evilution/mutator/operator/rescue_body_replacement.rb +9 -8
- data/lib/evilution/mutator/operator/rescue_removal.rb +4 -7
- data/lib/evilution/mutator/operator/superclass_removal.rb +21 -15
- data/lib/evilution/mutator/registry.rb +20 -0
- data/lib/evilution/parallel/work_queue/channel/frame.rb +5 -1
- data/lib/evilution/parallel/work_queue/dispatcher.rb +15 -8
- data/lib/evilution/parallel/work_queue/worker/loop.rb +1 -1
- data/lib/evilution/parallel/work_queue/worker.rb +10 -7
- data/lib/evilution/parallel/work_queue.rb +35 -18
- data/lib/evilution/process_cleanup.rb +19 -0
- data/lib/evilution/reporter/cli/item_formatters/coverage_gap.rb +13 -8
- data/lib/evilution/reporter/cli/line_formatters/mutations.rb +17 -8
- data/lib/evilution/reporter/html/baseline_keys.rb +1 -1
- data/lib/evilution/reporter/html/diff_formatter.rb +1 -1
- data/lib/evilution/reporter/html/escape.rb +1 -1
- data/lib/evilution/reporter/html/section.rb +1 -1
- data/lib/evilution/reporter/html/sections.rb +4 -2
- data/lib/evilution/reporter/html/stylesheet.rb +1 -1
- data/lib/evilution/reporter/html.rb +8 -3
- data/lib/evilution/reporter/json.rb +52 -18
- data/lib/evilution/reporter/suggestion/diff_helpers.rb +0 -13
- data/lib/evilution/reporter/suggestion/diff_lines.rb +28 -0
- data/lib/evilution/reporter/suggestion/registry.rb +1 -5
- data/lib/evilution/reporter/suggestion/templates/generic.rb +1 -1
- data/lib/evilution/reporter/suggestion/templates/minitest.rb +361 -649
- data/lib/evilution/reporter/suggestion/templates/rspec.rb +362 -603
- data/lib/evilution/reporter/suggestion/templates.rb +6 -0
- data/lib/evilution/result/error_info.rb +20 -0
- data/lib/evilution/result/memory_stats.rb +20 -0
- data/lib/evilution/result/mutation_result.rb +30 -14
- data/lib/evilution/runner/baseline_runner.rb +16 -10
- data/lib/evilution/runner/diagnostics.rb +14 -11
- data/lib/evilution/runner/isolation_resolver.rb +12 -11
- data/lib/evilution/runner/mutation_executor/mutation_runner.rb +1 -3
- data/lib/evilution/runner/mutation_executor/neutralization_pipeline.rb +1 -2
- data/lib/evilution/runner/mutation_executor/neutralizer/baseline_failed.rb +3 -10
- data/lib/evilution/runner/mutation_executor/neutralizer/infra_error.rb +3 -10
- data/lib/evilution/runner/mutation_executor/neutralizer.rb +11 -0
- data/lib/evilution/runner/mutation_executor/result_cache.rb +4 -4
- data/lib/evilution/runner/mutation_executor/result_notifier.rb +1 -3
- data/lib/evilution/runner/mutation_executor/result_packer.rb +11 -9
- data/lib/evilution/runner/mutation_executor/strategy/parallel.rb +33 -13
- data/lib/evilution/runner/mutation_executor/strategy/sequential.rb +2 -4
- data/lib/evilution/runner/mutation_executor/strategy.rb +11 -0
- data/lib/evilution/runner/mutation_executor.rb +14 -20
- data/lib/evilution/runner/mutation_planner.rb +38 -19
- data/lib/evilution/runner/report_publisher.rb +1 -2
- data/lib/evilution/runner/subject_pipeline.rb +22 -13
- data/lib/evilution/runner.rb +36 -34
- data/lib/evilution/session/diff.rb +15 -6
- data/lib/evilution/spec_ast_cache.rb +26 -12
- data/lib/evilution/version.rb +1 -1
- data/lib/evilution.rb +1 -0
- data/script/memory_check +14 -6
- data/scripts/benchmark_density +10 -9
- data/scripts/compare_mutations +38 -21
- data/scripts/mutant_json_adapter +7 -4
- metadata +15 -3
- data/lib/evilution/reporter/html/namespace.rb +0 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 607074e45fead28ec35facc8fadadbb44e0dc6429ee00ef37edf92145bc4b7fc
|
|
4
|
+
data.tar.gz: 3bc840215dff4272ea6da9aad5ebed982827507f765c6cadc444bd1f3fb63ddc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5febff050f2670b2ab5f1cbeac9bfc41b0f30edc3e5ee39591036da0ef9ccf5d1273c0abe0cc828d289a959c2e135adb348980882a931d263df65e4b8a4e2299
|
|
7
|
+
data.tar.gz: 1f0d38cab915255ce1742350255cce291fd9c6e3bbdc649af778750089ca9b219c7f2553d1de40c6b2ac72b128310c56d7fff3ecc99ee2c14db42680dd0f3b2b
|
data/.beads/interactions.jsonl
CHANGED
|
@@ -245,3 +245,68 @@
|
|
|
245
245
|
{"id":"int-9d182026","kind":"field_change","created_at":"2026-04-25T17:54:26.23542935Z","actor":"Denis Kiselev","issue_id":"EV-67yh","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged via PR #897"}}
|
|
246
246
|
{"id":"int-7ede49f5","kind":"field_change","created_at":"2026-04-25T18:16:18.358350457Z","actor":"Denis Kiselev","issue_id":"EV-zvhp","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged via PR #898"}}
|
|
247
247
|
{"id":"int-c28f7ee0","kind":"field_change","created_at":"2026-04-26T02:28:38.321564801Z","actor":"Denis Kiselev","issue_id":"EV-vev8","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged via PR #899"}}
|
|
248
|
+
{"id":"int-111f631a","kind":"field_change","created_at":"2026-04-26T07:57:40.59622849Z","actor":"Denis Kiselev","issue_id":"EV-p5vh","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Released in v0.27.0 (PR #901 merged, release PR #902 shipped)"}}
|
|
249
|
+
{"id":"int-5a159da9","kind":"field_change","created_at":"2026-04-29T11:51:40.86913186Z","actor":"Denis Kiselev","issue_id":"EV-vm61","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Closed"}}
|
|
250
|
+
{"id":"int-1787900f","kind":"field_change","created_at":"2026-04-30T02:40:52.288627178Z","actor":"Denis Kiselev","issue_id":"EV-v1i6","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"merged via PR #911"}}
|
|
251
|
+
{"id":"int-08958dc0","kind":"field_change","created_at":"2026-04-30T03:15:14.369272687Z","actor":"Denis Kiselev","issue_id":"EV-x3s0","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"merged via PR #912"}}
|
|
252
|
+
{"id":"int-e013b002","kind":"field_change","created_at":"2026-04-30T05:46:44.70653253Z","actor":"Denis Kiselev","issue_id":"EV-eww3","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"merged via PR #913"}}
|
|
253
|
+
{"id":"int-7b15a1b0","kind":"field_change","created_at":"2026-04-30T09:30:21.057039785Z","actor":"Denis Kiselev","issue_id":"EV-7rov","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"merged via PR #914"}}
|
|
254
|
+
{"id":"int-71c9f98d","kind":"field_change","created_at":"2026-04-30T12:30:36.528587334Z","actor":"Denis Kiselev","issue_id":"EV-318q","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"merged"}}
|
|
255
|
+
{"id":"int-42eadbdc","kind":"field_change","created_at":"2026-04-30T16:09:00.539400752Z","actor":"Denis Kiselev","issue_id":"EV-voay","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"merged"}}
|
|
256
|
+
{"id":"int-cb4c0ebf","kind":"field_change","created_at":"2026-05-01T02:38:05.091612839Z","actor":"Denis Kiselev","issue_id":"EV-t918","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"merged via PR #918"}}
|
|
257
|
+
{"id":"int-ffa8f0b2","kind":"field_change","created_at":"2026-05-01T17:47:23.967998678Z","actor":"Denis Kiselev","issue_id":"EV-m3ta","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"merged"}}
|
|
258
|
+
{"id":"int-b9cb7738","kind":"field_change","created_at":"2026-05-01T18:06:39.17748775Z","actor":"Denis Kiselev","issue_id":"EV-gffv","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"merged"}}
|
|
259
|
+
{"id":"int-983c2fe6","kind":"field_change","created_at":"2026-05-02T02:47:27.113692488Z","actor":"Denis Kiselev","issue_id":"EV-3t8l","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Audit confirmed feature already fully implemented in current master: --fail-fast CLI flag, .evilution.yml key, FailFast validator, ResultNotifier trips at threshold, sequential+parallel strategies short-circuit, summary.truncated? indicator, reporter notices (CLI/HTML/JSON), full spec coverage across notifier/sequential/parallel/runner/parser. CI signal available via summary.truncated? in reports."}}
|
|
260
|
+
{"id":"int-1790a8b7","kind":"field_change","created_at":"2026-05-02T17:53:56.73309749Z","actor":"Denis Kiselev","issue_id":"EV-2gpj","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Audit confirmed refactor already done: ProcessCleanup.safe_kill / safe_wait helpers extracted to lib/evilution/process_cleanup.rb (lines 8-18), used by baseline.rb (lines 85,93,94) and parallel/work_queue/worker.rb. No Style/RescueModifier disables remain anywhere in lib/. bundle exec rubocop lib/evilution/baseline.rb clean. Existing specs pass."}}
|
|
261
|
+
{"id":"int-a4ad60a6","kind":"field_change","created_at":"2026-05-06T03:30:34.744627494Z","actor":"Denis Kiselev","issue_id":"EV-s24s","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Closed"}}
|
|
262
|
+
{"id":"int-076a3c83","kind":"field_change","created_at":"2026-05-06T03:30:41.864846348Z","actor":"Denis Kiselev","issue_id":"EV-s24s","extra":{"field":"status","new_value":"in_progress","old_value":"closed"}}
|
|
263
|
+
{"id":"int-01566863","kind":"field_change","created_at":"2026-05-06T07:19:50.242235949Z","actor":"Denis Kiselev","issue_id":"EV-y27w","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
264
|
+
{"id":"int-2ead0736","kind":"field_change","created_at":"2026-05-06T07:19:50.702277885Z","actor":"Denis Kiselev","issue_id":"EV-2luk","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
265
|
+
{"id":"int-57a7f7bd","kind":"field_change","created_at":"2026-05-06T07:19:51.207502619Z","actor":"Denis Kiselev","issue_id":"EV-6pj3","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
266
|
+
{"id":"int-6c362b36","kind":"field_change","created_at":"2026-05-06T07:19:51.688513357Z","actor":"Denis Kiselev","issue_id":"EV-675y","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
267
|
+
{"id":"int-5debf846","kind":"field_change","created_at":"2026-05-06T07:19:52.17818897Z","actor":"Denis Kiselev","issue_id":"EV-g8pq","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
268
|
+
{"id":"int-53e2ac7d","kind":"field_change","created_at":"2026-05-06T07:19:52.618140112Z","actor":"Denis Kiselev","issue_id":"EV-7wi7","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
269
|
+
{"id":"int-dccffc47","kind":"field_change","created_at":"2026-05-06T07:19:53.069605194Z","actor":"Denis Kiselev","issue_id":"EV-cz6e","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
270
|
+
{"id":"int-58d8260f","kind":"field_change","created_at":"2026-05-06T07:19:53.543437653Z","actor":"Denis Kiselev","issue_id":"EV-rmzx","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
271
|
+
{"id":"int-789dee22","kind":"field_change","created_at":"2026-05-06T07:19:53.965510546Z","actor":"Denis Kiselev","issue_id":"EV-5qg6","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
272
|
+
{"id":"int-85fdbe1f","kind":"field_change","created_at":"2026-05-06T07:19:54.37777323Z","actor":"Denis Kiselev","issue_id":"EV-pfz5","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
273
|
+
{"id":"int-c61f5bf7","kind":"field_change","created_at":"2026-05-06T07:19:54.831384506Z","actor":"Denis Kiselev","issue_id":"EV-ynvi","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
274
|
+
{"id":"int-f66ee100","kind":"field_change","created_at":"2026-05-06T07:19:55.253094559Z","actor":"Denis Kiselev","issue_id":"EV-ltmi","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
275
|
+
{"id":"int-d0d38103","kind":"field_change","created_at":"2026-05-06T07:19:55.730451387Z","actor":"Denis Kiselev","issue_id":"EV-dha6","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
276
|
+
{"id":"int-14872708","kind":"field_change","created_at":"2026-05-06T07:19:56.276618255Z","actor":"Denis Kiselev","issue_id":"EV-gml5","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
277
|
+
{"id":"int-3a46d2c8","kind":"field_change","created_at":"2026-05-06T07:19:56.746453231Z","actor":"Denis Kiselev","issue_id":"EV-wg1v","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
278
|
+
{"id":"int-7e6cad25","kind":"field_change","created_at":"2026-05-06T07:19:57.199497315Z","actor":"Denis Kiselev","issue_id":"EV-jf8v","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
279
|
+
{"id":"int-596241e1","kind":"field_change","created_at":"2026-05-06T07:19:57.648508843Z","actor":"Denis Kiselev","issue_id":"EV-p699","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
280
|
+
{"id":"int-47d13782","kind":"field_change","created_at":"2026-05-06T07:19:58.119871705Z","actor":"Denis Kiselev","issue_id":"EV-mur2","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
281
|
+
{"id":"int-5bcb3ba0","kind":"field_change","created_at":"2026-05-06T07:19:58.593580083Z","actor":"Denis Kiselev","issue_id":"EV-t05i","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
282
|
+
{"id":"int-d0a8a49b","kind":"field_change","created_at":"2026-05-06T07:19:59.066030169Z","actor":"Denis Kiselev","issue_id":"EV-t3os","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
283
|
+
{"id":"int-c5fd767e","kind":"field_change","created_at":"2026-05-06T07:19:59.519830731Z","actor":"Denis Kiselev","issue_id":"EV-70v7","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
284
|
+
{"id":"int-ee96343f","kind":"field_change","created_at":"2026-05-06T07:19:59.945079843Z","actor":"Denis Kiselev","issue_id":"EV-g067","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
285
|
+
{"id":"int-1088cf0f","kind":"field_change","created_at":"2026-05-06T07:20:00.369713429Z","actor":"Denis Kiselev","issue_id":"EV-vybg","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
286
|
+
{"id":"int-677bcb4c","kind":"field_change","created_at":"2026-05-06T07:20:00.849595223Z","actor":"Denis Kiselev","issue_id":"EV-b2j8","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
287
|
+
{"id":"int-49853be8","kind":"field_change","created_at":"2026-05-06T07:20:01.309796737Z","actor":"Denis Kiselev","issue_id":"EV-jxio","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
288
|
+
{"id":"int-7d1d715d","kind":"field_change","created_at":"2026-05-06T07:20:01.802408893Z","actor":"Denis Kiselev","issue_id":"EV-v1df","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
289
|
+
{"id":"int-f8751a4f","kind":"field_change","created_at":"2026-05-06T07:20:02.269343453Z","actor":"Denis Kiselev","issue_id":"EV-05x3","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
290
|
+
{"id":"int-98efe12b","kind":"field_change","created_at":"2026-05-06T07:20:02.712854653Z","actor":"Denis Kiselev","issue_id":"EV-neqr","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
291
|
+
{"id":"int-2fab6f44","kind":"field_change","created_at":"2026-05-06T07:20:03.192828055Z","actor":"Denis Kiselev","issue_id":"EV-ugrl","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
292
|
+
{"id":"int-64c45537","kind":"field_change","created_at":"2026-05-06T07:20:03.645870239Z","actor":"Denis Kiselev","issue_id":"EV-1czz","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
293
|
+
{"id":"int-b226aaaa","kind":"field_change","created_at":"2026-05-06T07:20:04.115581656Z","actor":"Denis Kiselev","issue_id":"EV-nsp3","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
294
|
+
{"id":"int-40a144bf","kind":"field_change","created_at":"2026-05-06T07:20:04.57274421Z","actor":"Denis Kiselev","issue_id":"EV-bx35","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
295
|
+
{"id":"int-bb3376cc","kind":"field_change","created_at":"2026-05-06T07:20:05.130537402Z","actor":"Denis Kiselev","issue_id":"EV-2viy","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
296
|
+
{"id":"int-6ae5b449","kind":"field_change","created_at":"2026-05-06T07:20:05.597150157Z","actor":"Denis Kiselev","issue_id":"EV-0nre","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
297
|
+
{"id":"int-f37c08ed","kind":"field_change","created_at":"2026-05-06T07:20:06.22033588Z","actor":"Denis Kiselev","issue_id":"EV-n4ai","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
298
|
+
{"id":"int-f1cc76b6","kind":"field_change","created_at":"2026-05-06T07:20:06.659235495Z","actor":"Denis Kiselev","issue_id":"EV-oyus","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
299
|
+
{"id":"int-7d54a6e7","kind":"field_change","created_at":"2026-05-06T07:20:07.091511841Z","actor":"Denis Kiselev","issue_id":"EV-h46p","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
300
|
+
{"id":"int-2bea4b64","kind":"field_change","created_at":"2026-05-06T07:20:06.87585377Z","actor":"Denis Kiselev","issue_id":"EV-n05g","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
301
|
+
{"id":"int-3b25e06c","kind":"field_change","created_at":"2026-05-06T07:20:06.448623384Z","actor":"Denis Kiselev","issue_id":"EV-htfi","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
302
|
+
{"id":"int-9fedcb1f","kind":"field_change","created_at":"2026-05-06T07:20:06.870511987Z","actor":"Denis Kiselev","issue_id":"EV-86au","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
303
|
+
{"id":"int-55d2834d","kind":"field_change","created_at":"2026-05-06T07:20:07.313207411Z","actor":"Denis Kiselev","issue_id":"EV-fyq8","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
304
|
+
{"id":"int-44442358","kind":"field_change","created_at":"2026-05-06T07:20:07.761317317Z","actor":"Denis Kiselev","issue_id":"EV-jxr1","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
305
|
+
{"id":"int-1ec5b145","kind":"field_change","created_at":"2026-05-06T07:20:08.263779524Z","actor":"Denis Kiselev","issue_id":"EV-0lhb","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
306
|
+
{"id":"int-3177494f","kind":"field_change","created_at":"2026-05-06T07:20:08.690447157Z","actor":"Denis Kiselev","issue_id":"EV-6xeh","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
307
|
+
{"id":"int-c3e862ed","kind":"field_change","created_at":"2026-05-06T07:20:09.150754493Z","actor":"Denis Kiselev","issue_id":"EV-n5vx","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
308
|
+
{"id":"int-bbb95b03","kind":"field_change","created_at":"2026-05-06T07:20:09.610416065Z","actor":"Denis Kiselev","issue_id":"EV-pbo4","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
309
|
+
{"id":"int-42dadd38","kind":"field_change","created_at":"2026-05-06T07:20:10.034048271Z","actor":"Denis Kiselev","issue_id":"EV-45kf","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
310
|
+
{"id":"int-2973ca4f","kind":"field_change","created_at":"2026-05-06T07:20:10.503854404Z","actor":"Denis Kiselev","issue_id":"EV-t2o9","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
311
|
+
{"id":"int-523d3c8c","kind":"field_change","created_at":"2026-05-06T07:20:11.036667234Z","actor":"Denis Kiselev","issue_id":"EV-qtvs","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
|
312
|
+
{"id":"int-be905e1b","kind":"field_change","created_at":"2026-05-06T07:20:11.472283074Z","actor":"Denis Kiselev","issue_id":"EV-psit","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
|
data/.rubocop_todo.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.29.0] - 2026-05-06
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Internal codebase hygiene sweep — `Metrics/AbcSize` ceiling tightened from `25` → `17`** — every method exceeding the new threshold was refactored via pure extract-method (no behavior change). ~48 sites across `lib/evilution/{ast,cli,compare,config,disable_comment,integration,mcp,mutation,mutator,parallel,reporter,runner,session,spec_ast_cache}` plus supporting `scripts/` utilities. No public API, CLI flag, or output changes; mutation operators and report emission are bit-identical. The upper bound on per-method ABC is now strictly enforced repo-wide — only `lib/evilution/runner.rb` remains in `.rubocop_todo.yml` (#371, PR #1160 + per-file sub-PRs)
|
|
8
|
+
- **Tuple-return methods across the runner pipeline migrated to named `Data.define` value objects** — internal-only refactor introducing typed return shapes for `Runner::MutationExecutor#call` (→`ExecutionResult`), `Runner::MutationPlanner#call` (→`Plan` plus internal `GenerationResult` / `DisabledFilterResult` / `SigFilterResult` / `EquivalentFilterResult`), `Parallel::WorkQueue` outputs, `Cache#partition` (→`Partition`), `Config.normalize_limit` (→`LimitResult`), `Mutation::Slicer.collect_chain` (→`Chain`), `slice_affected_lines` (→`AffectedSlices`), `CLI::Parser::FilesAndRanges` (→`ParsedPaths`), and assorted CLI command helpers. Improves call-site readability without affecting external behavior (#948, PR #1094; #949, PR #1095; #950, PR #1096; #951, PR #1097; #952, PR #1098; #953, PR #1099; #954, PR #1100; #955, PR #1101; #956, PR #1102)
|
|
9
|
+
|
|
10
|
+
## [0.28.0] - 2026-05-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Operator profiles: `default` (current 72-operator set) and `strict` (adds aggressive truthiness mutators)** — pre-merge audits can opt into a more sensitive operator mix. The `strict` profile registers `PredicateToNil`, which replaces every `x.predicate?` call with `nil` to surface tests that only assert truthiness rather than exact return values. Wired through CLI (`--profile=strict`, `--strict` shortcut), `.evilution.yml` (`profile: strict`), and a new `Evilution::Mutator::Registry.for_profile(:default | :strict)` factory. `default` is unchanged, so existing CI runs are not affected (#920, PR #926)
|
|
15
|
+
- **Multi-file batch invocation documented** — `evilution path/a.rb path/b.rb path/c.rb` runs every file in a single Runner invocation so the framework (Rails, Sorbet, etc.) and the `preload` chain load **once** in the parent process. With `--isolation=fork` (default for Rails projects under `auto`), every per-mutation fork branches off the warmed parent — materially faster than `for f in ...; do bundle exec evilution run "$f"; done`. README now has a "5a. Multi-file batch scan" workflow section and an end-to-end runner spec covers two positional file paths; session save/load preserves per-file paths in `survived[].file` (#922, PR #927)
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **`Compare::Normalizer` mis-classified Mutant payload lines whose mutated source started with `--` or `++` as unified-diff headers** — pre-existing bug in `extract_from_mutant_diff` that would, for example, drop a removed line `--flag` (emitted as `---flag` in the diff). The new `DiffExtractor::Mutant` requires a trailing space after `---`/`+++` to match a header, preserving real payload. Equivalent Evilution/Mutant mutations on such lines now hash identically and `compare` no longer reports false additions/removals (#917, PR #934)
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **Internal `Evilution::Compare::Fingerprint` SOLID refactor** — module-function form replaced with a class taking injectable `(extractor:, normalizer:)` collaborators and a `#call(diff:, file_path:, line:)` interface. Diff parsing extracted into `Evilution::Compare::DiffExtractor::{Evilution,Mutant}` strategy classes (one per format, common duck-typed interface), enabling open/closed extension for future tools without touching the orchestrator. `Compare::Normalizer` constructs both fingerprints once and reuses them across records (#917, PR #934)
|
|
24
|
+
- **`Evilution::Mutation` migrated to value-object composition** — sources, slice, parse status now Data.define-backed value objects (#822, PR #907)
|
|
25
|
+
- **`Evilution::Result::MutationResult` encapsulates memory and error state in dedicated Data.define value objects** — `MemoryStats` and `ErrorInfo` instead of flat positional fields (#823, PR #907)
|
|
26
|
+
- **`Reporter::Suggestion` registry/templates and the RSpec/Minitest template builders unified** — single `build` entrypoint per format (#824, PR #908; #849, PR #905; #850, PR #904)
|
|
27
|
+
- **`Reporter::HTML` namespace inlined into `report.rb`** — separate `namespace.rb` removed, autoload pattern adopted for sub-templates (#826, PR #909)
|
|
28
|
+
- **`Compare::LineNormalizer` extracted into its own class** — whitespace collapse separated from fingerprint orchestration (#829, PR #832)
|
|
29
|
+
- **`Evilution::Config` attribute assignment migrated to a transformation map** — single source of truth for type coercion across simple attributes (#830)
|
|
30
|
+
- **`Runner` `require` chain consolidated** — sub-component loading now centralized; circular-require pitfalls in `MutationExecutor` resolved with `Module#autoload` for child strategy/neutralizer files (#831)
|
|
31
|
+
- **Process cleanup helpers extracted into `Evilution::ProcessCleanup`** — `safe_kill(sig, pid)` and `safe_wait(pid)` shared by `Baseline`, `Isolation::Fork`, and `WorkQueue::Worker`, replacing scattered inline `rescue` modifiers swallowing `Errno::ESRCH`/`ECHILD` (#838)
|
|
32
|
+
- **`ProgressStreamer` and `Loop` error handling tightened** — generic `StandardError` rescues replaced with specific `Errno::EPIPE`/`Errno::EBADF`/etc.; once-only warning suppression added so a flood of failures cannot drown stderr (#827, #840)
|
|
33
|
+
- **Crash detector predicate methods renamed** — `has_*?` → `*?` per Ruby/RSpec conventions (`have_X` matcher calls `has_X?`; the renamed methods are still picked up by `be_X` matchers used in specs) (#839)
|
|
34
|
+
- **Rubocop hygiene sweep across 6 sites** — `Style/RescueModifier`, `Lint/UnusedMethodArgument`, `Lint/SuppressedException` (3 instances), `Security/Eval`, and `Security/MarshalLoad` (3 instances) inline disable comments removed in favor of either narrowed code, explanatory rescue-body comments, or main-`.rubocop.yml` per-file Excludes documented with the underlying trust boundary (#832, #833, #834, #835, #836, #837)
|
|
35
|
+
|
|
36
|
+
### Documentation
|
|
37
|
+
|
|
38
|
+
- **README "Operator Profiles" subsection** — explains the `default` vs `strict` profiles, how to opt in (CLI, config, shortcut), and what `strict` adds today (#920, PR #926)
|
|
39
|
+
- **README "5a. Multi-file batch scan" workflow** — documents Rails-loads-once amortisation and qualifies the speed claim by isolation mode (`fork` vs `in_process`) (#922, PR #927)
|
|
40
|
+
- **`.evilution.yml` template gained a `profile:` block** — generated by `evilution init` (#920, PR #926)
|
|
41
|
+
|
|
3
42
|
## [0.27.0] - 2026-04-26
|
|
4
43
|
|
|
5
44
|
### Added
|
data/README.md
CHANGED
|
@@ -118,6 +118,17 @@ The shorter alias `evil` ships alongside `evilution` and accepts identical argum
|
|
|
118
118
|
| `--fallback-full-suite` | Boolean | false | When no matching spec/test resolves for a mutation, run the whole test suite instead of marking it `:unresolved` and skipping. |
|
|
119
119
|
| `--baseline-session PATH` | String | _(none)_ | Saved session file for HTML report comparison. |
|
|
120
120
|
| `-e CODE`, `--eval CODE` | String | _(none)_ | Inline Ruby code for `util mutation` command. |
|
|
121
|
+
| `--profile NAME` | String | `default` | Operator profile: `default` or `strict`. `strict` adds aggressive truthiness mutators (e.g. replaces `x.predicate?` with `nil`) intended for pre-merge audits. |
|
|
122
|
+
| `--strict` | Boolean | false | Shortcut for `--profile=strict`. |
|
|
123
|
+
|
|
124
|
+
### Operator Profiles
|
|
125
|
+
|
|
126
|
+
Two profiles ship out of the box:
|
|
127
|
+
|
|
128
|
+
- **`default`** — the 72 stable operators registered in `Mutator::Registry.default`. Suitable for everyday CI runs; balances coverage signal against survivor noise.
|
|
129
|
+
- **`strict`** — adds extra truthiness mutators on top of `default`. Currently `PredicateToNil` (replaces every `x.predicate?` call with `nil` to surface tests that only assert truthiness rather than exact return values). Use for pre-merge audits where you want maximum sensitivity at the cost of more survivors.
|
|
130
|
+
|
|
131
|
+
Set via `--profile=strict`, the `--strict` shortcut, or `profile: strict` in `.evilution.yml`.
|
|
121
132
|
|
|
122
133
|
### Exit Codes
|
|
123
134
|
|
|
@@ -484,6 +495,14 @@ bundle exec evilution run lib/specific_file.rb --format json
|
|
|
484
495
|
|
|
485
496
|
Use when you know which file was modified and want to verify its test coverage.
|
|
486
497
|
|
|
498
|
+
### 5a. Multi-file batch scan
|
|
499
|
+
|
|
500
|
+
```bash
|
|
501
|
+
bundle exec evilution run lib/models/user.rb lib/models/account.rb lib/models/order.rb
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
Pass multiple file paths on a single invocation to amortise startup cost. The framework (Rails, Sorbet, etc.) and the `preload` chain (`spec/rails_helper.rb` → `spec/spec_helper.rb` → `test/test_helper.rb`) load **once** in the parent process. When `--isolation=fork` is selected (the default `--isolation=auto` resolves to `fork` on Rails projects), every subsequent mutation across all files forks from that warmed parent — materially faster than scripting a `for f in ...; do bundle exec evilution run "$f"; done` loop, which pays the bootstrap per file. With `--isolation=in_process` (default for non-Rails projects under `auto`), there is no per-mutation fork, but the parent-process boot still runs once instead of N times. Per-file paths and line numbers are preserved in the report (`survived[].file`, HTML grouping by source file).
|
|
505
|
+
|
|
487
506
|
### 6. Fixing surviving mutants
|
|
488
507
|
|
|
489
508
|
For each entry in `survived[]`:
|
|
@@ -17,18 +17,35 @@ class Evilution::AST::ConstantNames
|
|
|
17
17
|
private
|
|
18
18
|
|
|
19
19
|
def collect(node, nesting = [])
|
|
20
|
-
names = []
|
|
21
20
|
case node
|
|
22
|
-
when Prism::ModuleNode, Prism::ClassNode
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
names.concat(collect(node.body, nesting + [const])) if node.body
|
|
27
|
-
when Prism::ProgramNode
|
|
28
|
-
names.concat(collect(node.statements, nesting)) if node.statements
|
|
29
|
-
when Prism::StatementsNode
|
|
30
|
-
node.body.each { |child| names.concat(collect(child, nesting)) }
|
|
21
|
+
when Prism::ModuleNode, Prism::ClassNode then collect_class(node, nesting)
|
|
22
|
+
when Prism::ProgramNode then collect_program(node, nesting)
|
|
23
|
+
when Prism::StatementsNode then collect_statements(node, nesting)
|
|
24
|
+
else []
|
|
31
25
|
end
|
|
32
|
-
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def collect_class(node, nesting)
|
|
29
|
+
const = node.constant_path.full_name
|
|
30
|
+
qualified = qualify(const, nesting)
|
|
31
|
+
return [qualified] if node.body.nil?
|
|
32
|
+
|
|
33
|
+
[qualified] + collect(node.body, nesting + [const])
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def collect_program(node, nesting)
|
|
37
|
+
return [] if node.statements.nil?
|
|
38
|
+
|
|
39
|
+
collect(node.statements, nesting)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def collect_statements(node, nesting)
|
|
43
|
+
node.body.flat_map { |child| collect(child, nesting) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def qualify(const, nesting)
|
|
47
|
+
return const if nesting.empty? || const.include?("::")
|
|
48
|
+
|
|
49
|
+
"#{nesting.join("::")}::#{const}"
|
|
33
50
|
end
|
|
34
51
|
end
|
|
@@ -75,24 +75,36 @@ class Evilution::AST::Pattern::Parser
|
|
|
75
75
|
|
|
76
76
|
def parse_value
|
|
77
77
|
skip_whitespace
|
|
78
|
+
parse_negation || parse_deep_wildcard || parse_single_wildcard || parse_any_node || parse_value_or_nested
|
|
79
|
+
end
|
|
78
80
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
81
|
+
def parse_negation
|
|
82
|
+
return nil unless current_char == "!"
|
|
83
|
+
|
|
84
|
+
advance(1)
|
|
85
|
+
skip_whitespace
|
|
86
|
+
Evilution::AST::Pattern::NegationMatcher.new(parse_value)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def parse_deep_wildcard
|
|
90
|
+
return nil unless peek_string("**")
|
|
91
|
+
|
|
92
|
+
advance(2)
|
|
93
|
+
Evilution::AST::Pattern::DeepWildcardMatcher.new
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def parse_single_wildcard
|
|
97
|
+
return nil unless current_char == "*"
|
|
98
|
+
|
|
99
|
+
advance(1)
|
|
100
|
+
Evilution::AST::Pattern::WildcardValueMatcher.new
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def parse_any_node
|
|
104
|
+
return nil unless current_char == "_" && !identifier_continues?(1)
|
|
105
|
+
|
|
106
|
+
advance(1)
|
|
107
|
+
Evilution::AST::Pattern::AnyNodeMatcher.new
|
|
96
108
|
end
|
|
97
109
|
|
|
98
110
|
def parse_value_or_nested
|
data/lib/evilution/baseline.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "spec_resolver"
|
|
4
|
+
require_relative "process_cleanup"
|
|
4
5
|
|
|
5
6
|
class Evilution::Baseline
|
|
6
7
|
Result = Struct.new(:failed_spec_files, :duration) do
|
|
@@ -72,7 +73,7 @@ class Evilution::Baseline
|
|
|
72
73
|
Process.wait(pid)
|
|
73
74
|
return false if data.empty?
|
|
74
75
|
|
|
75
|
-
result = Marshal.load(data)
|
|
76
|
+
result = Marshal.load(data)
|
|
76
77
|
result[:passed]
|
|
77
78
|
else
|
|
78
79
|
terminate_child(pid)
|
|
@@ -81,7 +82,7 @@ class Evilution::Baseline
|
|
|
81
82
|
end
|
|
82
83
|
|
|
83
84
|
def terminate_child(pid)
|
|
84
|
-
|
|
85
|
+
Evilution::ProcessCleanup.safe_kill("TERM", pid)
|
|
85
86
|
_, status = Process.waitpid2(pid, Process::WNOHANG)
|
|
86
87
|
return if status
|
|
87
88
|
|
|
@@ -89,8 +90,8 @@ class Evilution::Baseline
|
|
|
89
90
|
_, status = Process.waitpid2(pid, Process::WNOHANG)
|
|
90
91
|
return if status
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
Evilution::ProcessCleanup.safe_kill("KILL", pid)
|
|
94
|
+
Evilution::ProcessCleanup.safe_wait(pid)
|
|
94
95
|
end
|
|
95
96
|
|
|
96
97
|
private
|
|
@@ -14,10 +14,7 @@ class Evilution::CLI::Commands::SessionDiff < Evilution::CLI::Command
|
|
|
14
14
|
def perform
|
|
15
15
|
raise Evilution::ConfigError, "two session file paths required" unless @files.length == 2
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
base_data = store.load(@files[0])
|
|
19
|
-
head_data = store.load(@files[1])
|
|
20
|
-
result = Evilution::Session::Diff.new.call(base_data, head_data)
|
|
17
|
+
result = compute_diff(@files)
|
|
21
18
|
Evilution::CLI::Printers::SessionDiff.new(result, format: @options[:format]).render(@stdout)
|
|
22
19
|
0
|
|
23
20
|
rescue ::JSON::ParserError => e
|
|
@@ -25,6 +22,11 @@ class Evilution::CLI::Commands::SessionDiff < Evilution::CLI::Command
|
|
|
25
22
|
rescue SystemCallError => e
|
|
26
23
|
raise Evilution::Error, e.message
|
|
27
24
|
end
|
|
25
|
+
|
|
26
|
+
def compute_diff(files)
|
|
27
|
+
store = Evilution::Session::Store.new
|
|
28
|
+
Evilution::Session::Diff.new.call(store.load(files[0]), store.load(files[1]))
|
|
29
|
+
end
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
Evilution::CLI::Dispatcher.register(:session_diff, Evilution::CLI::Commands::SessionDiff)
|
|
@@ -9,6 +9,9 @@ require_relative "../../runner"
|
|
|
9
9
|
require_relative "../../mutator"
|
|
10
10
|
|
|
11
11
|
class Evilution::CLI::Commands::Subjects < Evilution::CLI::Command
|
|
12
|
+
EntriesResult = Data.define(:entries, :total)
|
|
13
|
+
private_constant :EntriesResult
|
|
14
|
+
|
|
12
15
|
private
|
|
13
16
|
|
|
14
17
|
def perform
|
|
@@ -23,8 +26,8 @@ class Evilution::CLI::Commands::Subjects < Evilution::CLI::Command
|
|
|
23
26
|
return 0
|
|
24
27
|
end
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
Evilution::CLI::Printers::Subjects.new(entries, total_mutations: total).render(@stdout)
|
|
29
|
+
result = collect_entries(subjects, config)
|
|
30
|
+
Evilution::CLI::Printers::Subjects.new(result.entries, total_mutations: result.total).render(@stdout)
|
|
28
31
|
0
|
|
29
32
|
end
|
|
30
33
|
|
|
@@ -43,7 +46,7 @@ class Evilution::CLI::Commands::Subjects < Evilution::CLI::Command
|
|
|
43
46
|
subj.release_node!
|
|
44
47
|
end
|
|
45
48
|
|
|
46
|
-
|
|
49
|
+
EntriesResult.new(entries: entries, total: total)
|
|
47
50
|
end
|
|
48
51
|
end
|
|
49
52
|
|
|
@@ -11,11 +11,14 @@ require_relative "../../mutator/registry"
|
|
|
11
11
|
require_relative "../../ast/parser"
|
|
12
12
|
|
|
13
13
|
class Evilution::CLI::Commands::UtilMutation < Evilution::CLI::Command
|
|
14
|
+
SourceInput = Data.define(:source, :file_path)
|
|
15
|
+
private_constant :SourceInput
|
|
16
|
+
|
|
14
17
|
private
|
|
15
18
|
|
|
16
19
|
def perform
|
|
17
|
-
|
|
18
|
-
subjects = parse_source_to_subjects(source, file_path)
|
|
20
|
+
input = resolve_util_mutation_source
|
|
21
|
+
subjects = parse_source_to_subjects(input.source, input.file_path)
|
|
19
22
|
config = Evilution::Config.new(**@options)
|
|
20
23
|
registry = Evilution::Mutator::Registry.default
|
|
21
24
|
operator_options = build_operator_options(config)
|
|
@@ -33,24 +36,26 @@ class Evilution::CLI::Commands::UtilMutation < Evilution::CLI::Command
|
|
|
33
36
|
end
|
|
34
37
|
|
|
35
38
|
def resolve_util_mutation_source
|
|
36
|
-
if @options[:eval]
|
|
37
|
-
|
|
38
|
-
tmpfile.write(@options[:eval])
|
|
39
|
-
tmpfile.flush
|
|
40
|
-
@util_tmpfile = tmpfile
|
|
41
|
-
[@options[:eval], tmpfile.path]
|
|
42
|
-
elsif @files.first
|
|
43
|
-
path = @files.first
|
|
44
|
-
raise Evilution::Error, "file not found: #{path}" unless File.exist?(path)
|
|
39
|
+
return build_eval_source(@options[:eval]) if @options[:eval]
|
|
40
|
+
return build_file_source(@files.first) if @files.first
|
|
45
41
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
42
|
+
raise Evilution::Error, "source required: use -e 'code' or provide a file path"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def build_eval_source(code)
|
|
46
|
+
tmpfile = Tempfile.new(["evilution_eval", ".rb"])
|
|
47
|
+
tmpfile.write(code)
|
|
48
|
+
tmpfile.flush
|
|
49
|
+
@util_tmpfile = tmpfile
|
|
50
|
+
SourceInput.new(source: code, file_path: tmpfile.path)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def build_file_source(path)
|
|
54
|
+
raise Evilution::Error, "file not found: #{path}" unless File.exist?(path)
|
|
55
|
+
|
|
56
|
+
SourceInput.new(source: File.read(path), file_path: path)
|
|
57
|
+
rescue SystemCallError => e
|
|
58
|
+
raise Evilution::Error, e.message
|
|
54
59
|
end
|
|
55
60
|
|
|
56
61
|
def parse_source_to_subjects(source, file_label)
|
|
@@ -20,6 +20,13 @@ class Evilution::CLI::Parser::CommandExtractor
|
|
|
20
20
|
ENVIRONMENT_SUBCOMMANDS = { "show" => :environment_show }.freeze
|
|
21
21
|
UTIL_SUBCOMMANDS = { "mutation" => :util_mutation }.freeze
|
|
22
22
|
|
|
23
|
+
SUBCOMMAND_FAMILIES = {
|
|
24
|
+
"session" => [SESSION_SUBCOMMANDS, "session", "list, show, diff, gc"],
|
|
25
|
+
"tests" => [TESTS_SUBCOMMANDS, "tests", "list"],
|
|
26
|
+
"environment" => [ENVIRONMENT_SUBCOMMANDS, "environment", "show"],
|
|
27
|
+
"util" => [UTIL_SUBCOMMANDS, "util", "mutation"]
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
23
30
|
Result = Struct.new(:command, :remaining_argv, :parse_error)
|
|
24
31
|
|
|
25
32
|
def self.call(argv)
|
|
@@ -46,18 +53,9 @@ class Evilution::CLI::Parser::CommandExtractor
|
|
|
46
53
|
@argv.shift
|
|
47
54
|
elsif first == "run"
|
|
48
55
|
@argv.shift
|
|
49
|
-
elsif first
|
|
50
|
-
@argv.shift
|
|
51
|
-
extract_subcommand(SESSION_SUBCOMMANDS, "session", "list, show, diff, gc")
|
|
52
|
-
elsif first == "tests"
|
|
53
|
-
@argv.shift
|
|
54
|
-
extract_subcommand(TESTS_SUBCOMMANDS, "tests", "list")
|
|
55
|
-
elsif first == "environment"
|
|
56
|
-
@argv.shift
|
|
57
|
-
extract_subcommand(ENVIRONMENT_SUBCOMMANDS, "environment", "show")
|
|
58
|
-
elsif first == "util"
|
|
56
|
+
elsif SUBCOMMAND_FAMILIES.key?(first)
|
|
59
57
|
@argv.shift
|
|
60
|
-
extract_subcommand(
|
|
58
|
+
extract_subcommand(*SUBCOMMAND_FAMILIES[first])
|
|
61
59
|
end
|
|
62
60
|
end
|
|
63
61
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Evilution::CLI::Parser::FileArgs
|
|
4
|
+
ParsedPaths = Data.define(:files, :ranges)
|
|
5
|
+
|
|
4
6
|
module_function
|
|
5
7
|
|
|
6
8
|
def parse(raw_args)
|
|
@@ -15,7 +17,7 @@ module Evilution::CLI::Parser::FileArgs
|
|
|
15
17
|
ranges[file] = parse_line_range(range_str)
|
|
16
18
|
end
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
ParsedPaths.new(files: files, ranges: ranges)
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def expand_spec_dir(dir)
|
|
@@ -21,6 +21,9 @@ class Evilution::CLI::Parser::OptionsBuilder
|
|
|
21
21
|
add_core_options(opts)
|
|
22
22
|
add_filter_options(opts)
|
|
23
23
|
add_flag_options(opts)
|
|
24
|
+
add_runner_mode_options(opts)
|
|
25
|
+
add_output_options(opts)
|
|
26
|
+
add_profile_options(opts)
|
|
24
27
|
add_extra_flag_options(opts)
|
|
25
28
|
add_session_options(opts)
|
|
26
29
|
add_compare_options(opts)
|
|
@@ -46,11 +49,19 @@ class Evilution::CLI::Parser::OptionsBuilder
|
|
|
46
49
|
end
|
|
47
50
|
|
|
48
51
|
def add_filter_options(opts)
|
|
52
|
+
add_spec_filter_options(opts)
|
|
53
|
+
add_targeting_options(opts)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def add_spec_filter_options(opts)
|
|
49
57
|
opts.on("--min-score FLOAT", Float, "Minimum mutation score to pass") { |s| @options[:min_score] = s }
|
|
50
58
|
opts.on("--spec FILES", Array, "Spec files to run (comma-separated)") { |f| @options[:spec_files] = f }
|
|
51
59
|
opts.on("--spec-dir DIR", "Include all specs in DIR") { |d| expand_spec_dir(d) }
|
|
52
60
|
opts.on("--spec-pattern GLOB",
|
|
53
61
|
"Restrict resolved spec candidates to files matching GLOB") { |p| @options[:spec_pattern] = p }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def add_targeting_options(opts)
|
|
54
65
|
opts.on("--no-example-targeting",
|
|
55
66
|
"Disable per-mutation example targeting (run all examples in resolved spec files)") do
|
|
56
67
|
@options[:example_targeting] = false
|
|
@@ -75,13 +86,19 @@ class Evilution::CLI::Parser::OptionsBuilder
|
|
|
75
86
|
"Use --no-incremental to override `incremental: true` from the config file for one run.") do |v|
|
|
76
87
|
@options[:incremental] = v
|
|
77
88
|
end
|
|
89
|
+
opts.on("--stdin", "Read target file paths from stdin (one per line)") { @options[:stdin] = true }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def add_runner_mode_options(opts)
|
|
78
93
|
opts.on("--integration NAME", "Test integration: rspec, minitest (default: rspec)") { |i| @options[:integration] = i }
|
|
79
94
|
opts.on("--isolation STRATEGY", "Isolation: auto, fork, in_process (default: auto)") { |s| @options[:isolation] = s }
|
|
80
95
|
opts.on("--preload FILE", "Preload FILE in the parent process before forking " \
|
|
81
96
|
"(default: auto-detect spec/rails_helper.rb -> spec/spec_helper.rb -> " \
|
|
82
97
|
"test/test_helper.rb for Rails projects)") { |f| @options[:preload] = f }
|
|
83
98
|
opts.on("--no-preload", "Disable parent-process preload even for Rails projects") { @options[:preload] = false }
|
|
84
|
-
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def add_output_options(opts)
|
|
85
102
|
opts.on("--suggest-tests", "Generate concrete test code in suggestions (RSpec or Minitest)") { @options[:suggest_tests] = true }
|
|
86
103
|
opts.on("--no-progress", "Disable progress bar") { @options[:progress] = false }
|
|
87
104
|
opts.on("--quiet-children",
|
|
@@ -95,7 +112,19 @@ class Evilution::CLI::Parser::OptionsBuilder
|
|
|
95
112
|
end
|
|
96
113
|
end
|
|
97
114
|
|
|
115
|
+
def add_profile_options(opts)
|
|
116
|
+
opts.on("--profile NAME", "Operator profile: default, strict (default: default). " \
|
|
117
|
+
"strict adds aggressive truthiness mutators for pre-merge audits.") { |p| @options[:profile] = p }
|
|
118
|
+
opts.on("--strict", "Shortcut for --profile=strict") { @options[:profile] = "strict" }
|
|
119
|
+
end
|
|
120
|
+
|
|
98
121
|
def add_extra_flag_options(opts)
|
|
122
|
+
add_mutation_behavior_options(opts)
|
|
123
|
+
add_session_persistence_options(opts)
|
|
124
|
+
add_misc_extra_options(opts)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def add_mutation_behavior_options(opts)
|
|
99
128
|
opts.on("--skip-heredoc-literals", "Skip all string literal mutations inside heredocs") { @options[:skip_heredoc_literals] = true }
|
|
100
129
|
opts.on("--related-specs-heuristic", "Append related request/integration/feature/system specs for includes() mutations") do
|
|
101
130
|
@options[:related_specs_heuristic] = true
|
|
@@ -105,8 +134,14 @@ class Evilution::CLI::Parser::OptionsBuilder
|
|
|
105
134
|
@options[:fallback_to_full_suite] = true
|
|
106
135
|
end
|
|
107
136
|
opts.on("--show-disabled", "Report mutations skipped by # evilution:disable") { @options[:show_disabled] = true }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def add_session_persistence_options(opts)
|
|
108
140
|
opts.on("--baseline-session PATH", "Compare against a baseline session in HTML report") { |p| @options[:baseline_session] = p }
|
|
109
141
|
opts.on("--save-session", "Save session results to .evilution/results/") { @options[:save_session] = true }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def add_misc_extra_options(opts)
|
|
110
145
|
opts.on("-e", "--eval CODE", "Evaluate code snippet (for util mutation)") { |c| @options[:eval] = c }
|
|
111
146
|
opts.on("-v", "--verbose", "Verbose output") { @options[:verbose] = true }
|
|
112
147
|
opts.on("-q", "--quiet", "Suppress output") { @options[:quiet] = true }
|
|
@@ -22,7 +22,7 @@ class Evilution::CLI::Parser::StdinReader
|
|
|
22
22
|
line = line.strip
|
|
23
23
|
lines << line unless line.empty?
|
|
24
24
|
end
|
|
25
|
-
|
|
26
|
-
Result.new(files, ranges, nil)
|
|
25
|
+
parsed_paths = Evilution::CLI::Parser::FileArgs.parse(lines)
|
|
26
|
+
Result.new(parsed_paths.files, parsed_paths.ranges, nil)
|
|
27
27
|
end
|
|
28
28
|
end
|