evilution 0.23.0 → 0.25.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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/.beads/interactions.jsonl +210 -0
  3. data/CHANGELOG.md +51 -0
  4. data/README.md +81 -4
  5. data/exe/evil +6 -0
  6. data/lib/evilution/ast/source_surgeon.rb +15 -1
  7. data/lib/evilution/cli/commands/compare.rb +68 -0
  8. data/lib/evilution/cli/parser/command_extractor.rb +78 -0
  9. data/lib/evilution/cli/parser/file_args.rb +41 -0
  10. data/lib/evilution/cli/parser/options_builder.rb +123 -0
  11. data/lib/evilution/cli/parser/stdin_reader.rb +28 -0
  12. data/lib/evilution/cli/parser.rb +27 -196
  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/normalizer.rb +106 -0
  19. data/lib/evilution/compare/record.rb +16 -0
  20. data/lib/evilution/compare.rb +15 -0
  21. data/lib/evilution/config.rb +178 -3
  22. data/lib/evilution/example_filter.rb +143 -0
  23. data/lib/evilution/integration/base.rb +11 -57
  24. data/lib/evilution/integration/crash_detector.rb +5 -2
  25. data/lib/evilution/integration/minitest.rb +25 -7
  26. data/lib/evilution/integration/minitest_crash_detector.rb +5 -2
  27. data/lib/evilution/integration/rspec.rb +99 -12
  28. data/lib/evilution/isolation/fork.rb +26 -0
  29. data/lib/evilution/isolation/in_process.rb +1 -0
  30. data/lib/evilution/mcp/info_tool.rb +77 -5
  31. data/lib/evilution/mcp/mutate_tool/config_builder.rb +20 -0
  32. data/lib/evilution/mcp/mutate_tool/error_payload.rb +17 -0
  33. data/lib/evilution/mcp/mutate_tool/option_parser.rb +54 -0
  34. data/lib/evilution/mcp/mutate_tool/progress_streamer.rb +37 -0
  35. data/lib/evilution/mcp/mutate_tool/report_trimmer.rb +31 -0
  36. data/lib/evilution/mcp/mutate_tool/survived_enricher.rb +52 -0
  37. data/lib/evilution/mcp/mutate_tool.rb +34 -186
  38. data/lib/evilution/mutation.rb +43 -3
  39. data/lib/evilution/mutator/base.rb +39 -1
  40. data/lib/evilution/mutator/operator/argument_nil_substitution.rb +5 -1
  41. data/lib/evilution/mutator/operator/argument_removal.rb +5 -1
  42. data/lib/evilution/parallel/work_queue.rb +149 -31
  43. data/lib/evilution/parallel_db_warning.rb +68 -0
  44. data/lib/evilution/reporter/cli.rb +38 -11
  45. data/lib/evilution/reporter/html/assets/style.css +85 -0
  46. data/lib/evilution/reporter/html/baseline_keys.rb +28 -0
  47. data/lib/evilution/reporter/html/diff_formatter.rb +27 -0
  48. data/lib/evilution/reporter/html/escape.rb +12 -0
  49. data/lib/evilution/reporter/html/namespace.rb +11 -0
  50. data/lib/evilution/reporter/html/report.rb +68 -0
  51. data/lib/evilution/reporter/html/section.rb +21 -0
  52. data/lib/evilution/reporter/html/sections/baseline_comparison.rb +46 -0
  53. data/lib/evilution/reporter/html/sections/error_details.rb +30 -0
  54. data/lib/evilution/reporter/html/sections/error_entry.rb +22 -0
  55. data/lib/evilution/reporter/html/sections/file_section.rb +62 -0
  56. data/lib/evilution/reporter/html/sections/header.rb +29 -0
  57. data/lib/evilution/reporter/html/sections/mutation_map.rb +32 -0
  58. data/lib/evilution/reporter/html/sections/neutral_details.rb +25 -0
  59. data/lib/evilution/reporter/html/sections/summary_cards.rb +11 -0
  60. data/lib/evilution/reporter/html/sections/survived_details.rb +35 -0
  61. data/lib/evilution/reporter/html/sections/survived_entry.rb +36 -0
  62. data/lib/evilution/reporter/html/sections/truncation_notice.rb +17 -0
  63. data/lib/evilution/reporter/html/sections/unparseable_details.rb +25 -0
  64. data/lib/evilution/reporter/html/sections/unresolved_details.rb +25 -0
  65. data/lib/evilution/reporter/html/sections.rb +4 -0
  66. data/lib/evilution/reporter/html/stylesheet.rb +14 -0
  67. data/lib/evilution/reporter/html/templates/baseline_comparison.html.erb +8 -0
  68. data/lib/evilution/reporter/html/templates/error_details.html.erb +6 -0
  69. data/lib/evilution/reporter/html/templates/error_entry.html.erb +10 -0
  70. data/lib/evilution/reporter/html/templates/file_section.html.erb +12 -0
  71. data/lib/evilution/reporter/html/templates/header.html.erb +4 -0
  72. data/lib/evilution/reporter/html/templates/mutation_map.html.erb +6 -0
  73. data/lib/evilution/reporter/html/templates/neutral_details.html.erb +14 -0
  74. data/lib/evilution/reporter/html/templates/report.html.erb +17 -0
  75. data/lib/evilution/reporter/html/templates/summary_cards.html.erb +26 -0
  76. data/lib/evilution/reporter/html/templates/survived_details.html.erb +21 -0
  77. data/lib/evilution/reporter/html/templates/survived_entry.html.erb +8 -0
  78. data/lib/evilution/reporter/html/templates/truncation_notice.html.erb +1 -0
  79. data/lib/evilution/reporter/html/templates/unparseable_details.html.erb +11 -0
  80. data/lib/evilution/reporter/html/templates/unresolved_details.html.erb +11 -0
  81. data/lib/evilution/reporter/html.rb +11 -390
  82. data/lib/evilution/reporter/json.rb +19 -9
  83. data/lib/evilution/reporter/suggestion/diff_helpers.rb +28 -0
  84. data/lib/evilution/reporter/suggestion/registry.rb +64 -0
  85. data/lib/evilution/reporter/suggestion/templates/generic.rb +55 -0
  86. data/lib/evilution/reporter/suggestion/templates/minitest.rb +659 -0
  87. data/lib/evilution/reporter/suggestion/templates/rspec.rb +613 -0
  88. data/lib/evilution/reporter/suggestion.rb +8 -1327
  89. data/lib/evilution/result/mutation_result.rb +9 -1
  90. data/lib/evilution/result/summary.rb +21 -1
  91. data/lib/evilution/runner/baseline_runner.rb +92 -0
  92. data/lib/evilution/runner/diagnostics.rb +105 -0
  93. data/lib/evilution/runner/isolation_resolver.rb +134 -0
  94. data/lib/evilution/runner/mutation_executor.rb +325 -0
  95. data/lib/evilution/runner/mutation_planner.rb +126 -0
  96. data/lib/evilution/runner/report_publisher.rb +60 -0
  97. data/lib/evilution/runner/subject_pipeline.rb +121 -0
  98. data/lib/evilution/runner.rb +61 -692
  99. data/lib/evilution/source_ast_cache.rb +39 -0
  100. data/lib/evilution/spec_ast_cache.rb +166 -0
  101. data/lib/evilution/spec_resolver.rb +6 -1
  102. data/lib/evilution/spec_selector.rb +39 -0
  103. data/lib/evilution/temp_dir_tracker.rb +23 -3
  104. data/lib/evilution/version.rb +1 -1
  105. data/script/memory_check +7 -5
  106. metadata +75 -2
@@ -3,1317 +3,12 @@
3
3
  require_relative "../reporter"
4
4
 
5
5
  class Evilution::Reporter::Suggestion
