evilution 0.24.0 → 0.26.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.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.beads/interactions.jsonl +210 -0
  3. data/.claude/prompts/architect.md +14 -1
  4. data/.claude/skills/create-issue/SKILL.md +55 -0
  5. data/CHANGELOG.md +51 -0
  6. data/README.md +80 -4
  7. data/exe/evil +6 -0
  8. data/lib/evilution/ast/constant_names.rb +34 -0
  9. data/lib/evilution/ast/source_surgeon.rb +15 -1
  10. data/lib/evilution/cli/commands/compare.rb +68 -0
  11. data/lib/evilution/cli/parser/command_extractor.rb +2 -1
  12. data/lib/evilution/cli/parser/options_builder.rb +21 -1
  13. data/lib/evilution/cli/printers/compare.rb +159 -0
  14. data/lib/evilution/cli.rb +1 -0
  15. data/lib/evilution/compare/categorizer.rb +109 -0
  16. data/lib/evilution/compare/detector.rb +21 -0
  17. data/lib/evilution/compare/fingerprint.rb +83 -0
  18. data/lib/evilution/compare/invalid_input.rb +12 -0
  19. data/lib/evilution/compare/normalizer.rb +106 -0
  20. data/lib/evilution/compare/record.rb +16 -0
  21. data/lib/evilution/compare.rb +6 -0
  22. data/lib/evilution/config.rb +165 -3
  23. data/lib/evilution/example_filter.rb +143 -0
  24. data/lib/evilution/integration/base.rb +4 -155
  25. data/lib/evilution/integration/crash_detector.rb +5 -2
  26. data/lib/evilution/integration/loading/concern_state_cleaner.rb +49 -0
  27. data/lib/evilution/integration/loading/constant_pinner.rb +24 -0
  28. data/lib/evilution/integration/loading/mutation_applier.rb +52 -0
  29. data/lib/evilution/integration/loading/redefinition_recovery.rb +54 -0
  30. data/lib/evilution/integration/loading/source_evaluator.rb +15 -0
  31. data/lib/evilution/integration/loading/syntax_validator.rb +19 -0
  32. data/lib/evilution/integration/loading.rb +6 -0
  33. data/lib/evilution/integration/minitest.rb +10 -5
  34. data/lib/evilution/integration/minitest_crash_detector.rb +5 -2
  35. data/lib/evilution/integration/rspec.rb +82 -7
  36. data/lib/evilution/isolation/fork.rb +25 -0
  37. data/lib/evilution/load_path/subpath_resolver.rb +25 -0
  38. data/lib/evilution/load_path.rb +4 -0
  39. data/lib/evilution/mcp/info_tool.rb +77 -5
  40. data/lib/evilution/mcp/mutate_tool/config_builder.rb +20 -0
  41. data/lib/evilution/mcp/mutate_tool/error_payload.rb +17 -0
  42. data/lib/evilution/mcp/mutate_tool/option_parser.rb +54 -0
  43. data/lib/evilution/mcp/mutate_tool/progress_streamer.rb +37 -0
  44. data/lib/evilution/mcp/mutate_tool/report_trimmer.rb +31 -0
  45. data/lib/evilution/mcp/mutate_tool/survived_enricher.rb +52 -0
  46. data/lib/evilution/mcp/mutate_tool.rb +34 -186
  47. data/lib/evilution/mutation.rb +43 -3
  48. data/lib/evilution/mutator/base.rb +39 -1
  49. data/lib/evilution/mutator/operator/argument_nil_substitution.rb +5 -1
  50. data/lib/evilution/mutator/operator/argument_removal.rb +5 -1
  51. data/lib/evilution/parallel/work_queue.rb +149 -31
  52. data/lib/evilution/parallel_db_warning.rb +68 -0
  53. data/lib/evilution/reporter/cli.rb +37 -11
  54. data/lib/evilution/reporter/html/assets/style.css +17 -0
  55. data/lib/evilution/reporter/html/sections/file_section.rb +15 -0
  56. data/lib/evilution/reporter/html/sections/neutral_details.rb +25 -0
  57. data/lib/evilution/reporter/html/sections/unparseable_details.rb +25 -0
  58. data/lib/evilution/reporter/html/sections/unresolved_details.rb +25 -0
  59. data/lib/evilution/reporter/html/templates/file_section.html.erb +3 -0
  60. data/lib/evilution/reporter/html/templates/neutral_details.html.erb +14 -0
  61. data/lib/evilution/reporter/html/templates/summary_cards.html.erb +3 -0
  62. data/lib/evilution/reporter/html/templates/unparseable_details.html.erb +11 -0
  63. data/lib/evilution/reporter/html/templates/unresolved_details.html.erb +11 -0
  64. data/lib/evilution/reporter/json.rb +8 -2
  65. data/lib/evilution/reporter/suggestion/diff_helpers.rb +28 -0
  66. data/lib/evilution/reporter/suggestion/registry.rb +64 -0
  67. data/lib/evilution/reporter/suggestion/templates/generic.rb +55 -0
  68. data/lib/evilution/reporter/suggestion/templates/minitest.rb +659 -0
  69. data/lib/evilution/reporter/suggestion/templates/rspec.rb +613 -0
  70. data/lib/evilution/reporter/suggestion.rb +8 -1327
  71. data/lib/evilution/result/mutation_result.rb +5 -1
  72. data/lib/evilution/result/summary.rb +13 -1
  73. data/lib/evilution/runner/baseline_runner.rb +23 -2
  74. data/lib/evilution/runner/isolation_resolver.rb +12 -1
  75. data/lib/evilution/runner/mutation_executor.rb +83 -13
  76. data/lib/evilution/runner/subject_pipeline.rb +18 -8
  77. data/lib/evilution/runner.rb +6 -0
  78. data/lib/evilution/source_ast_cache.rb +39 -0
  79. data/lib/evilution/spec_ast_cache.rb +166 -0
  80. data/lib/evilution/spec_resolver.rb +6 -1
  81. data/lib/evilution/spec_selector.rb +39 -0
  82. data/lib/evilution/temp_dir_tracker.rb +23 -3
  83. data/lib/evilution/version.rb +1 -1
  84. data/script/memory_check +7 -5
  85. metadata +46 -5
  86. data/lib/evilution/mcp/session_diff_tool.rb +0 -63
  87. data/lib/evilution/mcp/session_list_tool.rb +0 -50
  88. data/lib/evilution/mcp/session_show_tool.rb +0 -57
@@ -0,0 +1,613 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../registry"
4
+ require_relative "../diff_helpers"
5
+
6
+ module Evilution::Reporter::Suggestion::Templates::Rspec
7
+ H = Evilution::Reporter::Suggestion::DiffHelpers
8
+
9
+ RSPEC_ENTRIES = {
10
+ "comparison_replacement" => lambda { |mutation|
11
+ method_name = H.parse_method_name(mutation.subject.name)
12
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
13
+ <<~RSPEC.strip
14
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
15
+ # #{mutation.file_path}:#{mutation.line}
16
+ it 'returns the correct result at the comparison boundary in ##{method_name}' do
17
+ # Test with values where the original operator and mutated operator
18
+ # produce different results (e.g., equal values for > vs >=)
19
+ result = subject.#{method_name}(boundary_value)
20
+ expect(result).to eq(expected)
21
+ end
22
+ RSPEC
23
+ },
24
+ "arithmetic_replacement" => lambda { |mutation|
25
+ method_name = H.parse_method_name(mutation.subject.name)
26
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
27
+ <<~RSPEC.strip
28
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
29
+ # #{mutation.file_path}:#{mutation.line}
30
+ it 'computes the correct arithmetic result in ##{method_name}' do
31
+ # Assert the exact numeric result, not just truthiness or sign
32
+ result = subject.#{method_name}(input_value)
33
+ expect(result).to eq(expected)
34
+ end
35
+ RSPEC
36
+ },
37
+ "boolean_operator_replacement" => lambda { |mutation|
38
+ method_name = H.parse_method_name(mutation.subject.name)
39
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
40
+ <<~RSPEC.strip
41
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
42
+ # #{mutation.file_path}:#{mutation.line}
43
+ it 'returns the correct result when one condition is true and one is false in ##{method_name}' do
44
+ # Use inputs where only one operand is truthy to distinguish && from ||
45
+ result = subject.#{method_name}(input_value)
46
+ expect(result).to eq(expected)
47
+ end
48
+ RSPEC
49
+ },
50
+ "boolean_literal_replacement" => lambda { |mutation|
51
+ method_name = H.parse_method_name(mutation.subject.name)
52
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
53
+ <<~RSPEC.strip
54
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
55
+ # #{mutation.file_path}:#{mutation.line}
56
+ it 'returns the expected boolean value from ##{method_name}' do
57
+ # Assert the exact true/false/nil value, not just truthiness
58
+ result = subject.#{method_name}(input_value)
59
+ expect(result).to eq(expected)
60
+ end
61
+ RSPEC
62
+ },
63
+ "negation_insertion" => lambda { |mutation|
64
+ method_name = H.parse_method_name(mutation.subject.name)
65
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
66
+ <<~RSPEC.strip
67
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
68
+ # #{mutation.file_path}:#{mutation.line}
69
+ it 'returns the correct boolean from the predicate in ##{method_name}' do
70
+ # Assert the exact true/false result, not just truthiness
71
+ result = subject.#{method_name}(input_value)
72
+ expect(result).to eq(true).or eq(false)
73
+ end
74
+ RSPEC
75
+ },
76
+ "integer_literal" => lambda { |mutation|
77
+ method_name = H.parse_method_name(mutation.subject.name)
78
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
79
+ <<~RSPEC.strip
80
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
81
+ # #{mutation.file_path}:#{mutation.line}
82
+ it 'returns the exact integer value from ##{method_name}' do
83
+ # Assert the exact numeric value, not just > 0 or truthy
84
+ result = subject.#{method_name}(input_value)
85
+ expect(result).to eq(expected)
86
+ end
87
+ RSPEC
88
+ },
89
+ "float_literal" => lambda { |mutation|
90
+ method_name = H.parse_method_name(mutation.subject.name)
91
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
92
+ <<~RSPEC.strip
93
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
94
+ # #{mutation.file_path}:#{mutation.line}
95
+ it 'returns the exact float value from ##{method_name}' do
96
+ # Assert the exact floating-point result
97
+ result = subject.#{method_name}(input_value)
98
+ expect(result).to eq(expected)
99
+ end
100
+ RSPEC
101
+ },
102
+ "string_literal" => lambda { |mutation|
103
+ method_name = H.parse_method_name(mutation.subject.name)
104
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
105
+ <<~RSPEC.strip
106
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
107
+ # #{mutation.file_path}:#{mutation.line}
108
+ it 'returns the exact string content from ##{method_name}' do
109
+ # Assert the exact string value, not just presence or non-empty
110
+ result = subject.#{method_name}(input_value)
111
+ expect(result).to eq(expected)
112
+ end
113
+ RSPEC
114
+ },
115
+ "symbol_literal" => lambda { |mutation|
116
+ method_name = H.parse_method_name(mutation.subject.name)
117
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
118
+ <<~RSPEC.strip
119
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
120
+ # #{mutation.file_path}:#{mutation.line}
121
+ it 'returns the exact symbol from ##{method_name}' do
122
+ # Assert the exact symbol value, not just that it is a Symbol
123
+ result = subject.#{method_name}(input_value)
124
+ expect(result).to eq(expected)
125
+ end
126
+ RSPEC
127
+ },
128
+ "array_literal" => lambda { |mutation|
129
+ method_name = H.parse_method_name(mutation.subject.name)
130
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
131
+ <<~RSPEC.strip
132
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
133
+ # #{mutation.file_path}:#{mutation.line}
134
+ it 'returns the expected array contents from ##{method_name}' do
135
+ # Assert the exact array elements, not just non-empty or truthy
136
+ result = subject.#{method_name}(input_value)
137
+ expect(result).to eq(expected)
138
+ end
139
+ RSPEC
140
+ },
141
+ "hash_literal" => lambda { |mutation|
142
+ method_name = H.parse_method_name(mutation.subject.name)
143
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
144
+ <<~RSPEC.strip
145
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
146
+ # #{mutation.file_path}:#{mutation.line}
147
+ it 'returns the expected hash contents from ##{method_name}' do
148
+ # Assert the exact keys and values, not just non-empty or truthy
149
+ result = subject.#{method_name}(input_value)
150
+ expect(result).to eq(expected)
151
+ end
152
+ RSPEC
153
+ },
154
+ "collection_replacement" => lambda { |mutation|
155
+ method_name = H.parse_method_name(mutation.subject.name)
156
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
157
+ <<~RSPEC.strip
158
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
159
+ # #{mutation.file_path}:#{mutation.line}
160
+ it 'uses the return value of the collection operation in ##{method_name}' do
161
+ # Assert the return value of the collection method, not just side effects
162
+ result = subject.#{method_name}(input_value)
163
+ expect(result).to eq(expected)
164
+ end
165
+ RSPEC
166
+ },
167
+ "conditional_negation" => lambda { |mutation|
168
+ method_name = H.parse_method_name(mutation.subject.name)
169
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
170
+ <<~RSPEC.strip
171
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
172
+ # #{mutation.file_path}:#{mutation.line}
173
+ it 'exercises both branches of the conditional in ##{method_name}' do
174
+ # Test with inputs that make the condition true AND false
175
+ result = subject.#{method_name}(input_value)
176
+ expect(result).to eq(expected)
177
+ end
178
+ RSPEC
179
+ },
180
+ "conditional_branch" => lambda { |mutation|
181
+ method_name = H.parse_method_name(mutation.subject.name)
182
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
183
+ <<~RSPEC.strip
184
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
185
+ # #{mutation.file_path}:#{mutation.line}
186
+ it 'exercises the removed branch of the conditional in ##{method_name}' do
187
+ # Test with inputs that trigger the branch removed by this mutation
188
+ result = subject.#{method_name}(input_value)
189
+ expect(result).to eq(expected)
190
+ end
191
+ RSPEC
192
+ },
193
+ "statement_deletion" => lambda { |mutation|
194
+ method_name = H.parse_method_name(mutation.subject.name)
195
+ original_line, _mutated_line = H.extract_diff_lines(mutation.diff)
196
+ <<~RSPEC.strip
197
+ # Mutation: deleted `#{original_line}` in #{mutation.subject.name}
198
+ # #{mutation.file_path}:#{mutation.line}
199
+ it 'depends on the side effect of the deleted statement in ##{method_name}' do
200
+ # Assert a side effect or return value that changes when this statement is removed
201
+ subject.#{method_name}(input_value)
202
+ expect(observable_side_effect).to eq(expected)
203
+ end
204
+ RSPEC
205
+ },
206
+ "method_body_replacement" => lambda { |mutation|
207
+ method_name = H.parse_method_name(mutation.subject.name)
208
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
209
+ <<~RSPEC.strip
210
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
211
+ # #{mutation.file_path}:#{mutation.line}
212
+ it 'verifies the return value or side effects of ##{method_name}' do
213
+ # Assert the method produces a meaningful result, not just nil
214
+ result = subject.#{method_name}(input_value)
215
+ expect(result).to eq(expected)
216
+ end
217
+ RSPEC
218
+ },
219
+ "return_value_removal" => lambda { |mutation|
220
+ method_name = H.parse_method_name(mutation.subject.name)
221
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
222
+ <<~RSPEC.strip
223
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
224
+ # #{mutation.file_path}:#{mutation.line}
225
+ it 'uses the return value of ##{method_name}' do
226
+ # Assert the caller depends on the return value, not just side effects
227
+ result = subject.#{method_name}(input_value)
228
+ expect(result).to eq(expected)
229
+ end
230
+ RSPEC
231
+ },
232
+ "method_call_removal" => lambda { |mutation|
233
+ method_name = H.parse_method_name(mutation.subject.name)
234
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
235
+ <<~RSPEC.strip
236
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
237
+ # #{mutation.file_path}:#{mutation.line}
238
+ it 'depends on the return value or side effect of the call in ##{method_name}' do
239
+ # Assert the method call's effect is observable
240
+ result = subject.#{method_name}(input_value)
241
+ expect(result).to eq(expected)
242
+ end
243
+ RSPEC
244
+ },
245
+ "compound_assignment" => lambda { |mutation|
246
+ method_name = H.parse_method_name(mutation.subject.name)
247
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
248
+ <<~RSPEC.strip
249
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
250
+ # #{mutation.file_path}:#{mutation.line}
251
+ it 'verifies the compound assignment side effect in ##{method_name}' do
252
+ # Assert the accumulated value after the compound assignment
253
+ # The mutation changes the operator, so the final value will differ
254
+ subject.#{method_name}(input_value)
255
+ expect(observable_side_effect).to eq(expected)
256
+ end
257
+ RSPEC
258
+ },
259
+ "nil_replacement" => lambda { |mutation|
260
+ method_name = H.parse_method_name(mutation.subject.name)
261
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
262
+ <<~RSPEC.strip
263
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
264
+ # #{mutation.file_path}:#{mutation.line}
265
+ it 'asserts the nil return value from ##{method_name}' do
266
+ # Assert the method returns nil, not a substituted value
267
+ result = subject.#{method_name}(input_value)
268
+ expect(result).to be_nil
269
+ end
270
+ RSPEC
271
+ },
272
+ "superclass_removal" => lambda { |mutation|
273
+ method_name = H.parse_method_name(mutation.subject.name)
274
+ original_line, _mutated_line = H.extract_diff_lines(mutation.diff)
275
+ <<~RSPEC.strip
276
+ # Mutation: removed superclass from `#{original_line}` in #{mutation.subject.name}
277
+ # #{mutation.file_path}:#{mutation.line}
278
+ it 'depends on inherited behavior in ##{method_name}' do
279
+ # Assert behavior that comes from the superclass
280
+ result = subject.#{method_name}(input_value)
281
+ expect(result).to eq(expected)
282
+ end
283
+ RSPEC
284
+ },
285
+ "local_variable_assignment" => lambda { |mutation|
286
+ method_name = H.parse_method_name(mutation.subject.name)
287
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
288
+ <<~RSPEC.strip
289
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
290
+ # #{mutation.file_path}:#{mutation.line}
291
+ it 'verifies the local variable assignment is used in ##{method_name}' do
292
+ # Assert that the assigned variable is read later, not just the value expression
293
+ result = subject.#{method_name}(input_value)
294
+ expect(result).to eq(expected)
295
+ end
296
+ RSPEC
297
+ },
298
+ "instance_variable_write" => lambda { |mutation|
299
+ method_name = H.parse_method_name(mutation.subject.name)
300
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
301
+ <<~RSPEC.strip
302
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
303
+ # #{mutation.file_path}:#{mutation.line}
304
+ it 'verifies the instance variable @state is set correctly in ##{method_name}' do
305
+ # Assert that the instance variable holds the expected value after the method runs
306
+ subject.#{method_name}(input_value)
307
+ expect(subject.instance_variable_get(:@variable)).to eq(expected)
308
+ end
309
+ RSPEC
310
+ },
311
+ "class_variable_write" => lambda { |mutation|
312
+ method_name = H.parse_method_name(mutation.subject.name)
313
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
314
+ <<~RSPEC.strip
315
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
316
+ # #{mutation.file_path}:#{mutation.line}
317
+ it 'verifies the class variable @@shared state is set correctly in ##{method_name}' do
318
+ # Assert that the class variable holds the expected value and affects shared state
319
+ subject.#{method_name}(input_value)
320
+ expect(described_class.class_variable_get(:@@variable)).to eq(expected)
321
+ end
322
+ RSPEC
323
+ },
324
+ "global_variable_write" => lambda { |mutation|
325
+ method_name = H.parse_method_name(mutation.subject.name)
326
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
327
+ <<~RSPEC.strip
328
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
329
+ # #{mutation.file_path}:#{mutation.line}
330
+ it 'verifies the global variable $state is set correctly in ##{method_name}' do
331
+ # Assert that the global variable holds the expected value after the method runs
332
+ subject.#{method_name}(input_value)
333
+ expect($variable).to eq(expected)
334
+ end
335
+ RSPEC
336
+ },
337
+ "mixin_removal" => lambda { |mutation|
338
+ method_name = H.parse_method_name(mutation.subject.name)
339
+ original_line, _mutated_line = H.extract_diff_lines(mutation.diff)
340
+ <<~RSPEC.strip
341
+ # Mutation: removed `#{original_line}` in #{mutation.subject.name}
342
+ # #{mutation.file_path}:#{mutation.line}
343
+ it 'depends on behavior from the included module in ##{method_name}' do
344
+ # Assert behavior provided by the mixin
345
+ result = subject.#{method_name}(input_value)
346
+ expect(result).to eq(expected)
347
+ end
348
+ RSPEC
349
+ },
350
+ "rescue_removal" => lambda { |mutation|
351
+ method_name = H.parse_method_name(mutation.subject.name)
352
+ original_line, _mutated_line = H.extract_diff_lines(mutation.diff)
353
+ <<~RSPEC.strip
354
+ # Mutation: removed `#{original_line}` in #{mutation.subject.name}
355
+ # #{mutation.file_path}:#{mutation.line}
356
+ it 'verifies the rescue handler is needed in ##{method_name}' do
357
+ # Trigger the rescued exception and assert the handler's effect
358
+ result = subject.#{method_name}(input_that_raises)
359
+ expect(result).to eq(expected)
360
+ end
361
+ RSPEC
362
+ },
363
+ "rescue_body_replacement" => lambda { |mutation|
364
+ method_name = H.parse_method_name(mutation.subject.name)
365
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
366
+ <<~RSPEC.strip
367
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
368
+ # #{mutation.file_path}:#{mutation.line}
369
+ it 'verifies the rescue handler produces the correct result in ##{method_name}' do
370
+ # Trigger the exception and assert the rescue body's return value or side effect
371
+ result = subject.#{method_name}(input_that_raises)
372
+ expect(result).to eq(expected)
373
+ end
374
+ RSPEC
375
+ },
376
+ "inline_rescue" => lambda { |mutation|
377
+ method_name = H.parse_method_name(mutation.subject.name)
378
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
379
+ <<~RSPEC.strip
380
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
381
+ # #{mutation.file_path}:#{mutation.line}
382
+ it 'verifies the inline rescue fallback value in ##{method_name}' do
383
+ # Trigger the exception and assert the fallback value is correct
384
+ result = subject.#{method_name}(input_that_raises)
385
+ expect(result).to eq(expected)
386
+ end
387
+ RSPEC
388
+ },
389
+ "ensure_removal" => lambda { |mutation|
390
+ method_name = H.parse_method_name(mutation.subject.name)
391
+ original_line, _mutated_line = H.extract_diff_lines(mutation.diff)
392
+ <<~RSPEC.strip
393
+ # Mutation: removed ensure block `#{original_line}` in #{mutation.subject.name}
394
+ # #{mutation.file_path}:#{mutation.line}
395
+ it 'verifies the ensure cleanup runs in ##{method_name}' do
396
+ # Assert that the cleanup side effect is observable after the method runs
397
+ subject.#{method_name}(input_value)
398
+ expect(observable_cleanup_effect).to eq(expected)
399
+ end
400
+ RSPEC
401
+ },
402
+ "break_statement" => lambda { |mutation|
403
+ method_name = H.parse_method_name(mutation.subject.name)
404
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
405
+ <<~RSPEC.strip
406
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
407
+ # #{mutation.file_path}:#{mutation.line}
408
+ it 'verifies the break exits the loop correctly in ##{method_name}' do
409
+ # Assert the loop exits early and returns the expected value
410
+ result = subject.#{method_name}(input_value)
411
+ expect(result).to eq(expected)
412
+ end
413
+ RSPEC
414
+ },
415
+ "next_statement" => lambda { |mutation|
416
+ method_name = H.parse_method_name(mutation.subject.name)
417
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
418
+ <<~RSPEC.strip
419
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
420
+ # #{mutation.file_path}:#{mutation.line}
421
+ it 'verifies the next skips the iteration correctly in ##{method_name}' do
422
+ # Assert the iteration is skipped and the expected value is yielded
423
+ result = subject.#{method_name}(input_value)
424
+ expect(result).to eq(expected)
425
+ end
426
+ RSPEC
427
+ },
428
+ "redo_statement" => lambda { |mutation|
429
+ method_name = H.parse_method_name(mutation.subject.name)
430
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
431
+ <<~RSPEC.strip
432
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
433
+ # #{mutation.file_path}:#{mutation.line}
434
+ it 'verifies the redo retry logic is necessary in ##{method_name}' do
435
+ # Assert the iteration restart changes the outcome
436
+ result = subject.#{method_name}(input_value)
437
+ expect(result).to eq(expected)
438
+ end
439
+ RSPEC
440
+ },
441
+ "bitwise_replacement" => lambda { |mutation|
442
+ method_name = H.parse_method_name(mutation.subject.name)
443
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
444
+ <<~RSPEC.strip
445
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
446
+ # #{mutation.file_path}:#{mutation.line}
447
+ it 'verifies the exact bitwise result in ##{method_name}' do
448
+ # Assert the exact bit-level result to distinguish &, |, and ^ operators
449
+ result = subject.#{method_name}(input_value)
450
+ expect(result).to eq(expected)
451
+ end
452
+ RSPEC
453
+ },
454
+ "bitwise_complement" => lambda { |mutation|
455
+ method_name = H.parse_method_name(mutation.subject.name)
456
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
457
+ <<~RSPEC.strip
458
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
459
+ # #{mutation.file_path}:#{mutation.line}
460
+ it 'verifies the bitwise complement result in ##{method_name}' do
461
+ # Assert the exact complement (~) value, not just sign or magnitude
462
+ result = subject.#{method_name}(input_value)
463
+ expect(result).to eq(expected)
464
+ end
465
+ RSPEC
466
+ },
467
+ "bang_method" => lambda { |mutation|
468
+ method_name = H.parse_method_name(mutation.subject.name)
469
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
470
+ <<~RSPEC.strip
471
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
472
+ # #{mutation.file_path}:#{mutation.line}
473
+ it 'verifies in-place vs copy semantics matter in ##{method_name}' do
474
+ # Assert that the original object is or is not modified
475
+ result = subject.#{method_name}(input_value)
476
+ expect(result).to eq(expected)
477
+ end
478
+ RSPEC
479
+ },
480
+ "zsuper_removal" => lambda { |mutation|
481
+ method_name = H.parse_method_name(mutation.subject.name)
482
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
483
+ <<~RSPEC.strip
484
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
485
+ # #{mutation.file_path}:#{mutation.line}
486
+ it 'verifies inherited behavior from super is needed in ##{method_name}' do
487
+ # Assert that the result depends on the superclass implementation
488
+ result = subject.#{method_name}(input_value)
489
+ expect(result).to eq(expected)
490
+ end
491
+ RSPEC
492
+ },
493
+ "explicit_super_mutation" => lambda { |mutation|
494
+ method_name = H.parse_method_name(mutation.subject.name)
495
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
496
+ <<~RSPEC.strip
497
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
498
+ # #{mutation.file_path}:#{mutation.line}
499
+ it 'verifies the correct arguments are passed to super in ##{method_name}' do
500
+ # Assert the inherited method receives the expected arguments
501
+ result = subject.#{method_name}(input_value)
502
+ expect(result).to eq(expected)
503
+ end
504
+ RSPEC
505
+ },
506
+ "index_to_fetch" => lambda { |mutation|
507
+ method_name = H.parse_method_name(mutation.subject.name)
508
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
509
+ <<~RSPEC.strip
510
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
511
+ # #{mutation.file_path}:#{mutation.line}
512
+ it 'distinguishes [] from .fetch for missing keys in ##{method_name}' do
513
+ # Access a missing key: [] returns nil, .fetch raises KeyError
514
+ expect { subject.#{method_name}(collection_with_missing_key) }.to raise_error(KeyError)
515
+ end
516
+ RSPEC
517
+ },
518
+ "index_to_dig" => lambda { |mutation|
519
+ method_name = H.parse_method_name(mutation.subject.name)
520
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
521
+ <<~RSPEC.strip
522
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
523
+ # #{mutation.file_path}:#{mutation.line}
524
+ it 'verifies the chained [] access returns the correct nested value in ##{method_name}' do
525
+ # Assert the nested lookup produces the expected value
526
+ result = subject.#{method_name}(nested_collection)
527
+ expect(result).to eq(expected)
528
+ end
529
+ RSPEC
530
+ },
531
+ "index_assignment_removal" => lambda { |mutation|
532
+ method_name = H.parse_method_name(mutation.subject.name)
533
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
534
+ <<~RSPEC.strip
535
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
536
+ # #{mutation.file_path}:#{mutation.line}
537
+ it 'verifies the []= assignment modifies the collection in ##{method_name}' do
538
+ # Assert the collection contains the assigned value after the method runs
539
+ result = subject.#{method_name}(collection)
540
+ expect(result).to include(expected_key => expected_value)
541
+ end
542
+ RSPEC
543
+ },
544
+ "pattern_matching_guard" => lambda { |mutation|
545
+ method_name = H.parse_method_name(mutation.subject.name)
546
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
547
+ <<~RSPEC.strip
548
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
549
+ # #{mutation.file_path}:#{mutation.line}
550
+ it 'verifies the pattern guard filters correctly in ##{method_name}' do
551
+ # Test with input that matches the pattern but fails the guard condition
552
+ # The guard should prevent matching, routing to a different branch
553
+ result = subject.#{method_name}(input_matching_pattern_but_failing_guard)
554
+ expect(result).to eq(expected)
555
+ end
556
+ RSPEC
557
+ },
558
+ "pattern_matching_alternative" => lambda { |mutation|
559
+ method_name = H.parse_method_name(mutation.subject.name)
560
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
561
+ <<~RSPEC.strip
562
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
563
+ # #{mutation.file_path}:#{mutation.line}
564
+ it 'verifies each pattern alternative is reachable in ##{method_name}' do
565
+ # Test with input that matches only one specific alternative
566
+ # Each alternative should have a dedicated test case
567
+ result = subject.#{method_name}(input_for_specific_alternative)
568
+ expect(result).to eq(expected)
569
+ end
570
+ RSPEC
571
+ },
572
+ "collection_return" => lambda { |mutation|
573
+ method_name = H.parse_method_name(mutation.subject.name)
574
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
575
+ <<~RSPEC.strip
576
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
577
+ # #{mutation.file_path}:#{mutation.line}
578
+ it 'returns a non-empty collection from ##{method_name}' do
579
+ # Assert the collection has the expected elements, not just non-empty
580
+ result = subject.#{method_name}(input_value)
581
+ expect(result).to eq(expected)
582
+ end
583
+ RSPEC
584
+ },
585
+ "scalar_return" => lambda { |mutation|
586
+ method_name = H.parse_method_name(mutation.subject.name)
587
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
588
+ <<~RSPEC.strip
589
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
590
+ # #{mutation.file_path}:#{mutation.line}
591
+ it 'returns a non-zero/non-empty value from ##{method_name}' do
592
+ # Assert the exact scalar value, not just presence or type
593
+ result = subject.#{method_name}(input_value)
594
+ expect(result).to eq(expected)
595
+ end
596
+ RSPEC
597
+ },
598
+ "pattern_matching_array" => lambda { |mutation|
599
+ method_name = H.parse_method_name(mutation.subject.name)
600
+ original_line, mutated_line = H.extract_diff_lines(mutation.diff)
601
+ <<~RSPEC.strip
602
+ # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
603
+ # #{mutation.file_path}:#{mutation.line}
604
+ it 'verifies each array pattern element matters in ##{method_name}' do
605
+ # Test with input where changing one element type causes a different match
606
+ # Each position in the array pattern should be validated
607
+ result = subject.#{method_name}(input_with_wrong_element_type)
608
+ expect(result).to eq(expected)
609
+ end
610
+ RSPEC
611
+ }
612
+ }.freeze
613
+ end