6
- TEMPLATES = {
7
- "comparison_replacement" => "Add a test for the boundary condition where the comparison operand equals the threshold exactly",
8
- "arithmetic_replacement" => "Add a test that verifies the arithmetic result, not just truthiness of the outcome",
9
- "boolean_operator_replacement" => "Add a test where only one of the boolean conditions is true to distinguish && from ||",
10
- "boolean_literal_replacement" => "Add a test that exercises the false/true branch explicitly",
11
- "nil_replacement" => "Add a test that asserts the return value is not nil",
12
- "integer_literal" => "Add a test that checks the exact numeric value, not just > 0 or truthy",
13
- "float_literal" => "Add a test that checks the exact floating-point value returned",
14
- "string_literal" => "Add a test that asserts the string content, not just its presence",
15
- "array_literal" => "Add a test that verifies the array contents or length",
16
- "hash_literal" => "Add a test that verifies the hash keys and values",
17
- "symbol_literal" => "Add a test that checks the exact symbol returned",
18
- "conditional_negation" => "Add tests for both the true and false branches of this conditional",
19
- "conditional_branch" => "Add a test that exercises the removed branch of this conditional",
20
- "statement_deletion" => "Add a test that depends on the side effect of this statement",
21
- "method_body_replacement" => "Add a test that checks the method's return value or side effects",
22
- "negation_insertion" => "Add a test where the predicate result matters (not just truthiness)",
23
- "return_value_removal" => "Add a test that uses the return value of this method",
24
- "collection_replacement" => "Add a test that checks the return value of the collection operation, not just side effects",
25
- "method_call_removal" => "Add a test that depends on the return value or side effect of this method call",
26
- "argument_removal" => "Add a test that verifies the correct arguments are passed to this method call",
27
- "compound_assignment" => "Add a test that verifies the side effect of this compound assignment (the accumulated value matters)",
28
- "superclass_removal" => "Add a test that exercises inherited behavior from the superclass",
29
- "mixin_removal" => "Add a test that exercises behavior provided by the included/extended module",
30
- "local_variable_assignment" => "Add a test that depends on the assigned variable being stored, not just the value expression",
31
- "instance_variable_write" => "Add a test that verifies the instance variable is set correctly, not just the return value",
32
- "class_variable_write" => "Add a test that verifies the class variable is set correctly and affects shared state",
33
- "global_variable_write" => "Add a test that verifies the global variable is set correctly, not just the value expression",
34
- "rescue_removal" => "Add a test that triggers the rescued exception and verifies the rescue handler behavior",
35
- "rescue_body_replacement" => "Add a test that triggers the rescued exception and verifies the rescue body produces the correct result",
36
- "inline_rescue" => "Add a test that triggers the inline rescue and verifies the fallback value is used correctly",
37
- "ensure_removal" => "Add a test that verifies the ensure cleanup code runs and its side effects are observable",
38
- "break_statement" => "Add a test that verifies the break condition and the value returned when the loop exits early",
39
- "next_statement" => "Add a test that verifies the next condition and the value yielded when the iteration skips",
40
- "redo_statement" => "Add a test that verifies the redo restarts the iteration and the retry logic is necessary",
41
- "bang_method" => "Add a test that distinguishes in-place mutation from copy semantics (bang vs non-bang)",
42
- "bitwise_replacement" => "Add a test that checks the exact bitwise result to distinguish &, |, and ^ operators",
43
- "bitwise_complement" => "Add a test that verifies the bitwise complement (~) result, not just the sign or magnitude",
44
- "zsuper_removal" => "Add a test that verifies inherited behavior from super is needed, not just the subclass logic",
45
- "explicit_super_mutation" => "Add a test that verifies the correct arguments are passed to super and the inherited result matters",
46
- "index_to_fetch" => "Add a test that distinguishes [] (returns nil for missing keys) from .fetch (raises KeyError)",
47
- "index_to_dig" => "Add a test that verifies chained [] access returns the correct nested value",
48
- "index_assignment_removal" => "Add a test that verifies the []= assignment side effect is observable (the collection is modified)",
49
- "pattern_matching_guard" => "Add a test with input that matches the pattern but fails the guard to verify filtering",
50
- "pattern_matching_alternative" => "Add a test with input that matches only one specific alternative to verify each branch is reachable",
51
- "pattern_matching_array" => "Add a test that verifies each element position in the array pattern matches the expected type or value",
52
- "collection_return" => "Add a test that verifies the method returns a non-empty collection, not just any array or hash",
53
- "scalar_return" => "Add a test that verifies the method returns a non-zero/non-empty scalar value, not just any type"
54
- }.freeze
55
-
56
- CONCRETE_TEMPLATES = {
57
- "comparison_replacement" => lambda { |mutation|
58
- method_name = parse_method_name(mutation.subject.name)
59
- original_line, mutated_line = extract_diff_lines(mutation.diff)
60
- <<~RSPEC.strip
61
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
62
- # #{mutation.file_path}:#{mutation.line}
63
- it 'returns the correct result at the comparison boundary in ##{method_name}' do
64
- # Test with values where the original operator and mutated operator
65
- # produce different results (e.g., equal values for > vs >=)
66
- result = subject.#{method_name}(boundary_value)
67
- expect(result).to eq(expected)
68
- end
69
- RSPEC
70
- },
71
- "arithmetic_replacement" => lambda { |mutation|
72
- method_name = parse_method_name(mutation.subject.name)
73
- original_line, mutated_line = extract_diff_lines(mutation.diff)
74
- <<~RSPEC.strip
75
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
76
- # #{mutation.file_path}:#{mutation.line}
77
- it 'computes the correct arithmetic result in ##{method_name}' do
78
- # Assert the exact numeric result, not just truthiness or sign
79
- result = subject.#{method_name}(input_value)
80
- expect(result).to eq(expected)
81
- end
82
- RSPEC
83
- },
84
- "boolean_operator_replacement" => lambda { |mutation|
85
- method_name = parse_method_name(mutation.subject.name)
86
- original_line, mutated_line = extract_diff_lines(mutation.diff)
87
- <<~RSPEC.strip
88
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
89
- # #{mutation.file_path}:#{mutation.line}
90
- it 'returns the correct result when one condition is true and one is false in ##{method_name}' do
91
- # Use inputs where only one operand is truthy to distinguish && from ||
92
- result = subject.#{method_name}(input_value)
93
- expect(result).to eq(expected)
94
- end
95
- RSPEC
96
- },
97
- "boolean_literal_replacement" => lambda { |mutation|
98
- method_name = parse_method_name(mutation.subject.name)
99
- original_line, mutated_line = extract_diff_lines(mutation.diff)
100
- <<~RSPEC.strip
101
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
102
- # #{mutation.file_path}:#{mutation.line}
103
- it 'returns the expected boolean value from ##{method_name}' do
104
- # Assert the exact true/false/nil value, not just truthiness
105
- result = subject.#{method_name}(input_value)
106
- expect(result).to eq(expected)
107
- end
108
- RSPEC
109
- },
110
- "negation_insertion" => lambda { |mutation|
111
- method_name = parse_method_name(mutation.subject.name)
112
- original_line, mutated_line = extract_diff_lines(mutation.diff)
113
- <<~RSPEC.strip
114
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
115
- # #{mutation.file_path}:#{mutation.line}
116
- it 'returns the correct boolean from the predicate in ##{method_name}' do
117
- # Assert the exact true/false result, not just truthiness
118
- result = subject.#{method_name}(input_value)
119
- expect(result).to eq(true).or eq(false)
120
- end
121
- RSPEC
122
- },
123
- "integer_literal" => lambda { |mutation|
124
- method_name = parse_method_name(mutation.subject.name)
125
- original_line, mutated_line = extract_diff_lines(mutation.diff)
126
- <<~RSPEC.strip
127
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
128
- # #{mutation.file_path}:#{mutation.line}
129
- it 'returns the exact integer value from ##{method_name}' do
130
- # Assert the exact numeric value, not just > 0 or truthy
131
- result = subject.#{method_name}(input_value)
132
- expect(result).to eq(expected)
133
- end
134
- RSPEC
135
- },
136
- "float_literal" => lambda { |mutation|
137
- method_name = parse_method_name(mutation.subject.name)
138
- original_line, mutated_line = extract_diff_lines(mutation.diff)
139
- <<~RSPEC.strip
140
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
141
- # #{mutation.file_path}:#{mutation.line}
142
- it 'returns the exact float value from ##{method_name}' do
143
- # Assert the exact floating-point result
144
- result = subject.#{method_name}(input_value)
145
- expect(result).to eq(expected)
146
- end
147
- RSPEC
148
- },
149
- "string_literal" => lambda { |mutation|
150
- method_name = parse_method_name(mutation.subject.name)
151
- original_line, mutated_line = extract_diff_lines(mutation.diff)
152
- <<~RSPEC.strip
153
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
154
- # #{mutation.file_path}:#{mutation.line}
155
- it 'returns the exact string content from ##{method_name}' do
156
- # Assert the exact string value, not just presence or non-empty
157
- result = subject.#{method_name}(input_value)
158
- expect(result).to eq(expected)
159
- end
160
- RSPEC
161
- },
162
- "symbol_literal" => lambda { |mutation|
163
- method_name = parse_method_name(mutation.subject.name)
164
- original_line, mutated_line = extract_diff_lines(mutation.diff)
165
- <<~RSPEC.strip
166
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
167
- # #{mutation.file_path}:#{mutation.line}
168
- it 'returns the exact symbol from ##{method_name}' do
169
- # Assert the exact symbol value, not just that it is a Symbol
170
- result = subject.#{method_name}(input_value)
171
- expect(result).to eq(expected)
172
- end
173
- RSPEC
174
- },
175
- "array_literal" => lambda { |mutation|
176
- method_name = parse_method_name(mutation.subject.name)
177
- original_line, mutated_line = extract_diff_lines(mutation.diff)
178
- <<~RSPEC.strip
179
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
180
- # #{mutation.file_path}:#{mutation.line}
181
- it 'returns the expected array contents from ##{method_name}' do
182
- # Assert the exact array elements, not just non-empty or truthy
183
- result = subject.#{method_name}(input_value)
184
- expect(result).to eq(expected)
185
- end
186
- RSPEC
187
- },
188
- "hash_literal" => lambda { |mutation|
189
- method_name = parse_method_name(mutation.subject.name)
190
- original_line, mutated_line = extract_diff_lines(mutation.diff)
191
- <<~RSPEC.strip
192
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
193
- # #{mutation.file_path}:#{mutation.line}
194
- it 'returns the expected hash contents from ##{method_name}' do
195
- # Assert the exact keys and values, not just non-empty or truthy
196
- result = subject.#{method_name}(input_value)
197
- expect(result).to eq(expected)
198
- end
199
- RSPEC
200
- },
201
- "collection_replacement" => lambda { |mutation|
202
- method_name = parse_method_name(mutation.subject.name)
203
- original_line, mutated_line = extract_diff_lines(mutation.diff)
204
- <<~RSPEC.strip
205
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
206
- # #{mutation.file_path}:#{mutation.line}
207
- it 'uses the return value of the collection operation in ##{method_name}' do
208
- # Assert the return value of the collection method, not just side effects
209
- result = subject.#{method_name}(input_value)
210
- expect(result).to eq(expected)
211
- end
212
- RSPEC
213
- },
214
- "conditional_negation" => lambda { |mutation|
215
- method_name = parse_method_name(mutation.subject.name)
216
- original_line, mutated_line = extract_diff_lines(mutation.diff)
217
- <<~RSPEC.strip
218
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
219
- # #{mutation.file_path}:#{mutation.line}
220
- it 'exercises both branches of the conditional in ##{method_name}' do
221
- # Test with inputs that make the condition true AND false
222
- result = subject.#{method_name}(input_value)
223
- expect(result).to eq(expected)
224
- end
225
- RSPEC
226
- },
227
- "conditional_branch" => lambda { |mutation|
228
- method_name = parse_method_name(mutation.subject.name)
229
- original_line, mutated_line = extract_diff_lines(mutation.diff)
230
- <<~RSPEC.strip
231
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
232
- # #{mutation.file_path}:#{mutation.line}
233
- it 'exercises the removed branch of the conditional in ##{method_name}' do
234
- # Test with inputs that trigger the branch removed by this mutation
235
- result = subject.#{method_name}(input_value)
236
- expect(result).to eq(expected)
237
- end
238
- RSPEC
239
- },
240
- "statement_deletion" => lambda { |mutation|
241
- method_name = parse_method_name(mutation.subject.name)
242
- original_line, _mutated_line = extract_diff_lines(mutation.diff)
243
- <<~RSPEC.strip
244
- # Mutation: deleted `#{original_line}` in #{mutation.subject.name}
245
- # #{mutation.file_path}:#{mutation.line}
246
- it 'depends on the side effect of the deleted statement in ##{method_name}' do
247
- # Assert a side effect or return value that changes when this statement is removed
248
- subject.#{method_name}(input_value)
249
- expect(observable_side_effect).to eq(expected)
250
- end
251
- RSPEC
252
- },
253
- "method_body_replacement" => lambda { |mutation|
254
- method_name = parse_method_name(mutation.subject.name)
255
- original_line, mutated_line = extract_diff_lines(mutation.diff)
256
- <<~RSPEC.strip
257
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
258
- # #{mutation.file_path}:#{mutation.line}
259
- it 'verifies the return value or side effects of ##{method_name}' do
260
- # Assert the method produces a meaningful result, not just nil
261
- result = subject.#{method_name}(input_value)
262
- expect(result).to eq(expected)
263
- end
264
- RSPEC
265
- },
266
- "return_value_removal" => lambda { |mutation|
267
- method_name = parse_method_name(mutation.subject.name)
268
- original_line, mutated_line = extract_diff_lines(mutation.diff)
269
- <<~RSPEC.strip
270
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
271
- # #{mutation.file_path}:#{mutation.line}
272
- it 'uses the return value of ##{method_name}' do
273
- # Assert the caller depends on the return value, not just side effects
274
- result = subject.#{method_name}(input_value)
275
- expect(result).to eq(expected)
276
- end
277
- RSPEC
278
- },
279
- "method_call_removal" => lambda { |mutation|
280
- method_name = parse_method_name(mutation.subject.name)
281
- original_line, mutated_line = extract_diff_lines(mutation.diff)
282
- <<~RSPEC.strip
283
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
284
- # #{mutation.file_path}:#{mutation.line}
285
- it 'depends on the return value or side effect of the call in ##{method_name}' do
286
- # Assert the method call's effect is observable
287
- result = subject.#{method_name}(input_value)
288
- expect(result).to eq(expected)
289
- end
290
- RSPEC
291
- },
292
- "compound_assignment" => lambda { |mutation|
293
- method_name = parse_method_name(mutation.subject.name)
294
- original_line, mutated_line = extract_diff_lines(mutation.diff)
295
- <<~RSPEC.strip
296
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
297
- # #{mutation.file_path}:#{mutation.line}
298
- it 'verifies the compound assignment side effect in ##{method_name}' do
299
- # Assert the accumulated value after the compound assignment
300
- # The mutation changes the operator, so the final value will differ
301
- subject.#{method_name}(input_value)
302
- expect(observable_side_effect).to eq(expected)
303
- end
304
- RSPEC
305
- },
306
- "nil_replacement" => lambda { |mutation|
307
- method_name = parse_method_name(mutation.subject.name)
308
- original_line, mutated_line = extract_diff_lines(mutation.diff)
309
- <<~RSPEC.strip
310
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
311
- # #{mutation.file_path}:#{mutation.line}
312
- it 'asserts the nil return value from ##{method_name}' do
313
- # Assert the method returns nil, not a substituted value
314
- result = subject.#{method_name}(input_value)
315
- expect(result).to be_nil
316
- end
317
- RSPEC
318
- },
319
- "superclass_removal" => lambda { |mutation|
320
- method_name = parse_method_name(mutation.subject.name)
321
- original_line, _mutated_line = extract_diff_lines(mutation.diff)
322
- <<~RSPEC.strip
323
- # Mutation: removed superclass from `#{original_line}` in #{mutation.subject.name}
324
- # #{mutation.file_path}:#{mutation.line}
325
- it 'depends on inherited behavior in ##{method_name}' do
326
- # Assert behavior that comes from the superclass
327
- result = subject.#{method_name}(input_value)
328
- expect(result).to eq(expected)
329
- end
330
- RSPEC
331
- },
332
- "local_variable_assignment" => lambda { |mutation|
333
- method_name = parse_method_name(mutation.subject.name)
334
- original_line, mutated_line = extract_diff_lines(mutation.diff)
335
- <<~RSPEC.strip
336
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
337
- # #{mutation.file_path}:#{mutation.line}
338
- it 'verifies the local variable assignment is used in ##{method_name}' do
339
- # Assert that the assigned variable is read later, not just the value expression
340
- result = subject.#{method_name}(input_value)
341
- expect(result).to eq(expected)
342
- end
343
- RSPEC
344
- },
345
- "instance_variable_write" => lambda { |mutation|
346
- method_name = parse_method_name(mutation.subject.name)
347
- original_line, mutated_line = extract_diff_lines(mutation.diff)
348
- <<~RSPEC.strip
349
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
350
- # #{mutation.file_path}:#{mutation.line}
351
- it 'verifies the instance variable @state is set correctly in ##{method_name}' do
352
- # Assert that the instance variable holds the expected value after the method runs
353
- subject.#{method_name}(input_value)
354
- expect(subject.instance_variable_get(:@variable)).to eq(expected)
355
- end
356
- RSPEC
357
- },
358
- "class_variable_write" => lambda { |mutation|
359
- method_name = parse_method_name(mutation.subject.name)
360
- original_line, mutated_line = extract_diff_lines(mutation.diff)
361
- <<~RSPEC.strip
362
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
363
- # #{mutation.file_path}:#{mutation.line}
364
- it 'verifies the class variable @@shared state is set correctly in ##{method_name}' do
365
- # Assert that the class variable holds the expected value and affects shared state
366
- subject.#{method_name}(input_value)
367
- expect(described_class.class_variable_get(:@@variable)).to eq(expected)
368
- end
369
- RSPEC
370
- },
371
- "global_variable_write" => lambda { |mutation|
372
- method_name = parse_method_name(mutation.subject.name)
373
- original_line, mutated_line = extract_diff_lines(mutation.diff)
374
- <<~RSPEC.strip
375
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
376
- # #{mutation.file_path}:#{mutation.line}
377
- it 'verifies the global variable $state is set correctly in ##{method_name}' do
378
- # Assert that the global variable holds the expected value after the method runs
379
- subject.#{method_name}(input_value)
380
- expect($variable).to eq(expected)
381
- end
382
- RSPEC
383
- },
384
- "mixin_removal" => lambda { |mutation|
385
- method_name = parse_method_name(mutation.subject.name)
386
- original_line, _mutated_line = extract_diff_lines(mutation.diff)
387
- <<~RSPEC.strip
388
- # Mutation: removed `#{original_line}` in #{mutation.subject.name}
389
- # #{mutation.file_path}:#{mutation.line}
390
- it 'depends on behavior from the included module in ##{method_name}' do
391
- # Assert behavior provided by the mixin
392
- result = subject.#{method_name}(input_value)
393
- expect(result).to eq(expected)
394
- end
395
- RSPEC
396
- },
397
- "rescue_removal" => lambda { |mutation|
398
- method_name = parse_method_name(mutation.subject.name)
399
- original_line, _mutated_line = extract_diff_lines(mutation.diff)
400
- <<~RSPEC.strip
401
- # Mutation: removed `#{original_line}` in #{mutation.subject.name}
402
- # #{mutation.file_path}:#{mutation.line}
403
- it 'verifies the rescue handler is needed in ##{method_name}' do
404
- # Trigger the rescued exception and assert the handler's effect
405
- result = subject.#{method_name}(input_that_raises)
406
- expect(result).to eq(expected)
407
- end
408
- RSPEC
409
- },
410
- "rescue_body_replacement" => lambda { |mutation|
411
- method_name = parse_method_name(mutation.subject.name)
412
- original_line, mutated_line = extract_diff_lines(mutation.diff)
413
- <<~RSPEC.strip
414
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
415
- # #{mutation.file_path}:#{mutation.line}
416
- it 'verifies the rescue handler produces the correct result in ##{method_name}' do
417
- # Trigger the exception and assert the rescue body's return value or side effect
418
- result = subject.#{method_name}(input_that_raises)
419
- expect(result).to eq(expected)
420
- end
421
- RSPEC
422
- },
423
- "inline_rescue" => lambda { |mutation|
424
- method_name = parse_method_name(mutation.subject.name)
425
- original_line, mutated_line = extract_diff_lines(mutation.diff)
426
- <<~RSPEC.strip
427
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
428
- # #{mutation.file_path}:#{mutation.line}
429
- it 'verifies the inline rescue fallback value in ##{method_name}' do
430
- # Trigger the exception and assert the fallback value is correct
431
- result = subject.#{method_name}(input_that_raises)
432
- expect(result).to eq(expected)
433
- end
434
- RSPEC
435
- },
436
- "ensure_removal" => lambda { |mutation|
437
- method_name = parse_method_name(mutation.subject.name)
438
- original_line, _mutated_line = extract_diff_lines(mutation.diff)
439
- <<~RSPEC.strip
440
- # Mutation: removed ensure block `#{original_line}` in #{mutation.subject.name}
441
- # #{mutation.file_path}:#{mutation.line}
442
- it 'verifies the ensure cleanup runs in ##{method_name}' do
443
- # Assert that the cleanup side effect is observable after the method runs
444
- subject.#{method_name}(input_value)
445
- expect(observable_cleanup_effect).to eq(expected)
446
- end
447
- RSPEC
448
- },
449
- "break_statement" => lambda { |mutation|
450
- method_name = parse_method_name(mutation.subject.name)
451
- original_line, mutated_line = extract_diff_lines(mutation.diff)
452
- <<~RSPEC.strip
453
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
454
- # #{mutation.file_path}:#{mutation.line}
455
- it 'verifies the break exits the loop correctly in ##{method_name}' do
456
- # Assert the loop exits early and returns the expected value
457
- result = subject.#{method_name}(input_value)
458
- expect(result).to eq(expected)
459
- end
460
- RSPEC
461
- },
462
- "next_statement" => lambda { |mutation|
463
- method_name = parse_method_name(mutation.subject.name)
464
- original_line, mutated_line = extract_diff_lines(mutation.diff)
465
- <<~RSPEC.strip
466
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
467
- # #{mutation.file_path}:#{mutation.line}
468
- it 'verifies the next skips the iteration correctly in ##{method_name}' do
469
- # Assert the iteration is skipped and the expected value is yielded
470
- result = subject.#{method_name}(input_value)
471
- expect(result).to eq(expected)
472
- end
473
- RSPEC
474
- },
475
- "redo_statement" => lambda { |mutation|
476
- method_name = parse_method_name(mutation.subject.name)
477
- original_line, mutated_line = extract_diff_lines(mutation.diff)
478
- <<~RSPEC.strip
479
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
480
- # #{mutation.file_path}:#{mutation.line}
481
- it 'verifies the redo retry logic is necessary in ##{method_name}' do
482
- # Assert the iteration restart changes the outcome
483
- result = subject.#{method_name}(input_value)
484
- expect(result).to eq(expected)
485
- end
486
- RSPEC
487
- },
488
- "bitwise_replacement" => lambda { |mutation|
489
- method_name = parse_method_name(mutation.subject.name)
490
- original_line, mutated_line = extract_diff_lines(mutation.diff)
491
- <<~RSPEC.strip
492
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
493
- # #{mutation.file_path}:#{mutation.line}
494
- it 'verifies the exact bitwise result in ##{method_name}' do
495
- # Assert the exact bit-level result to distinguish &, |, and ^ operators
496
- result = subject.#{method_name}(input_value)
497
- expect(result).to eq(expected)
498
- end
499
- RSPEC
500
- },
501
- "bitwise_complement" => lambda { |mutation|
502
- method_name = parse_method_name(mutation.subject.name)
503
- original_line, mutated_line = extract_diff_lines(mutation.diff)
504
- <<~RSPEC.strip
505
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
506
- # #{mutation.file_path}:#{mutation.line}
507
- it 'verifies the bitwise complement result in ##{method_name}' do
508
- # Assert the exact complement (~) value, not just sign or magnitude
509
- result = subject.#{method_name}(input_value)
510
- expect(result).to eq(expected)
511
- end
512
- RSPEC
513
- },
514
- "bang_method" => lambda { |mutation|
515
- method_name = parse_method_name(mutation.subject.name)
516
- original_line, mutated_line = extract_diff_lines(mutation.diff)
517
- <<~RSPEC.strip
518
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
519
- # #{mutation.file_path}:#{mutation.line}
520
- it 'verifies in-place vs copy semantics matter in ##{method_name}' do
521
- # Assert that the original object is or is not modified
522
- result = subject.#{method_name}(input_value)
523
- expect(result).to eq(expected)
524
- end
525
- RSPEC
526
- },
527
- "zsuper_removal" => lambda { |mutation|
528
- method_name = parse_method_name(mutation.subject.name)
529
- original_line, mutated_line = extract_diff_lines(mutation.diff)
530
- <<~RSPEC.strip
531
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
532
- # #{mutation.file_path}:#{mutation.line}
533
- it 'verifies inherited behavior from super is needed in ##{method_name}' do
534
- # Assert that the result depends on the superclass implementation
535
- result = subject.#{method_name}(input_value)
536
- expect(result).to eq(expected)
537
- end
538
- RSPEC
539
- },
540
- "explicit_super_mutation" => lambda { |mutation|
541
- method_name = parse_method_name(mutation.subject.name)
542
- original_line, mutated_line = extract_diff_lines(mutation.diff)
543
- <<~RSPEC.strip
544
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
545
- # #{mutation.file_path}:#{mutation.line}
546
- it 'verifies the correct arguments are passed to super in ##{method_name}' do
547
- # Assert the inherited method receives the expected arguments
548
- result = subject.#{method_name}(input_value)
549
- expect(result).to eq(expected)
550
- end
551
- RSPEC
552
- },
553
- "index_to_fetch" => lambda { |mutation|
554
- method_name = parse_method_name(mutation.subject.name)
555
- original_line, mutated_line = extract_diff_lines(mutation.diff)
556
- <<~RSPEC.strip
557
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
558
- # #{mutation.file_path}:#{mutation.line}
559
- it 'distinguishes [] from .fetch for missing keys in ##{method_name}' do
560
- # Access a missing key: [] returns nil, .fetch raises KeyError
561
- expect { subject.#{method_name}(collection_with_missing_key) }.to raise_error(KeyError)
562
- end
563
- RSPEC
564
- },
565
- "index_to_dig" => lambda { |mutation|
566
- method_name = parse_method_name(mutation.subject.name)
567
- original_line, mutated_line = extract_diff_lines(mutation.diff)
568
- <<~RSPEC.strip
569
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
570
- # #{mutation.file_path}:#{mutation.line}
571
- it 'verifies the chained [] access returns the correct nested value in ##{method_name}' do
572
- # Assert the nested lookup produces the expected value
573
- result = subject.#{method_name}(nested_collection)
574
- expect(result).to eq(expected)
575
- end
576
- RSPEC
577
- },
578
- "index_assignment_removal" => lambda { |mutation|
579
- method_name = parse_method_name(mutation.subject.name)
580
- original_line, mutated_line = extract_diff_lines(mutation.diff)
581
- <<~RSPEC.strip
582
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
583
- # #{mutation.file_path}:#{mutation.line}
584
- it 'verifies the []= assignment modifies the collection in ##{method_name}' do
585
- # Assert the collection contains the assigned value after the method runs
586
- result = subject.#{method_name}(collection)
587
- expect(result).to include(expected_key => expected_value)
588
- end
589
- RSPEC
590
- },
591
- "pattern_matching_guard" => lambda { |mutation|
592
- method_name = parse_method_name(mutation.subject.name)
593
- original_line, mutated_line = extract_diff_lines(mutation.diff)
594
- <<~RSPEC.strip
595
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
596
- # #{mutation.file_path}:#{mutation.line}
597
- it 'verifies the pattern guard filters correctly in ##{method_name}' do
598
- # Test with input that matches the pattern but fails the guard condition
599
- # The guard should prevent matching, routing to a different branch
600
- result = subject.#{method_name}(input_matching_pattern_but_failing_guard)
601
- expect(result).to eq(expected)
602
- end
603
- RSPEC
604
- },
605
- "pattern_matching_alternative" => lambda { |mutation|
606
- method_name = parse_method_name(mutation.subject.name)
607
- original_line, mutated_line = extract_diff_lines(mutation.diff)
608
- <<~RSPEC.strip
609
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
610
- # #{mutation.file_path}:#{mutation.line}
611
- it 'verifies each pattern alternative is reachable in ##{method_name}' do
612
- # Test with input that matches only one specific alternative
613
- # Each alternative should have a dedicated test case
614
- result = subject.#{method_name}(input_for_specific_alternative)
615
- expect(result).to eq(expected)
616
- end
617
- RSPEC
618
- },
619
- "collection_return" => lambda { |mutation|
620
- method_name = parse_method_name(mutation.subject.name)
621
- original_line, mutated_line = extract_diff_lines(mutation.diff)
622
- <<~RSPEC.strip
623
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
624
- # #{mutation.file_path}:#{mutation.line}
625
- it 'returns a non-empty collection from ##{method_name}' do
626
- # Assert the collection has the expected elements, not just non-empty
627
- result = subject.#{method_name}(input_value)
628
- expect(result).to eq(expected)
629
- end
630
- RSPEC
631
- },
632
- "scalar_return" => lambda { |mutation|
633
- method_name = parse_method_name(mutation.subject.name)
634
- original_line, mutated_line = extract_diff_lines(mutation.diff)
635
- <<~RSPEC.strip
636
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
637
- # #{mutation.file_path}:#{mutation.line}
638
- it 'returns a non-zero/non-empty value from ##{method_name}' do
639
- # Assert the exact scalar value, not just presence or type
640
- result = subject.#{method_name}(input_value)
641
- expect(result).to eq(expected)
642
- end
643
- RSPEC
644
- },
645
- "pattern_matching_array" => lambda { |mutation|
646
- method_name = parse_method_name(mutation.subject.name)
647
- original_line, mutated_line = extract_diff_lines(mutation.diff)
648
- <<~RSPEC.strip
649
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
650
- # #{mutation.file_path}:#{mutation.line}
651
- it 'verifies each array pattern element matters in ##{method_name}' do
652
- # Test with input where changing one element type causes a different match
653
- # Each position in the array pattern should be validated
654
- result = subject.#{method_name}(input_with_wrong_element_type)
655
- expect(result).to eq(expected)
656
- end
657
- RSPEC
658
- }
659
- }.freeze
660
-
661
- MINITEST_CONCRETE_TEMPLATES = {
662
- "comparison_replacement" => lambda { |mutation|
663
- method_name = parse_method_name(mutation.subject.name)
664
- safe_name = sanitize_method_name(method_name)
665
- original_line, mutated_line = extract_diff_lines(mutation.diff)
666
- <<~MINITEST.strip
667
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
668
- # #{mutation.file_path}:#{mutation.line}
669
- def test_returns_correct_result_at_comparison_boundary_in_#{safe_name}
670
- # Test with values where the original operator and mutated operator
671
- # produce different results (e.g., equal values for > vs >=)
672
- result = subject.#{method_name}(boundary_value)
673
- assert_equal expected, result
674
- end
675
- MINITEST
676
- },
677
- "arithmetic_replacement" => lambda { |mutation|
678
- method_name = parse_method_name(mutation.subject.name)
679
- safe_name = sanitize_method_name(method_name)
680
- original_line, mutated_line = extract_diff_lines(mutation.diff)
681
- <<~MINITEST.strip
682
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
683
- # #{mutation.file_path}:#{mutation.line}
684
- def test_computes_correct_arithmetic_result_in_#{safe_name}
685
- # Assert the exact numeric result, not just truthiness or sign
686
- result = subject.#{method_name}(input_value)
687
- assert_equal expected, result
688
- end
689
- MINITEST
690
- },
691
- "boolean_operator_replacement" => lambda { |mutation|
692
- method_name = parse_method_name(mutation.subject.name)
693
- safe_name = sanitize_method_name(method_name)
694
- original_line, mutated_line = extract_diff_lines(mutation.diff)
695
- <<~MINITEST.strip
696
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
697
- # #{mutation.file_path}:#{mutation.line}
698
- def test_returns_correct_result_when_one_condition_differs_in_#{safe_name}
699
- # Use inputs where only one operand is truthy to distinguish && from ||
700
- result = subject.#{method_name}(input_value)
701
- assert_equal expected, result
702
- end
703
- MINITEST
704
- },
705
- "boolean_literal_replacement" => lambda { |mutation|
706
- method_name = parse_method_name(mutation.subject.name)
707
- safe_name = sanitize_method_name(method_name)
708
- original_line, mutated_line = extract_diff_lines(mutation.diff)
709
- <<~MINITEST.strip
710
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
711
- # #{mutation.file_path}:#{mutation.line}
712
- def test_returns_expected_boolean_value_from_#{safe_name}
713
- # Assert the exact true/false/nil value, not just truthiness
714
- result = subject.#{method_name}(input_value)
715
- assert_equal expected, result
716
- end
717
- MINITEST
718
- },
719
- "negation_insertion" => lambda { |mutation|
720
- method_name = parse_method_name(mutation.subject.name)
721
- safe_name = sanitize_method_name(method_name)
722
- original_line, mutated_line = extract_diff_lines(mutation.diff)
723
- <<~MINITEST.strip
724
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
725
- # #{mutation.file_path}:#{mutation.line}
726
- def test_returns_correct_boolean_from_predicate_in_#{safe_name}
727
- # Assert the exact true/false result, not just truthiness
728
- result = subject.#{method_name}(input_value)
729
- assert_includes [true, false], result
730
- end
731
- MINITEST
732
- },
733
- "integer_literal" => lambda { |mutation|
734
- method_name = parse_method_name(mutation.subject.name)
735
- safe_name = sanitize_method_name(method_name)
736
- original_line, mutated_line = extract_diff_lines(mutation.diff)
737
- <<~MINITEST.strip
738
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
739
- # #{mutation.file_path}:#{mutation.line}
740
- def test_returns_exact_integer_value_from_#{safe_name}
741
- # Assert the exact numeric value, not just > 0 or truthy
742
- result = subject.#{method_name}(input_value)
743
- assert_equal expected, result
744
- end
745
- MINITEST
746
- },
747
- "float_literal" => lambda { |mutation|
748
- method_name = parse_method_name(mutation.subject.name)
749
- safe_name = sanitize_method_name(method_name)
750
- original_line, mutated_line = extract_diff_lines(mutation.diff)
751
- <<~MINITEST.strip
752
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
753
- # #{mutation.file_path}:#{mutation.line}
754
- def test_returns_exact_float_value_from_#{safe_name}
755
- # Assert the exact floating-point result
756
- result = subject.#{method_name}(input_value)
757
- assert_in_delta expected, result
758
- end
759
- MINITEST
760
- },
761
- "string_literal" => lambda { |mutation|
762
- method_name = parse_method_name(mutation.subject.name)
763
- safe_name = sanitize_method_name(method_name)
764
- original_line, mutated_line = extract_diff_lines(mutation.diff)
765
- <<~MINITEST.strip
766
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
767
- # #{mutation.file_path}:#{mutation.line}
768
- def test_returns_exact_string_content_from_#{safe_name}
769
- # Assert the exact string value, not just presence or non-empty
770
- result = subject.#{method_name}(input_value)
771
- assert_equal expected, result
772
- end
773
- MINITEST
774
- },
775
- "symbol_literal" => lambda { |mutation|
776
- method_name = parse_method_name(mutation.subject.name)
777
- safe_name = sanitize_method_name(method_name)
778
- original_line, mutated_line = extract_diff_lines(mutation.diff)
779
- <<~MINITEST.strip
780
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
781
- # #{mutation.file_path}:#{mutation.line}
782
- def test_returns_exact_symbol_from_#{safe_name}
783
- # Assert the exact symbol value, not just that it is a Symbol
784
- result = subject.#{method_name}(input_value)
785
- assert_equal expected, result
786
- end
787
- MINITEST
788
- },
789
- "array_literal" => lambda { |mutation|
790
- method_name = parse_method_name(mutation.subject.name)
791
- safe_name = sanitize_method_name(method_name)
792
- original_line, mutated_line = extract_diff_lines(mutation.diff)
793
- <<~MINITEST.strip
794
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
795
- # #{mutation.file_path}:#{mutation.line}
796
- def test_returns_expected_array_contents_from_#{safe_name}
797
- # Assert the exact array elements, not just non-empty or truthy
798
- result = subject.#{method_name}(input_value)
799
- assert_equal expected, result
800
- end
801
- MINITEST
802
- },
803
- "hash_literal" => lambda { |mutation|
804
- method_name = parse_method_name(mutation.subject.name)
805
- safe_name = sanitize_method_name(method_name)
806
- original_line, mutated_line = extract_diff_lines(mutation.diff)
807
- <<~MINITEST.strip
808
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
809
- # #{mutation.file_path}:#{mutation.line}
810
- def test_returns_expected_hash_contents_from_#{safe_name}
811
- # Assert the exact keys and values, not just non-empty or truthy
812
- result = subject.#{method_name}(input_value)
813
- assert_equal expected, result
814
- end
815
- MINITEST
816
- },
817
- "collection_replacement" => lambda { |mutation|
818
- method_name = parse_method_name(mutation.subject.name)
819
- safe_name = sanitize_method_name(method_name)
820
- original_line, mutated_line = extract_diff_lines(mutation.diff)
821
- <<~MINITEST.strip
822
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
823
- # #{mutation.file_path}:#{mutation.line}
824
- def test_uses_return_value_of_collection_operation_in_#{safe_name}
825
- # Assert the return value of the collection method, not just side effects
826
- result = subject.#{method_name}(input_value)
827
- assert_equal expected, result
828
- end
829
- MINITEST
830
- },
831
- "conditional_negation" => lambda { |mutation|
832
- method_name = parse_method_name(mutation.subject.name)
833
- safe_name = sanitize_method_name(method_name)
834
- original_line, mutated_line = extract_diff_lines(mutation.diff)
835
- <<~MINITEST.strip
836
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
837
- # #{mutation.file_path}:#{mutation.line}
838
- def test_exercises_both_branches_of_conditional_in_#{safe_name}
839
- # Test with inputs that make the condition true AND false
840
- result = subject.#{method_name}(input_value)
841
- assert_equal expected, result
842
- end
843
- MINITEST
844
- },
845
- "conditional_branch" => lambda { |mutation|
846
- method_name = parse_method_name(mutation.subject.name)
847
- safe_name = sanitize_method_name(method_name)
848
- original_line, mutated_line = extract_diff_lines(mutation.diff)
849
- <<~MINITEST.strip
850
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
851
- # #{mutation.file_path}:#{mutation.line}
852
- def test_exercises_removed_branch_of_conditional_in_#{safe_name}
853
- # Test with inputs that trigger the branch removed by this mutation
854
- result = subject.#{method_name}(input_value)
855
- assert_equal expected, result
856
- end
857
- MINITEST
858
- },
859
- "statement_deletion" => lambda { |mutation|
860
- method_name = parse_method_name(mutation.subject.name)
861
- safe_name = sanitize_method_name(method_name)
862
- original_line, _mutated_line = extract_diff_lines(mutation.diff)
863
- <<~MINITEST.strip
864
- # Mutation: deleted `#{original_line}` in #{mutation.subject.name}
865
- # #{mutation.file_path}:#{mutation.line}
866
- def test_depends_on_side_effect_of_deleted_statement_in_#{safe_name}
867
- # Assert a side effect or return value that changes when this statement is removed
868
- subject.#{method_name}(input_value)
869
- assert_equal expected, observable_side_effect
870
- end
871
- MINITEST
872
- },
873
- "method_body_replacement" => lambda { |mutation|
874
- method_name = parse_method_name(mutation.subject.name)
875
- safe_name = sanitize_method_name(method_name)
876
- original_line, mutated_line = extract_diff_lines(mutation.diff)
877
- <<~MINITEST.strip
878
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
879
- # #{mutation.file_path}:#{mutation.line}
880
- def test_verifies_return_value_or_side_effects_of_#{safe_name}
881
- # Assert the method produces a meaningful result, not just nil
882
- result = subject.#{method_name}(input_value)
883
- assert_equal expected, result
884
- end
885
- MINITEST
886
- },
887
- "return_value_removal" => lambda { |mutation|
888
- method_name = parse_method_name(mutation.subject.name)
889
- safe_name = sanitize_method_name(method_name)
890
- original_line, mutated_line = extract_diff_lines(mutation.diff)
891
- <<~MINITEST.strip
892
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
893
- # #{mutation.file_path}:#{mutation.line}
894
- def test_uses_return_value_of_#{safe_name}
895
- # Assert the caller depends on the return value, not just side effects
896
- result = subject.#{method_name}(input_value)
897
- assert_equal expected, result
898
- end
899
- MINITEST
900
- },
901
- "method_call_removal" => lambda { |mutation|
902
- method_name = parse_method_name(mutation.subject.name)
903
- safe_name = sanitize_method_name(method_name)
904
- original_line, mutated_line = extract_diff_lines(mutation.diff)
905
- <<~MINITEST.strip
906
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
907
- # #{mutation.file_path}:#{mutation.line}
908
- def test_depends_on_return_value_or_side_effect_of_call_in_#{safe_name}
909
- # Assert the method call's effect is observable
910
- result = subject.#{method_name}(input_value)
911
- assert_equal expected, result
912
- end
913
- MINITEST
914
- },
915
- "compound_assignment" => lambda { |mutation|
916
- method_name = parse_method_name(mutation.subject.name)
917
- safe_name = sanitize_method_name(method_name)
918
- original_line, mutated_line = extract_diff_lines(mutation.diff)
919
- <<~MINITEST.strip
920
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
921
- # #{mutation.file_path}:#{mutation.line}
922
- def test_verifies_compound_assignment_side_effect_in_#{safe_name}
923
- # Assert the accumulated value after the compound assignment
924
- # The mutation changes the operator, so the final value will differ
925
- subject.#{method_name}(input_value)
926
- assert_equal expected, observable_side_effect
927
- end
928
- MINITEST
929
- },
930
- "nil_replacement" => lambda { |mutation|
931
- method_name = parse_method_name(mutation.subject.name)
932
- safe_name = sanitize_method_name(method_name)
933
- original_line, mutated_line = extract_diff_lines(mutation.diff)
934
- <<~MINITEST.strip
935
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
936
- # #{mutation.file_path}:#{mutation.line}
937
- def test_asserts_nil_return_value_from_#{safe_name}
938
- # Assert the method returns nil, not a substituted value
939
- result = subject.#{method_name}(input_value)
940
- assert_nil result
941
- end
942
- MINITEST
943
- },
944
- "superclass_removal" => lambda { |mutation|
945
- method_name = parse_method_name(mutation.subject.name)
946
- safe_name = sanitize_method_name(method_name)
947
- original_line, _mutated_line = extract_diff_lines(mutation.diff)
948
- <<~MINITEST.strip
949
- # Mutation: removed superclass from `#{original_line}` in #{mutation.subject.name}
950
- # #{mutation.file_path}:#{mutation.line}
951
- def test_depends_on_inherited_behavior_in_#{safe_name}
952
- # Assert behavior that comes from the superclass
953
- result = subject.#{method_name}(input_value)
954
- assert_equal expected, result
955
- end
956
- MINITEST
957
- },
958
- "local_variable_assignment" => lambda { |mutation|
959
- method_name = parse_method_name(mutation.subject.name)
960
- safe_name = sanitize_method_name(method_name)
961
- original_line, mutated_line = extract_diff_lines(mutation.diff)
962
- <<~MINITEST.strip
963
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
964
- # #{mutation.file_path}:#{mutation.line}
965
- def test_verifies_local_variable_assignment_is_used_in_#{safe_name}
966
- # Assert that the assigned variable is read later, not just the value expression
967
- result = subject.#{method_name}(input_value)
968
- assert_equal expected, result
969
- end
970
- MINITEST
971
- },
972
- "instance_variable_write" => lambda { |mutation|
973
- method_name = parse_method_name(mutation.subject.name)
974
- safe_name = sanitize_method_name(method_name)
975
- original_line, mutated_line = extract_diff_lines(mutation.diff)
976
- <<~MINITEST.strip
977
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
978
- # #{mutation.file_path}:#{mutation.line}
979
- def test_verifies_instance_variable_is_set_correctly_in_#{safe_name}
980
- # Assert that the instance variable holds the expected value after the method runs
981
- subject.#{method_name}(input_value)
982
- assert_equal expected, subject.instance_variable_get(:@variable)
983
- end
984
- MINITEST
985
- },
986
- "class_variable_write" => lambda { |mutation|
987
- method_name = parse_method_name(mutation.subject.name)
988
- safe_name = sanitize_method_name(method_name)
989
- original_line, mutated_line = extract_diff_lines(mutation.diff)
990
- <<~MINITEST.strip
991
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
992
- # #{mutation.file_path}:#{mutation.line}
993
- def test_verifies_class_variable_shared_state_in_#{safe_name}
994
- # Assert that the class variable holds the expected value and affects shared state
995
- subject.#{method_name}(input_value)
996
- assert_equal expected, klass.class_variable_get(:@@variable)
997
- end
998
- MINITEST
999
- },
1000
- "global_variable_write" => lambda { |mutation|
1001
- method_name = parse_method_name(mutation.subject.name)
1002
- safe_name = sanitize_method_name(method_name)
1003
- original_line, mutated_line = extract_diff_lines(mutation.diff)
1004
- <<~MINITEST.strip
1005
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
1006
- # #{mutation.file_path}:#{mutation.line}
1007
- def test_verifies_global_variable_is_set_correctly_in_#{safe_name}
1008
- # Assert that the global variable holds the expected value after the method runs
1009
- subject.#{method_name}(input_value)
1010
- assert_equal expected, $variable
1011
- end
1012
- MINITEST
1013
- },
1014
- "mixin_removal" => lambda { |mutation|
1015
- method_name = parse_method_name(mutation.subject.name)
1016
- safe_name = sanitize_method_name(method_name)
1017
- original_line, _mutated_line = extract_diff_lines(mutation.diff)
1018
- <<~MINITEST.strip
1019
- # Mutation: removed `#{original_line}` in #{mutation.subject.name}
1020
- # #{mutation.file_path}:#{mutation.line}
1021
- def test_depends_on_behavior_from_included_module_in_#{safe_name}
1022
- # Assert behavior provided by the mixin
1023
- result = subject.#{method_name}(input_value)
1024
- assert_equal expected, result
1025
- end
1026
- MINITEST
1027
- },
1028
- "rescue_removal" => lambda { |mutation|
1029
- method_name = parse_method_name(mutation.subject.name)
1030
- safe_name = sanitize_method_name(method_name)
1031
- original_line, _mutated_line = extract_diff_lines(mutation.diff)
1032
- <<~MINITEST.strip
1033
- # Mutation: removed `#{original_line}` in #{mutation.subject.name}
1034
- # #{mutation.file_path}:#{mutation.line}
1035
- def test_verifies_rescue_handler_is_needed_in_#{safe_name}
1036
- # Trigger the rescued exception and assert the handler's effect
1037
- result = subject.#{method_name}(input_that_raises)
1038
- assert_equal expected, result
1039
- end
1040
- MINITEST
1041
- },
1042
- "rescue_body_replacement" => lambda { |mutation|
1043
- method_name = parse_method_name(mutation.subject.name)
1044
- safe_name = sanitize_method_name(method_name)
1045
- original_line, mutated_line = extract_diff_lines(mutation.diff)
1046
- <<~MINITEST.strip
1047
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
1048
- # #{mutation.file_path}:#{mutation.line}
1049
- def test_verifies_rescue_handler_produces_correct_result_in_#{safe_name}
1050
- # Trigger the exception and assert the rescue body's return value or side effect
1051
- result = subject.#{method_name}(input_that_raises)
1052
- assert_equal expected, result
1053
- end
1054
- MINITEST
1055
- },
1056
- "inline_rescue" => lambda { |mutation|
1057
- method_name = parse_method_name(mutation.subject.name)
1058
- safe_name = sanitize_method_name(method_name)
1059
- original_line, mutated_line = extract_diff_lines(mutation.diff)
1060
- <<~MINITEST.strip
1061
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
1062
- # #{mutation.file_path}:#{mutation.line}
1063
- def test_verifies_inline_rescue_fallback_value_in_#{safe_name}
1064
- # Trigger the exception and assert the fallback value is correct
1065
- result = subject.#{method_name}(input_that_raises)
1066
- assert_equal expected, result
1067
- end
1068
- MINITEST
1069
- },
1070
- "ensure_removal" => lambda { |mutation|
1071
- method_name = parse_method_name(mutation.subject.name)
1072
- safe_name = sanitize_method_name(method_name)
1073
- original_line, _mutated_line = extract_diff_lines(mutation.diff)
1074
- <<~MINITEST.strip
1075
- # Mutation: removed ensure block `#{original_line}` in #{mutation.subject.name}
1076
- # #{mutation.file_path}:#{mutation.line}
1077
- def test_verifies_ensure_cleanup_runs_in_#{safe_name}
1078
- # Assert that the cleanup side effect is observable after the method runs
1079
- subject.#{method_name}(input_value)
1080
- assert_equal expected, observable_cleanup_effect
1081
- end
1082
- MINITEST
1083
- },
1084
- "break_statement" => lambda { |mutation|
1085
- method_name = parse_method_name(mutation.subject.name)
1086
- safe_name = sanitize_method_name(method_name)
1087
- original_line, mutated_line = extract_diff_lines(mutation.diff)
1088
- <<~MINITEST.strip
1089
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
1090
- # #{mutation.file_path}:#{mutation.line}
1091
- def test_verifies_break_exits_loop_correctly_in_#{safe_name}
1092
- # Assert the loop exits early and returns the expected value
1093
- result = subject.#{method_name}(input_value)
1094
- assert_equal expected, result
1095
- end
1096
- MINITEST
1097
- },
1098
- "next_statement" => lambda { |mutation|
1099
- method_name = parse_method_name(mutation.subject.name)
1100
- safe_name = sanitize_method_name(method_name)
1101
- original_line, mutated_line = extract_diff_lines(mutation.diff)
1102
- <<~MINITEST.strip
1103
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
1104
- # #{mutation.file_path}:#{mutation.line}
1105
- def test_verifies_next_skips_iteration_correctly_in_#{safe_name}
1106
- # Assert the iteration is skipped and the expected value is yielded
1107
- result = subject.#{method_name}(input_value)
1108
- assert_equal expected, result
1109
- end
1110
- MINITEST
1111
- },
1112
- "redo_statement" => lambda { |mutation|
1113
- method_name = parse_method_name(mutation.subject.name)
1114
- safe_name = sanitize_method_name(method_name)
1115
- original_line, mutated_line = extract_diff_lines(mutation.diff)
1116
- <<~MINITEST.strip
1117
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
1118
- # #{mutation.file_path}:#{mutation.line}
1119
- def test_verifies_redo_retry_logic_is_necessary_in_#{safe_name}
1120
- # Assert the iteration restart changes the outcome
1121
- result = subject.#{method_name}(input_value)
1122
- assert_equal expected, result
1123
- end
1124
- MINITEST
1125
- },
1126
- "bitwise_replacement" => lambda { |mutation|
1127
- method_name = parse_method_name(mutation.subject.name)
1128
- safe_name = sanitize_method_name(method_name)
1129
- original_line, mutated_line = extract_diff_lines(mutation.diff)
1130
- <<~MINITEST.strip
1131
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
1132
- # #{mutation.file_path}:#{mutation.line}
1133
- def test_verifies_exact_bitwise_result_in_#{safe_name}
1134
- # Assert the exact bit-level result to distinguish &, |, and ^ operators
1135
- result = subject.#{method_name}(input_value)
1136
- assert_equal expected, result
1137
- end
1138
- MINITEST
1139
- },
1140
- "bitwise_complement" => lambda { |mutation|
1141
- method_name = parse_method_name(mutation.subject.name)
1142
- safe_name = sanitize_method_name(method_name)
1143
- original_line, mutated_line = extract_diff_lines(mutation.diff)
1144
- <<~MINITEST.strip
1145
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
1146
- # #{mutation.file_path}:#{mutation.line}
1147
- def test_verifies_bitwise_complement_result_in_#{safe_name}
1148
- # Assert the exact complement (~) value, not just sign or magnitude
1149
- result = subject.#{method_name}(input_value)
1150
- assert_equal expected, result
1151
- end
1152
- MINITEST
1153
- },
1154
- "bang_method" => lambda { |mutation|
1155
- method_name = parse_method_name(mutation.subject.name)
1156
- safe_name = sanitize_method_name(method_name)
1157
- original_line, mutated_line = extract_diff_lines(mutation.diff)
1158
- <<~MINITEST.strip
1159
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
1160
- # #{mutation.file_path}:#{mutation.line}
1161
- def test_verifies_in_place_vs_copy_semantics_matter_in_#{safe_name}
1162
- # Assert that the original object is or is not modified
1163
- result = subject.#{method_name}(input_value)
1164
- assert_equal expected, result
1165
- end
1166
- MINITEST
1167
- },
1168
- "zsuper_removal" => lambda { |mutation|
1169
- method_name = parse_method_name(mutation.subject.name)
1170
- safe_name = sanitize_method_name(method_name)
1171
- original_line, mutated_line = extract_diff_lines(mutation.diff)
1172
- <<~MINITEST.strip
1173
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
1174
- # #{mutation.file_path}:#{mutation.line}
1175
- def test_verifies_inherited_behavior_from_super_in_#{safe_name}
1176
- # Assert that the result depends on the superclass implementation
1177
- result = subject.#{method_name}(input_value)
1178
- assert_equal expected, result
1179
- end
1180
- MINITEST
1181
- },
1182
- "explicit_super_mutation" => lambda { |mutation|
1183
- method_name = parse_method_name(mutation.subject.name)
1184
- safe_name = sanitize_method_name(method_name)
1185
- original_line, mutated_line = extract_diff_lines(mutation.diff)
1186
- <<~MINITEST.strip
1187
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
1188
- # #{mutation.file_path}:#{mutation.line}
1189
- def test_verifies_correct_arguments_passed_to_super_in_#{safe_name}
1190
- # Assert the inherited method receives the expected arguments
1191
- result = subject.#{method_name}(input_value)
1192
- assert_equal expected, result
1193
- end
1194
- MINITEST
1195
- },
1196
- "index_to_fetch" => lambda { |mutation|
1197
- method_name = parse_method_name(mutation.subject.name)
1198
- safe_name = sanitize_method_name(method_name)
1199
- original_line, mutated_line = extract_diff_lines(mutation.diff)
1200
- <<~MINITEST.strip
1201
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
1202
- # #{mutation.file_path}:#{mutation.line}
1203
- def test_distinguishes_bracket_from_fetch_for_missing_keys_in_#{safe_name}
1204
- # Access a missing key: [] returns nil, .fetch raises KeyError
1205
- assert_raises(KeyError) { subject.#{method_name}(collection_with_missing_key) }
1206
- end
1207
- MINITEST
1208
- },
1209
- "index_to_dig" => lambda { |mutation|
1210
- method_name = parse_method_name(mutation.subject.name)
1211
- safe_name = sanitize_method_name(method_name)
1212
- original_line, mutated_line = extract_diff_lines(mutation.diff)
1213
- <<~MINITEST.strip
1214
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
1215
- # #{mutation.file_path}:#{mutation.line}
1216
- def test_verifies_chained_bracket_access_returns_correct_nested_value_in_#{safe_name}
1217
- # Assert the nested lookup produces the expected value
1218
- result = subject.#{method_name}(nested_collection)
1219
- assert_equal expected, result
1220
- end
1221
- MINITEST
1222
- },
1223
- "index_assignment_removal" => lambda { |mutation|
1224
- method_name = parse_method_name(mutation.subject.name)
1225
- safe_name = sanitize_method_name(method_name)
1226
- original_line, mutated_line = extract_diff_lines(mutation.diff)
1227
- <<~MINITEST.strip
1228
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
1229
- # #{mutation.file_path}:#{mutation.line}
1230
- def test_verifies_bracket_assignment_modifies_collection_in_#{safe_name}
1231
- # Assert the collection contains the assigned value after the method runs
1232
- result = subject.#{method_name}(collection)
1233
- assert_includes result, expected_value
1234
- end
1235
- MINITEST
1236
- },
1237
- "pattern_matching_guard" => lambda { |mutation|
1238
- method_name = parse_method_name(mutation.subject.name)
1239
- safe_name = sanitize_method_name(method_name)
1240
- original_line, mutated_line = extract_diff_lines(mutation.diff)
1241
- <<~MINITEST.strip
1242
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
1243
- # #{mutation.file_path}:#{mutation.line}
1244
- def test_verifies_pattern_guard_filters_correctly_in_#{safe_name}
1245
- # Test with input that matches the pattern but fails the guard condition
1246
- # The guard should prevent matching, routing to a different branch
1247
- result = subject.#{method_name}(input_matching_pattern_but_failing_guard)
1248
- assert_equal expected, result
1249
- end
1250
- MINITEST
1251
- },
1252
- "pattern_matching_alternative" => lambda { |mutation|
1253
- method_name = parse_method_name(mutation.subject.name)
1254
- safe_name = sanitize_method_name(method_name)
1255
- original_line, mutated_line = extract_diff_lines(mutation.diff)
1256
- <<~MINITEST.strip
1257
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
1258
- # #{mutation.file_path}:#{mutation.line}
1259
- def test_verifies_each_pattern_alternative_is_reachable_in_#{safe_name}
1260
- # Test with input that matches only one specific alternative
1261
- # Each alternative should have a dedicated test case
1262
- result = subject.#{method_name}(input_for_specific_alternative)
1263
- assert_equal expected, result
1264
- end
1265
- MINITEST
1266
- },
1267
- "collection_return" => lambda { |mutation|
1268
- method_name = parse_method_name(mutation.subject.name)
1269
- safe_name = sanitize_method_name(method_name)
1270
- original_line, mutated_line = extract_diff_lines(mutation.diff)
1271
- <<~MINITEST.strip
1272
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
1273
- # #{mutation.file_path}:#{mutation.line}
1274
- def test_returns_non_empty_collection_from_#{safe_name}
1275
- # Assert the collection has the expected elements, not just non-empty
1276
- result = subject.#{method_name}(input_value)
1277
- assert_equal expected, result
1278
- end
1279
- MINITEST
1280
- },
1281
- "scalar_return" => lambda { |mutation|
1282
- method_name = parse_method_name(mutation.subject.name)
1283
- safe_name = sanitize_method_name(method_name)
1284
- original_line, mutated_line = extract_diff_lines(mutation.diff)
1285
- <<~MINITEST.strip
1286
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
1287
- # #{mutation.file_path}:#{mutation.line}
1288
- def test_returns_non_zero_non_empty_value_from_#{safe_name}
1289
- # Assert the exact scalar value, not just presence or type
1290
- result = subject.#{method_name}(input_value)
1291
- assert_equal expected, result
1292
- end
1293
- MINITEST
1294
- },
1295
- "pattern_matching_array" => lambda { |mutation|
1296
- method_name = parse_method_name(mutation.subject.name)
1297
- safe_name = sanitize_method_name(method_name)
1298
- original_line, mutated_line = extract_diff_lines(mutation.diff)
1299
- <<~MINITEST.strip
1300
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
1301
- # #{mutation.file_path}:#{mutation.line}
1302
- def test_verifies_each_array_pattern_element_matters_in_#{safe_name}
1303
- # Test with input where changing one element type causes a different match
1304
- # Each position in the array pattern should be validated
1305
- result = subject.#{method_name}(input_with_wrong_element_type)
1306
- assert_equal expected, result
1307
- end
1308
- MINITEST
1309
- }
1310
- }.freeze
1311
-
1312
6
  DEFAULT_SUGGESTION = "Add a more specific test that detects this mutation"
1313
7
 
1314
- def initialize(suggest_tests: false, integration: :rspec)
8
+ def initialize(suggest_tests: false, integration: :rspec, registry: Registry.default)
1315
9
  @suggest_tests = suggest_tests
1316
10
  @integration = integration
11
+ @registry = registry
1317
12
  end
1318
13
 
1319
14
  # Generate suggestions for survived mutations.
@@ -1334,29 +29,15 @@ class Evilution::Reporter::Suggestion
1334
29
  # @param mutation [Mutation]
1335
30
  # @return [String]
1336
31
  def suggestion_for(mutation)
32
+ op = mutation.operator_name
1337
33
  if @suggest_tests
1338
- templates = @integration == :minitest ? MINITEST_CONCRETE_TEMPLATES : CONCRETE_TEMPLATES
1339
- concrete = templates[mutation.operator_name]
34
+ concrete = @registry.concrete(op, integration: @integration)
1340
35
  return concrete.call(mutation) if concrete
1341
36
  end
1342
37
 
1343
- TEMPLATES.fetch(mutation.operator_name, DEFAULT_SUGGESTION)
1344
- end
1345
-
1346
- class << self
1347
- def parse_method_name(subject_name)
1348
- subject_name.split(/[#.]/).last
1349
- end
1350
-
1351
- def sanitize_method_name(name)
1352
- name.gsub(/[^a-zA-Z0-9_]/, "_").gsub(/_+/, "_").gsub(/\A_|_\z/, "")
1353
- end
1354
-
1355
- def extract_diff_lines(diff)
1356
- lines = diff.split("\n")
1357
- original = lines.find { |l| l.start_with?("- ") }
1358
- mutated = lines.find { |l| l.start_with?("+ ") }
1359
- [original&.sub(/^- /, "")&.strip, mutated&.sub(/^\+ /, "")&.strip]
1360
- end
38
+ @registry.generic(op) || DEFAULT_SUGGESTION
1361
39
  end
1362
40
  end
41
+
42
+ require_relative "suggestion/diff_helpers"
43
+ require_relative "suggestion/registry"