evilution 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/.beads/interactions.jsonl +65 -0
  3. data/.rubocop_todo.yml +0 -1
  4. data/CHANGELOG.md +39 -0
  5. data/README.md +19 -0
  6. data/lib/evilution/ast/constant_names.rb +28 -11
  7. data/lib/evilution/ast/pattern/parser.rb +29 -17
  8. data/lib/evilution/baseline.rb +5 -4
  9. data/lib/evilution/cli/commands/session_diff.rb +6 -4
  10. data/lib/evilution/cli/commands/subjects.rb +6 -3
  11. data/lib/evilution/cli/commands/util_mutation.rb +24 -19
  12. data/lib/evilution/cli/parser/command_extractor.rb +9 -11
  13. data/lib/evilution/cli/parser/file_args.rb +3 -1
  14. data/lib/evilution/cli/parser/options_builder.rb +36 -1
  15. data/lib/evilution/cli/parser/stdin_reader.rb +2 -2
  16. data/lib/evilution/cli/parser.rb +18 -20
  17. data/lib/evilution/cli/printers/environment.rb +19 -19
  18. data/lib/evilution/cli/printers/session_diff.rb +8 -8
  19. data/lib/evilution/compare/diff_extractor/evilution.rb +22 -0
  20. data/lib/evilution/compare/diff_extractor/mutant.rb +30 -0
  21. data/lib/evilution/compare/diff_extractor.rb +6 -0
  22. data/lib/evilution/compare/fingerprint.rb +15 -72
  23. data/lib/evilution/compare/line_normalizer.rb +72 -0
  24. data/lib/evilution/compare/normalizer.rb +27 -9
  25. data/lib/evilution/config/validators/profile.rb +11 -0
  26. data/lib/evilution/config.rb +49 -32
  27. data/lib/evilution/disable_comment.rb +21 -12
  28. data/lib/evilution/integration/crash_detector.rb +2 -2
  29. data/lib/evilution/integration/loading/mutation_applier.rb +17 -12
  30. data/lib/evilution/integration/loading/source_evaluator.rb +6 -2
  31. data/lib/evilution/integration/minitest.rb +25 -16
  32. data/lib/evilution/integration/minitest_crash_detector.rb +2 -2
  33. data/lib/evilution/integration/rspec/state_guard/object_space_example_groups.rb +11 -3
  34. data/lib/evilution/integration/rspec.rb +4 -0
  35. data/lib/evilution/isolation/fork.rb +43 -28
  36. data/lib/evilution/isolation/in_process.rb +10 -6
  37. data/lib/evilution/mcp/info_tool/actions/subjects.rb +32 -23
  38. data/lib/evilution/mcp/info_tool/actions/tests.rb +22 -12
  39. data/lib/evilution/mcp/info_tool/request_parser.rb +3 -1
  40. data/lib/evilution/mcp/info_tool.rb +7 -3
  41. data/lib/evilution/mcp/mutate_tool/option_parser.rb +3 -1
  42. data/lib/evilution/mcp/mutate_tool/progress_streamer.rb +5 -1
  43. data/lib/evilution/mcp/mutate_tool/survived_enricher.rb +19 -9
  44. data/lib/evilution/mcp/mutate_tool.rb +27 -14
  45. data/lib/evilution/mcp/session_tool.rb +27 -20
  46. data/lib/evilution/mutation.rb +60 -42
  47. data/lib/evilution/mutator/base.rb +23 -21
  48. data/lib/evilution/mutator/operator/argument_nil_substitution.rb +11 -14
  49. data/lib/evilution/mutator/operator/argument_removal.rb +11 -14
  50. data/lib/evilution/mutator/operator/begin_unwrap.rb +17 -5
  51. data/lib/evilution/mutator/operator/bitwise_complement.rb +26 -19
  52. data/lib/evilution/mutator/operator/block_param_removal.rb +18 -8
  53. data/lib/evilution/mutator/operator/block_pass_removal.rb +19 -15
  54. data/lib/evilution/mutator/operator/case_when.rb +7 -5
  55. data/lib/evilution/mutator/operator/conditional_branch.rb +22 -22
  56. data/lib/evilution/mutator/operator/equality_to_identity.rb +8 -3
  57. data/lib/evilution/mutator/operator/explicit_super_mutation.rb +17 -13
  58. data/lib/evilution/mutator/operator/index_to_at.rb +5 -4
  59. data/lib/evilution/mutator/operator/index_to_dig.rb +12 -6
  60. data/lib/evilution/mutator/operator/index_to_fetch.rb +5 -4
  61. data/lib/evilution/mutator/operator/keyword_argument.rb +30 -25
  62. data/lib/evilution/mutator/operator/mixin_removal.rb +20 -14
  63. data/lib/evilution/mutator/operator/multiple_assignment.rb +12 -13
  64. data/lib/evilution/mutator/operator/predicate_to_nil.rb +20 -0
  65. data/lib/evilution/mutator/operator/receiver_replacement.rb +9 -6
  66. data/lib/evilution/mutator/operator/regex_simplification.rb +62 -67
  67. data/lib/evilution/mutator/operator/rescue_body_replacement.rb +9 -8
  68. data/lib/evilution/mutator/operator/rescue_removal.rb +4 -7
  69. data/lib/evilution/mutator/operator/superclass_removal.rb +21 -15
  70. data/lib/evilution/mutator/registry.rb +20 -0
  71. data/lib/evilution/parallel/work_queue/channel/frame.rb +5 -1
  72. data/lib/evilution/parallel/work_queue/dispatcher.rb +15 -8
  73. data/lib/evilution/parallel/work_queue/worker/loop.rb +1 -1
  74. data/lib/evilution/parallel/work_queue/worker.rb +10 -7
  75. data/lib/evilution/parallel/work_queue.rb +35 -18
  76. data/lib/evilution/process_cleanup.rb +19 -0
  77. data/lib/evilution/reporter/cli/item_formatters/coverage_gap.rb +13 -8
  78. data/lib/evilution/reporter/cli/line_formatters/mutations.rb +17 -8
  79. data/lib/evilution/reporter/html/baseline_keys.rb +1 -1
  80. data/lib/evilution/reporter/html/diff_formatter.rb +1 -1
  81. data/lib/evilution/reporter/html/escape.rb +1 -1
  82. data/lib/evilution/reporter/html/section.rb +1 -1
  83. data/lib/evilution/reporter/html/sections.rb +4 -2
  84. data/lib/evilution/reporter/html/stylesheet.rb +1 -1
  85. data/lib/evilution/reporter/html.rb +8 -3
  86. data/lib/evilution/reporter/json.rb +52 -18
  87. data/lib/evilution/reporter/suggestion/diff_helpers.rb +0 -13
  88. data/lib/evilution/reporter/suggestion/diff_lines.rb +28 -0
  89. data/lib/evilution/reporter/suggestion/registry.rb +1 -5
  90. data/lib/evilution/reporter/suggestion/templates/generic.rb +1 -1
  91. data/lib/evilution/reporter/suggestion/templates/minitest.rb +361 -649
  92. data/lib/evilution/reporter/suggestion/templates/rspec.rb +362 -603
  93. data/lib/evilution/reporter/suggestion/templates.rb +6 -0
  94. data/lib/evilution/result/error_info.rb +20 -0
  95. data/lib/evilution/result/memory_stats.rb +20 -0
  96. data/lib/evilution/result/mutation_result.rb +30 -14
  97. data/lib/evilution/runner/baseline_runner.rb +16 -10
  98. data/lib/evilution/runner/diagnostics.rb +14 -11
  99. data/lib/evilution/runner/isolation_resolver.rb +12 -11
  100. data/lib/evilution/runner/mutation_executor/mutation_runner.rb +1 -3
  101. data/lib/evilution/runner/mutation_executor/neutralization_pipeline.rb +1 -2
  102. data/lib/evilution/runner/mutation_executor/neutralizer/baseline_failed.rb +3 -10
  103. data/lib/evilution/runner/mutation_executor/neutralizer/infra_error.rb +3 -10
  104. data/lib/evilution/runner/mutation_executor/neutralizer.rb +11 -0
  105. data/lib/evilution/runner/mutation_executor/result_cache.rb +4 -4
  106. data/lib/evilution/runner/mutation_executor/result_notifier.rb +1 -3
  107. data/lib/evilution/runner/mutation_executor/result_packer.rb +11 -9
  108. data/lib/evilution/runner/mutation_executor/strategy/parallel.rb +33 -13
  109. data/lib/evilution/runner/mutation_executor/strategy/sequential.rb +2 -4
  110. data/lib/evilution/runner/mutation_executor/strategy.rb +11 -0
  111. data/lib/evilution/runner/mutation_executor.rb +14 -20
  112. data/lib/evilution/runner/mutation_planner.rb +38 -19
  113. data/lib/evilution/runner/report_publisher.rb +1 -2
  114. data/lib/evilution/runner/subject_pipeline.rb +22 -13
  115. data/lib/evilution/runner.rb +36 -34
  116. data/lib/evilution/session/diff.rb +15 -6
  117. data/lib/evilution/spec_ast_cache.rb +26 -12
  118. data/lib/evilution/version.rb +1 -1
  119. data/lib/evilution.rb +1 -0
  120. data/script/memory_check +14 -6
  121. data/scripts/benchmark_density +10 -9
  122. data/scripts/compare_mutations +38 -21
  123. data/scripts/mutant_json_adapter +7 -4
  124. metadata +15 -3
  125. data/lib/evilution/reporter/html/namespace.rb +0 -11
@@ -1,659 +1,371 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../registry"
3
+ require_relative "../templates"
4
4
  require_relative "../diff_helpers"
5
+ require_relative "../diff_lines"
5
6
 
6
7
  module Evilution::Reporter::Suggestion::Templates::Minitest
7
8
  H = Evilution::Reporter::Suggestion::DiffHelpers
8
9
 
10
+ def self.format_header(action, original, mutated, subject_name)
11
+ case action
12
+ when :changed then "changed `#{original}` to `#{mutated}` in #{subject_name}"
13
+ when :deleted then "deleted `#{original}` in #{subject_name}"
14
+ when :removed then "removed `#{original}` in #{subject_name}"
15
+ when :removed_superclass then "removed superclass from `#{original}` in #{subject_name}"
16
+ when :removed_ensure then "removed ensure block `#{original}` in #{subject_name}"
17
+ end
18
+ end
19
+
20
+ def self.build(test_name:, action: :changed, &body_block)
21
+ ->(mutation) { render(test_name, action, body_block, mutation) }
22
+ end
23
+
24
+ def self.render(test_name, action, body_block, mutation)
25
+ method_name = H.parse_method_name(mutation.subject.name)
26
+ safe_name = H.sanitize_method_name(method_name)
27
+ diff_lines = Evilution::Reporter::Suggestion::DiffLines.from_diff(mutation.diff)
28
+ indented = indent_body(body_block.call(method_name))
29
+
30
+ <<~MINITEST.strip
31
+ # Mutation: #{format_header(action, diff_lines.original, diff_lines.mutated, mutation.subject.name)}
32
+ # #{mutation.file_path}:#{mutation.line}
33
+ def test_#{test_name}_#{safe_name}
34
+ #{indented}
35
+ end
36
+ MINITEST
37
+ end
38
+
39
+ def self.indent_body(body)
40
+ body.lines.map { |l| " #{l}" }.join.chomp
41
+ end
42
+
9
43
  MINITEST_ENTRIES = {
10
- "comparison_replacement" => lambda { |mutation|
11
- method_name = H.parse_method_name(mutation.subject.name)
12
- safe_name = H.sanitize_method_name(method_name)
13
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
14
- <<~MINITEST.strip
15
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
16
- # #{mutation.file_path}:#{mutation.line}
17
- def test_returns_correct_result_at_comparison_boundary_in_#{safe_name}
18
- # Test with values where the original operator and mutated operator
19
- # produce different results (e.g., equal values for > vs >=)
20
- result = subject.#{method_name}(boundary_value)
21
- assert_equal expected, result
22
- end
23
- MINITEST
24
- },
25
- "arithmetic_replacement" => lambda { |mutation|
26
- method_name = H.parse_method_name(mutation.subject.name)
27
- safe_name = H.sanitize_method_name(method_name)
28
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
29
- <<~MINITEST.strip
30
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
31
- # #{mutation.file_path}:#{mutation.line}
32
- def test_computes_correct_arithmetic_result_in_#{safe_name}
33
- # Assert the exact numeric result, not just truthiness or sign
34
- result = subject.#{method_name}(input_value)
35
- assert_equal expected, result
36
- end
37
- MINITEST
38
- },
39
- "boolean_operator_replacement" => lambda { |mutation|
40
- method_name = H.parse_method_name(mutation.subject.name)
41
- safe_name = H.sanitize_method_name(method_name)
42
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
43
- <<~MINITEST.strip
44
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
45
- # #{mutation.file_path}:#{mutation.line}
46
- def test_returns_correct_result_when_one_condition_differs_in_#{safe_name}
47
- # Use inputs where only one operand is truthy to distinguish && from ||
48
- result = subject.#{method_name}(input_value)
49
- assert_equal expected, result
50
- end
51
- MINITEST
52
- },
53
- "boolean_literal_replacement" => lambda { |mutation|
54
- method_name = H.parse_method_name(mutation.subject.name)
55
- safe_name = H.sanitize_method_name(method_name)
56
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
57
- <<~MINITEST.strip
58
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
59
- # #{mutation.file_path}:#{mutation.line}
60
- def test_returns_expected_boolean_value_from_#{safe_name}
61
- # Assert the exact true/false/nil value, not just truthiness
62
- result = subject.#{method_name}(input_value)
63
- assert_equal expected, result
64
- end
65
- MINITEST
66
- },
67
- "negation_insertion" => lambda { |mutation|
68
- method_name = H.parse_method_name(mutation.subject.name)
69
- safe_name = H.sanitize_method_name(method_name)
70
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
71
- <<~MINITEST.strip
72
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
73
- # #{mutation.file_path}:#{mutation.line}
74
- def test_returns_correct_boolean_from_predicate_in_#{safe_name}
75
- # Assert the exact true/false result, not just truthiness
76
- result = subject.#{method_name}(input_value)
77
- assert_includes [true, false], result
78
- end
79
- MINITEST
80
- },
81
- "integer_literal" => lambda { |mutation|
82
- method_name = H.parse_method_name(mutation.subject.name)
83
- safe_name = H.sanitize_method_name(method_name)
84
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
85
- <<~MINITEST.strip
86
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
87
- # #{mutation.file_path}:#{mutation.line}
88
- def test_returns_exact_integer_value_from_#{safe_name}
89
- # Assert the exact numeric value, not just > 0 or truthy
90
- result = subject.#{method_name}(input_value)
91
- assert_equal expected, result
92
- end
93
- MINITEST
94
- },
95
- "float_literal" => lambda { |mutation|
96
- method_name = H.parse_method_name(mutation.subject.name)
97
- safe_name = H.sanitize_method_name(method_name)
98
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
99
- <<~MINITEST.strip
100
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
101
- # #{mutation.file_path}:#{mutation.line}
102
- def test_returns_exact_float_value_from_#{safe_name}
103
- # Assert the exact floating-point result
104
- result = subject.#{method_name}(input_value)
105
- assert_in_delta expected, result
106
- end
107
- MINITEST
108
- },
109
- "string_literal" => lambda { |mutation|
110
- method_name = H.parse_method_name(mutation.subject.name)
111
- safe_name = H.sanitize_method_name(method_name)
112
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
113
- <<~MINITEST.strip
114
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
115
- # #{mutation.file_path}:#{mutation.line}
116
- def test_returns_exact_string_content_from_#{safe_name}
117
- # Assert the exact string value, not just presence or non-empty
118
- result = subject.#{method_name}(input_value)
119
- assert_equal expected, result
120
- end
121
- MINITEST
122
- },
123
- "symbol_literal" => lambda { |mutation|
124
- method_name = H.parse_method_name(mutation.subject.name)
125
- safe_name = H.sanitize_method_name(method_name)
126
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
127
- <<~MINITEST.strip
128
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
129
- # #{mutation.file_path}:#{mutation.line}
130
- def test_returns_exact_symbol_from_#{safe_name}
131
- # Assert the exact symbol value, not just that it is a Symbol
132
- result = subject.#{method_name}(input_value)
133
- assert_equal expected, result
134
- end
135
- MINITEST
136
- },
137
- "array_literal" => lambda { |mutation|
138
- method_name = H.parse_method_name(mutation.subject.name)
139
- safe_name = H.sanitize_method_name(method_name)
140
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
141
- <<~MINITEST.strip
142
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
143
- # #{mutation.file_path}:#{mutation.line}
144
- def test_returns_expected_array_contents_from_#{safe_name}
145
- # Assert the exact array elements, not just non-empty or truthy
146
- result = subject.#{method_name}(input_value)
147
- assert_equal expected, result
148
- end
149
- MINITEST
150
- },
151
- "hash_literal" => lambda { |mutation|
152
- method_name = H.parse_method_name(mutation.subject.name)
153
- safe_name = H.sanitize_method_name(method_name)
154
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
155
- <<~MINITEST.strip
156
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
157
- # #{mutation.file_path}:#{mutation.line}
158
- def test_returns_expected_hash_contents_from_#{safe_name}
159
- # Assert the exact keys and values, not just non-empty or truthy
160
- result = subject.#{method_name}(input_value)
161
- assert_equal expected, result
162
- end
163
- MINITEST
164
- },
165
- "collection_replacement" => lambda { |mutation|
166
- method_name = H.parse_method_name(mutation.subject.name)
167
- safe_name = H.sanitize_method_name(method_name)
168
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
169
- <<~MINITEST.strip
170
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
171
- # #{mutation.file_path}:#{mutation.line}
172
- def test_uses_return_value_of_collection_operation_in_#{safe_name}
173
- # Assert the return value of the collection method, not just side effects
174
- result = subject.#{method_name}(input_value)
175
- assert_equal expected, result
176
- end
177
- MINITEST
178
- },
179
- "conditional_negation" => lambda { |mutation|
180
- method_name = H.parse_method_name(mutation.subject.name)
181
- safe_name = H.sanitize_method_name(method_name)
182
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
183
- <<~MINITEST.strip
184
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
185
- # #{mutation.file_path}:#{mutation.line}
186
- def test_exercises_both_branches_of_conditional_in_#{safe_name}
187
- # Test with inputs that make the condition true AND false
188
- result = subject.#{method_name}(input_value)
189
- assert_equal expected, result
190
- end
191
- MINITEST
192
- },
193
- "conditional_branch" => lambda { |mutation|
194
- method_name = H.parse_method_name(mutation.subject.name)
195
- safe_name = H.sanitize_method_name(method_name)
196
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
197
- <<~MINITEST.strip
198
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
199
- # #{mutation.file_path}:#{mutation.line}
200
- def test_exercises_removed_branch_of_conditional_in_#{safe_name}
201
- # Test with inputs that trigger the branch removed by this mutation
202
- result = subject.#{method_name}(input_value)
203
- assert_equal expected, result
204
- end
205
- MINITEST
206
- },
207
- "statement_deletion" => lambda { |mutation|
208
- method_name = H.parse_method_name(mutation.subject.name)
209
- safe_name = H.sanitize_method_name(method_name)
210
- original_line, _mutated_line = H.extract_diff_lines(mutation.diff)
211
- <<~MINITEST.strip
212
- # Mutation: deleted `#{original_line}` in #{mutation.subject.name}
213
- # #{mutation.file_path}:#{mutation.line}
214
- def test_depends_on_side_effect_of_deleted_statement_in_#{safe_name}
215
- # Assert a side effect or return value that changes when this statement is removed
216
- subject.#{method_name}(input_value)
217
- assert_equal expected, observable_side_effect
218
- end
219
- MINITEST
220
- },
221
- "method_body_replacement" => lambda { |mutation|
222
- method_name = H.parse_method_name(mutation.subject.name)
223
- safe_name = H.sanitize_method_name(method_name)
224
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
225
- <<~MINITEST.strip
226
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
227
- # #{mutation.file_path}:#{mutation.line}
228
- def test_verifies_return_value_or_side_effects_of_#{safe_name}
229
- # Assert the method produces a meaningful result, not just nil
230
- result = subject.#{method_name}(input_value)
231
- assert_equal expected, result
232
- end
233
- MINITEST
234
- },
235
- "return_value_removal" => lambda { |mutation|
236
- method_name = H.parse_method_name(mutation.subject.name)
237
- safe_name = H.sanitize_method_name(method_name)
238
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
239
- <<~MINITEST.strip
240
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
241
- # #{mutation.file_path}:#{mutation.line}
242
- def test_uses_return_value_of_#{safe_name}
243
- # Assert the caller depends on the return value, not just side effects
244
- result = subject.#{method_name}(input_value)
245
- assert_equal expected, result
246
- end
247
- MINITEST
248
- },
249
- "method_call_removal" => lambda { |mutation|
250
- method_name = H.parse_method_name(mutation.subject.name)
251
- safe_name = H.sanitize_method_name(method_name)
252
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
253
- <<~MINITEST.strip
254
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
255
- # #{mutation.file_path}:#{mutation.line}
256
- def test_depends_on_return_value_or_side_effect_of_call_in_#{safe_name}
257
- # Assert the method call's effect is observable
258
- result = subject.#{method_name}(input_value)
259
- assert_equal expected, result
260
- end
261
- MINITEST
262
- },
263
- "compound_assignment" => lambda { |mutation|
264
- method_name = H.parse_method_name(mutation.subject.name)
265
- safe_name = H.sanitize_method_name(method_name)
266
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
267
- <<~MINITEST.strip
268
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
269
- # #{mutation.file_path}:#{mutation.line}
270
- def test_verifies_compound_assignment_side_effect_in_#{safe_name}
271
- # Assert the accumulated value after the compound assignment
272
- # The mutation changes the operator, so the final value will differ
273
- subject.#{method_name}(input_value)
274
- assert_equal expected, observable_side_effect
275
- end
276
- MINITEST
277
- },
278
- "nil_replacement" => lambda { |mutation|
279
- method_name = H.parse_method_name(mutation.subject.name)
280
- safe_name = H.sanitize_method_name(method_name)
281
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
282
- <<~MINITEST.strip
283
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
284
- # #{mutation.file_path}:#{mutation.line}
285
- def test_asserts_nil_return_value_from_#{safe_name}
286
- # Assert the method returns nil, not a substituted value
287
- result = subject.#{method_name}(input_value)
288
- assert_nil result
289
- end
290
- MINITEST
291
- },
292
- "superclass_removal" => lambda { |mutation|
293
- method_name = H.parse_method_name(mutation.subject.name)
294
- safe_name = H.sanitize_method_name(method_name)
295
- original_line, _mutated_line = H.extract_diff_lines(mutation.diff)
296
- <<~MINITEST.strip
297
- # Mutation: removed superclass from `#{original_line}` in #{mutation.subject.name}
298
- # #{mutation.file_path}:#{mutation.line}
299
- def test_depends_on_inherited_behavior_in_#{safe_name}
300
- # Assert behavior that comes from the superclass
301
- result = subject.#{method_name}(input_value)
302
- assert_equal expected, result
303
- end
304
- MINITEST
305
- },
306
- "local_variable_assignment" => lambda { |mutation|
307
- method_name = H.parse_method_name(mutation.subject.name)
308
- safe_name = H.sanitize_method_name(method_name)
309
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
310
- <<~MINITEST.strip
311
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
312
- # #{mutation.file_path}:#{mutation.line}
313
- def test_verifies_local_variable_assignment_is_used_in_#{safe_name}
314
- # Assert that the assigned variable is read later, not just the value expression
315
- result = subject.#{method_name}(input_value)
316
- assert_equal expected, result
317
- end
318
- MINITEST
319
- },
320
- "instance_variable_write" => lambda { |mutation|
321
- method_name = H.parse_method_name(mutation.subject.name)
322
- safe_name = H.sanitize_method_name(method_name)
323
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
324
- <<~MINITEST.strip
325
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
326
- # #{mutation.file_path}:#{mutation.line}
327
- def test_verifies_instance_variable_is_set_correctly_in_#{safe_name}
328
- # Assert that the instance variable holds the expected value after the method runs
329
- subject.#{method_name}(input_value)
330
- assert_equal expected, subject.instance_variable_get(:@variable)
331
- end
332
- MINITEST
333
- },
334
- "class_variable_write" => lambda { |mutation|
335
- method_name = H.parse_method_name(mutation.subject.name)
336
- safe_name = H.sanitize_method_name(method_name)
337
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
338
- <<~MINITEST.strip
339
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
340
- # #{mutation.file_path}:#{mutation.line}
341
- def test_verifies_class_variable_shared_state_in_#{safe_name}
342
- # Assert that the class variable holds the expected value and affects shared state
343
- subject.#{method_name}(input_value)
344
- assert_equal expected, klass.class_variable_get(:@@variable)
345
- end
346
- MINITEST
347
- },
348
- "global_variable_write" => lambda { |mutation|
349
- method_name = H.parse_method_name(mutation.subject.name)
350
- safe_name = H.sanitize_method_name(method_name)
351
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
352
- <<~MINITEST.strip
353
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
354
- # #{mutation.file_path}:#{mutation.line}
355
- def test_verifies_global_variable_is_set_correctly_in_#{safe_name}
356
- # Assert that the global variable holds the expected value after the method runs
357
- subject.#{method_name}(input_value)
358
- assert_equal expected, $variable
359
- end
360
- MINITEST
361
- },
362
- "mixin_removal" => lambda { |mutation|
363
- method_name = H.parse_method_name(mutation.subject.name)
364
- safe_name = H.sanitize_method_name(method_name)
365
- original_line, _mutated_line = H.extract_diff_lines(mutation.diff)
366
- <<~MINITEST.strip
367
- # Mutation: removed `#{original_line}` in #{mutation.subject.name}
368
- # #{mutation.file_path}:#{mutation.line}
369
- def test_depends_on_behavior_from_included_module_in_#{safe_name}
370
- # Assert behavior provided by the mixin
371
- result = subject.#{method_name}(input_value)
372
- assert_equal expected, result
373
- end
374
- MINITEST
375
- },
376
- "rescue_removal" => lambda { |mutation|
377
- method_name = H.parse_method_name(mutation.subject.name)
378
- safe_name = H.sanitize_method_name(method_name)
379
- original_line, _mutated_line = H.extract_diff_lines(mutation.diff)
380
- <<~MINITEST.strip
381
- # Mutation: removed `#{original_line}` in #{mutation.subject.name}
382
- # #{mutation.file_path}:#{mutation.line}
383
- def test_verifies_rescue_handler_is_needed_in_#{safe_name}
384
- # Trigger the rescued exception and assert the handler's effect
385
- result = subject.#{method_name}(input_that_raises)
386
- assert_equal expected, result
387
- end
388
- MINITEST
389
- },
390
- "rescue_body_replacement" => lambda { |mutation|
391
- method_name = H.parse_method_name(mutation.subject.name)
392
- safe_name = H.sanitize_method_name(method_name)
393
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
394
- <<~MINITEST.strip
395
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
396
- # #{mutation.file_path}:#{mutation.line}
397
- def test_verifies_rescue_handler_produces_correct_result_in_#{safe_name}
398
- # Trigger the exception and assert the rescue body's return value or side effect
399
- result = subject.#{method_name}(input_that_raises)
400
- assert_equal expected, result
401
- end
402
- MINITEST
403
- },
404
- "inline_rescue" => lambda { |mutation|
405
- method_name = H.parse_method_name(mutation.subject.name)
406
- safe_name = H.sanitize_method_name(method_name)
407
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
408
- <<~MINITEST.strip
409
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
410
- # #{mutation.file_path}:#{mutation.line}
411
- def test_verifies_inline_rescue_fallback_value_in_#{safe_name}
412
- # Trigger the exception and assert the fallback value is correct
413
- result = subject.#{method_name}(input_that_raises)
414
- assert_equal expected, result
415
- end
416
- MINITEST
417
- },
418
- "ensure_removal" => lambda { |mutation|
419
- method_name = H.parse_method_name(mutation.subject.name)
420
- safe_name = H.sanitize_method_name(method_name)
421
- original_line, _mutated_line = H.extract_diff_lines(mutation.diff)
422
- <<~MINITEST.strip
423
- # Mutation: removed ensure block `#{original_line}` in #{mutation.subject.name}
424
- # #{mutation.file_path}:#{mutation.line}
425
- def test_verifies_ensure_cleanup_runs_in_#{safe_name}
426
- # Assert that the cleanup side effect is observable after the method runs
427
- subject.#{method_name}(input_value)
428
- assert_equal expected, observable_cleanup_effect
429
- end
430
- MINITEST
431
- },
432
- "break_statement" => lambda { |mutation|
433
- method_name = H.parse_method_name(mutation.subject.name)
434
- safe_name = H.sanitize_method_name(method_name)
435
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
436
- <<~MINITEST.strip
437
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
438
- # #{mutation.file_path}:#{mutation.line}
439
- def test_verifies_break_exits_loop_correctly_in_#{safe_name}
440
- # Assert the loop exits early and returns the expected value
441
- result = subject.#{method_name}(input_value)
442
- assert_equal expected, result
443
- end
444
- MINITEST
445
- },
446
- "next_statement" => lambda { |mutation|
447
- method_name = H.parse_method_name(mutation.subject.name)
448
- safe_name = H.sanitize_method_name(method_name)
449
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
450
- <<~MINITEST.strip
451
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
452
- # #{mutation.file_path}:#{mutation.line}
453
- def test_verifies_next_skips_iteration_correctly_in_#{safe_name}
454
- # Assert the iteration is skipped and the expected value is yielded
455
- result = subject.#{method_name}(input_value)
456
- assert_equal expected, result
457
- end
458
- MINITEST
459
- },
460
- "redo_statement" => lambda { |mutation|
461
- method_name = H.parse_method_name(mutation.subject.name)
462
- safe_name = H.sanitize_method_name(method_name)
463
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
464
- <<~MINITEST.strip
465
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
466
- # #{mutation.file_path}:#{mutation.line}
467
- def test_verifies_redo_retry_logic_is_necessary_in_#{safe_name}
468
- # Assert the iteration restart changes the outcome
469
- result = subject.#{method_name}(input_value)
470
- assert_equal expected, result
471
- end
472
- MINITEST
473
- },
474
- "bitwise_replacement" => lambda { |mutation|
475
- method_name = H.parse_method_name(mutation.subject.name)
476
- safe_name = H.sanitize_method_name(method_name)
477
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
478
- <<~MINITEST.strip
479
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
480
- # #{mutation.file_path}:#{mutation.line}
481
- def test_verifies_exact_bitwise_result_in_#{safe_name}
482
- # Assert the exact bit-level result to distinguish &, |, and ^ operators
483
- result = subject.#{method_name}(input_value)
484
- assert_equal expected, result
485
- end
486
- MINITEST
487
- },
488
- "bitwise_complement" => lambda { |mutation|
489
- method_name = H.parse_method_name(mutation.subject.name)
490
- safe_name = H.sanitize_method_name(method_name)
491
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
492
- <<~MINITEST.strip
493
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
494
- # #{mutation.file_path}:#{mutation.line}
495
- def test_verifies_bitwise_complement_result_in_#{safe_name}
496
- # Assert the exact complement (~) value, not just sign or magnitude
497
- result = subject.#{method_name}(input_value)
498
- assert_equal expected, result
499
- end
500
- MINITEST
501
- },
502
- "bang_method" => lambda { |mutation|
503
- method_name = H.parse_method_name(mutation.subject.name)
504
- safe_name = H.sanitize_method_name(method_name)
505
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
506
- <<~MINITEST.strip
507
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
508
- # #{mutation.file_path}:#{mutation.line}
509
- def test_verifies_in_place_vs_copy_semantics_matter_in_#{safe_name}
510
- # Assert that the original object is or is not modified
511
- result = subject.#{method_name}(input_value)
512
- assert_equal expected, result
513
- end
514
- MINITEST
515
- },
516
- "zsuper_removal" => lambda { |mutation|
517
- method_name = H.parse_method_name(mutation.subject.name)
518
- safe_name = H.sanitize_method_name(method_name)
519
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
520
- <<~MINITEST.strip
521
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
522
- # #{mutation.file_path}:#{mutation.line}
523
- def test_verifies_inherited_behavior_from_super_in_#{safe_name}
524
- # Assert that the result depends on the superclass implementation
525
- result = subject.#{method_name}(input_value)
526
- assert_equal expected, result
527
- end
528
- MINITEST
529
- },
530
- "explicit_super_mutation" => lambda { |mutation|
531
- method_name = H.parse_method_name(mutation.subject.name)
532
- safe_name = H.sanitize_method_name(method_name)
533
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
534
- <<~MINITEST.strip
535
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
536
- # #{mutation.file_path}:#{mutation.line}
537
- def test_verifies_correct_arguments_passed_to_super_in_#{safe_name}
538
- # Assert the inherited method receives the expected arguments
539
- result = subject.#{method_name}(input_value)
540
- assert_equal expected, result
541
- end
542
- MINITEST
543
- },
544
- "index_to_fetch" => lambda { |mutation|
545
- method_name = H.parse_method_name(mutation.subject.name)
546
- safe_name = H.sanitize_method_name(method_name)
547
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
548
- <<~MINITEST.strip
549
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
550
- # #{mutation.file_path}:#{mutation.line}
551
- def test_distinguishes_bracket_from_fetch_for_missing_keys_in_#{safe_name}
552
- # Access a missing key: [] returns nil, .fetch raises KeyError
553
- assert_raises(KeyError) { subject.#{method_name}(collection_with_missing_key) }
554
- end
555
- MINITEST
556
- },
557
- "index_to_dig" => lambda { |mutation|
558
- method_name = H.parse_method_name(mutation.subject.name)
559
- safe_name = H.sanitize_method_name(method_name)
560
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
561
- <<~MINITEST.strip
562
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
563
- # #{mutation.file_path}:#{mutation.line}
564
- def test_verifies_chained_bracket_access_returns_correct_nested_value_in_#{safe_name}
565
- # Assert the nested lookup produces the expected value
566
- result = subject.#{method_name}(nested_collection)
567
- assert_equal expected, result
568
- end
569
- MINITEST
570
- },
571
- "index_assignment_removal" => lambda { |mutation|
572
- method_name = H.parse_method_name(mutation.subject.name)
573
- safe_name = H.sanitize_method_name(method_name)
574
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
575
- <<~MINITEST.strip
576
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
577
- # #{mutation.file_path}:#{mutation.line}
578
- def test_verifies_bracket_assignment_modifies_collection_in_#{safe_name}
579
- # Assert the collection contains the assigned value at the expected key after the method runs
580
- result = subject.#{method_name}(collection)
581
- assert_equal expected_value, result[expected_key]
582
- end
583
- MINITEST
584
- },
585
- "pattern_matching_guard" => lambda { |mutation|
586
- method_name = H.parse_method_name(mutation.subject.name)
587
- safe_name = H.sanitize_method_name(method_name)
588
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
589
- <<~MINITEST.strip
590
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
591
- # #{mutation.file_path}:#{mutation.line}
592
- def test_verifies_pattern_guard_filters_correctly_in_#{safe_name}
593
- # Test with input that matches the pattern but fails the guard condition
594
- # The guard should prevent matching, routing to a different branch
595
- result = subject.#{method_name}(input_matching_pattern_but_failing_guard)
596
- assert_equal expected, result
597
- end
598
- MINITEST
599
- },
600
- "pattern_matching_alternative" => lambda { |mutation|
601
- method_name = H.parse_method_name(mutation.subject.name)
602
- safe_name = H.sanitize_method_name(method_name)
603
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
604
- <<~MINITEST.strip
605
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
606
- # #{mutation.file_path}:#{mutation.line}
607
- def test_verifies_each_pattern_alternative_is_reachable_in_#{safe_name}
608
- # Test with input that matches only one specific alternative
609
- # Each alternative should have a dedicated test case
610
- result = subject.#{method_name}(input_for_specific_alternative)
611
- assert_equal expected, result
612
- end
613
- MINITEST
614
- },
615
- "collection_return" => lambda { |mutation|
616
- method_name = H.parse_method_name(mutation.subject.name)
617
- safe_name = H.sanitize_method_name(method_name)
618
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
619
- <<~MINITEST.strip
620
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
621
- # #{mutation.file_path}:#{mutation.line}
622
- def test_returns_non_empty_collection_from_#{safe_name}
623
- # Assert the collection has the expected elements, not just non-empty
624
- result = subject.#{method_name}(input_value)
625
- assert_equal expected, result
626
- end
627
- MINITEST
628
- },
629
- "scalar_return" => lambda { |mutation|
630
- method_name = H.parse_method_name(mutation.subject.name)
631
- safe_name = H.sanitize_method_name(method_name)
632
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
633
- <<~MINITEST.strip
634
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
635
- # #{mutation.file_path}:#{mutation.line}
636
- def test_returns_non_zero_non_empty_value_from_#{safe_name}
637
- # Assert the exact scalar value, not just presence or type
638
- result = subject.#{method_name}(input_value)
639
- assert_equal expected, result
640
- end
641
- MINITEST
642
- },
643
- "pattern_matching_array" => lambda { |mutation|
644
- method_name = H.parse_method_name(mutation.subject.name)
645
- safe_name = H.sanitize_method_name(method_name)
646
- original_line, mutated_line = H.extract_diff_lines(mutation.diff)
647
- <<~MINITEST.strip
648
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
649
- # #{mutation.file_path}:#{mutation.line}
650
- def test_verifies_each_array_pattern_element_matters_in_#{safe_name}
651
- # Test with input where changing one element type causes a different match
652
- # Each position in the array pattern should be validated
653
- result = subject.#{method_name}(input_with_wrong_element_type)
654
- assert_equal expected, result
655
- end
656
- MINITEST
657
- }
44
+ "comparison_replacement" => build(test_name: "returns_correct_result_at_comparison_boundary_in") do |method_name|
45
+ <<~BODY
46
+ # Test with values where the original operator and mutated operator
47
+ # produce different results (e.g., equal values for > vs >=)
48
+ result = subject.#{method_name}(boundary_value)
49
+ assert_equal expected, result
50
+ BODY
51
+ end,
52
+ "arithmetic_replacement" => build(test_name: "computes_correct_arithmetic_result_in") do |method_name|
53
+ <<~BODY
54
+ # Assert the exact numeric result, not just truthiness or sign
55
+ result = subject.#{method_name}(input_value)
56
+ assert_equal expected, result
57
+ BODY
58
+ end,
59
+ "boolean_operator_replacement" => build(test_name: "returns_correct_result_when_one_condition_differs_in") do |method_name|
60
+ <<~BODY
61
+ # Use inputs where only one operand is truthy to distinguish && from ||
62
+ result = subject.#{method_name}(input_value)
63
+ assert_equal expected, result
64
+ BODY
65
+ end,
66
+ "boolean_literal_replacement" => build(test_name: "returns_expected_boolean_value_from") do |method_name|
67
+ <<~BODY
68
+ # Assert the exact true/false/nil value, not just truthiness
69
+ result = subject.#{method_name}(input_value)
70
+ assert_equal expected, result
71
+ BODY
72
+ end,
73
+ "negation_insertion" => build(test_name: "returns_correct_boolean_from_predicate_in") do |method_name|
74
+ <<~BODY
75
+ # Assert the exact true/false result, not just truthiness
76
+ result = subject.#{method_name}(input_value)
77
+ assert_includes [true, false], result
78
+ BODY
79
+ end,
80
+ "integer_literal" => build(test_name: "returns_exact_integer_value_from") do |method_name|
81
+ <<~BODY
82
+ # Assert the exact numeric value, not just > 0 or truthy
83
+ result = subject.#{method_name}(input_value)
84
+ assert_equal expected, result
85
+ BODY
86
+ end,
87
+ "float_literal" => build(test_name: "returns_exact_float_value_from") do |method_name|
88
+ <<~BODY
89
+ # Assert the exact floating-point result
90
+ result = subject.#{method_name}(input_value)
91
+ assert_in_delta expected, result
92
+ BODY
93
+ end,
94
+ "string_literal" => build(test_name: "returns_exact_string_content_from") do |method_name|
95
+ <<~BODY
96
+ # Assert the exact string value, not just presence or non-empty
97
+ result = subject.#{method_name}(input_value)
98
+ assert_equal expected, result
99
+ BODY
100
+ end,
101
+ "symbol_literal" => build(test_name: "returns_exact_symbol_from") do |method_name|
102
+ <<~BODY
103
+ # Assert the exact symbol value, not just that it is a Symbol
104
+ result = subject.#{method_name}(input_value)
105
+ assert_equal expected, result
106
+ BODY
107
+ end,
108
+ "array_literal" => build(test_name: "returns_expected_array_contents_from") do |method_name|
109
+ <<~BODY
110
+ # Assert the exact array elements, not just non-empty or truthy
111
+ result = subject.#{method_name}(input_value)
112
+ assert_equal expected, result
113
+ BODY
114
+ end,
115
+ "hash_literal" => build(test_name: "returns_expected_hash_contents_from") do |method_name|
116
+ <<~BODY
117
+ # Assert the exact keys and values, not just non-empty or truthy
118
+ result = subject.#{method_name}(input_value)
119
+ assert_equal expected, result
120
+ BODY
121
+ end,
122
+ "collection_replacement" => build(test_name: "uses_return_value_of_collection_operation_in") do |method_name|
123
+ <<~BODY
124
+ # Assert the return value of the collection method, not just side effects
125
+ result = subject.#{method_name}(input_value)
126
+ assert_equal expected, result
127
+ BODY
128
+ end,
129
+ "conditional_negation" => build(test_name: "exercises_both_branches_of_conditional_in") do |method_name|
130
+ <<~BODY
131
+ # Test with inputs that make the condition true AND false
132
+ result = subject.#{method_name}(input_value)
133
+ assert_equal expected, result
134
+ BODY
135
+ end,
136
+ "conditional_branch" => build(test_name: "exercises_removed_branch_of_conditional_in") do |method_name|
137
+ <<~BODY
138
+ # Test with inputs that trigger the branch removed by this mutation
139
+ result = subject.#{method_name}(input_value)
140
+ assert_equal expected, result
141
+ BODY
142
+ end,
143
+ "statement_deletion" => build(test_name: "depends_on_side_effect_of_deleted_statement_in", action: :deleted) do |method_name|
144
+ <<~BODY
145
+ # Assert a side effect or return value that changes when this statement is removed
146
+ subject.#{method_name}(input_value)
147
+ assert_equal expected, observable_side_effect
148
+ BODY
149
+ end,
150
+ "method_body_replacement" => build(test_name: "verifies_return_value_or_side_effects_of") do |method_name|
151
+ <<~BODY
152
+ # Assert the method produces a meaningful result, not just nil
153
+ result = subject.#{method_name}(input_value)
154
+ assert_equal expected, result
155
+ BODY
156
+ end,
157
+ "return_value_removal" => build(test_name: "uses_return_value_of") do |method_name|
158
+ <<~BODY
159
+ # Assert the caller depends on the return value, not just side effects
160
+ result = subject.#{method_name}(input_value)
161
+ assert_equal expected, result
162
+ BODY
163
+ end,
164
+ "method_call_removal" => build(test_name: "depends_on_return_value_or_side_effect_of_call_in") do |method_name|
165
+ <<~BODY
166
+ # Assert the method call's effect is observable
167
+ result = subject.#{method_name}(input_value)
168
+ assert_equal expected, result
169
+ BODY
170
+ end,
171
+ "compound_assignment" => build(test_name: "verifies_compound_assignment_side_effect_in") do |method_name|
172
+ <<~BODY
173
+ # Assert the accumulated value after the compound assignment
174
+ # The mutation changes the operator, so the final value will differ
175
+ subject.#{method_name}(input_value)
176
+ assert_equal expected, observable_side_effect
177
+ BODY
178
+ end,
179
+ "nil_replacement" => build(test_name: "asserts_nil_return_value_from") do |method_name|
180
+ <<~BODY
181
+ # Assert the method returns nil, not a substituted value
182
+ result = subject.#{method_name}(input_value)
183
+ assert_nil result
184
+ BODY
185
+ end,
186
+ "superclass_removal" => build(test_name: "depends_on_inherited_behavior_in", action: :removed_superclass) do |method_name|
187
+ <<~BODY
188
+ # Assert behavior that comes from the superclass
189
+ result = subject.#{method_name}(input_value)
190
+ assert_equal expected, result
191
+ BODY
192
+ end,
193
+ "local_variable_assignment" => build(test_name: "verifies_local_variable_assignment_is_used_in") do |method_name|
194
+ <<~BODY
195
+ # Assert that the assigned variable is read later, not just the value expression
196
+ result = subject.#{method_name}(input_value)
197
+ assert_equal expected, result
198
+ BODY
199
+ end,
200
+ "instance_variable_write" => build(test_name: "verifies_instance_variable_is_set_correctly_in") do |method_name|
201
+ <<~BODY
202
+ # Assert that the instance variable holds the expected value after the method runs
203
+ subject.#{method_name}(input_value)
204
+ assert_equal expected, subject.instance_variable_get(:@variable)
205
+ BODY
206
+ end,
207
+ "class_variable_write" => build(test_name: "verifies_class_variable_shared_state_in") do |method_name|
208
+ <<~BODY
209
+ # Assert that the class variable holds the expected value and affects shared state
210
+ subject.#{method_name}(input_value)
211
+ assert_equal expected, klass.class_variable_get(:@@variable)
212
+ BODY
213
+ end,
214
+ "global_variable_write" => build(test_name: "verifies_global_variable_is_set_correctly_in") do |method_name|
215
+ <<~BODY
216
+ # Assert that the global variable holds the expected value after the method runs
217
+ subject.#{method_name}(input_value)
218
+ assert_equal expected, $variable
219
+ BODY
220
+ end,
221
+ "mixin_removal" => build(test_name: "depends_on_behavior_from_included_module_in", action: :removed) do |method_name|
222
+ <<~BODY
223
+ # Assert behavior provided by the mixin
224
+ result = subject.#{method_name}(input_value)
225
+ assert_equal expected, result
226
+ BODY
227
+ end,
228
+ "rescue_removal" => build(test_name: "verifies_rescue_handler_is_needed_in", action: :removed) do |method_name|
229
+ <<~BODY
230
+ # Trigger the rescued exception and assert the handler's effect
231
+ result = subject.#{method_name}(input_that_raises)
232
+ assert_equal expected, result
233
+ BODY
234
+ end,
235
+ "rescue_body_replacement" => build(test_name: "verifies_rescue_handler_produces_correct_result_in") do |method_name|
236
+ <<~BODY
237
+ # Trigger the exception and assert the rescue body's return value or side effect
238
+ result = subject.#{method_name}(input_that_raises)
239
+ assert_equal expected, result
240
+ BODY
241
+ end,
242
+ "inline_rescue" => build(test_name: "verifies_inline_rescue_fallback_value_in") do |method_name|
243
+ <<~BODY
244
+ # Trigger the exception and assert the fallback value is correct
245
+ result = subject.#{method_name}(input_that_raises)
246
+ assert_equal expected, result
247
+ BODY
248
+ end,
249
+ "ensure_removal" => build(test_name: "verifies_ensure_cleanup_runs_in", action: :removed_ensure) do |method_name|
250
+ <<~BODY
251
+ # Assert that the cleanup side effect is observable after the method runs
252
+ subject.#{method_name}(input_value)
253
+ assert_equal expected, observable_cleanup_effect
254
+ BODY
255
+ end,
256
+ "break_statement" => build(test_name: "verifies_break_exits_loop_correctly_in") do |method_name|
257
+ <<~BODY
258
+ # Assert the loop exits early and returns the expected value
259
+ result = subject.#{method_name}(input_value)
260
+ assert_equal expected, result
261
+ BODY
262
+ end,
263
+ "next_statement" => build(test_name: "verifies_next_skips_iteration_correctly_in") do |method_name|
264
+ <<~BODY
265
+ # Assert the iteration is skipped and the expected value is yielded
266
+ result = subject.#{method_name}(input_value)
267
+ assert_equal expected, result
268
+ BODY
269
+ end,
270
+ "redo_statement" => build(test_name: "verifies_redo_retry_logic_is_necessary_in") do |method_name|
271
+ <<~BODY
272
+ # Assert the iteration restart changes the outcome
273
+ result = subject.#{method_name}(input_value)
274
+ assert_equal expected, result
275
+ BODY
276
+ end,
277
+ "bitwise_replacement" => build(test_name: "verifies_exact_bitwise_result_in") do |method_name|
278
+ <<~BODY
279
+ # Assert the exact bit-level result to distinguish &, |, and ^ operators
280
+ result = subject.#{method_name}(input_value)
281
+ assert_equal expected, result
282
+ BODY
283
+ end,
284
+ "bitwise_complement" => build(test_name: "verifies_bitwise_complement_result_in") do |method_name|
285
+ <<~BODY
286
+ # Assert the exact complement (~) value, not just sign or magnitude
287
+ result = subject.#{method_name}(input_value)
288
+ assert_equal expected, result
289
+ BODY
290
+ end,
291
+ "bang_method" => build(test_name: "verifies_in_place_vs_copy_semantics_matter_in") do |method_name|
292
+ <<~BODY
293
+ # Assert that the original object is or is not modified
294
+ result = subject.#{method_name}(input_value)
295
+ assert_equal expected, result
296
+ BODY
297
+ end,
298
+ "zsuper_removal" => build(test_name: "verifies_inherited_behavior_from_super_in") do |method_name|
299
+ <<~BODY
300
+ # Assert that the result depends on the superclass implementation
301
+ result = subject.#{method_name}(input_value)
302
+ assert_equal expected, result
303
+ BODY
304
+ end,
305
+ "explicit_super_mutation" => build(test_name: "verifies_correct_arguments_passed_to_super_in") do |method_name|
306
+ <<~BODY
307
+ # Assert the inherited method receives the expected arguments
308
+ result = subject.#{method_name}(input_value)
309
+ assert_equal expected, result
310
+ BODY
311
+ end,
312
+ "index_to_fetch" => build(test_name: "distinguishes_bracket_from_fetch_for_missing_keys_in") do |method_name|
313
+ <<~BODY
314
+ # Access a missing key: [] returns nil, .fetch raises KeyError
315
+ assert_raises(KeyError) { subject.#{method_name}(collection_with_missing_key) }
316
+ BODY
317
+ end,
318
+ "index_to_dig" => build(test_name: "verifies_chained_bracket_access_returns_correct_nested_value_in") do |method_name|
319
+ <<~BODY
320
+ # Assert the nested lookup produces the expected value
321
+ result = subject.#{method_name}(nested_collection)
322
+ assert_equal expected, result
323
+ BODY
324
+ end,
325
+ "index_assignment_removal" => build(test_name: "verifies_bracket_assignment_modifies_collection_in") do |method_name|
326
+ <<~BODY
327
+ # Assert the collection contains the assigned value at the expected key after the method runs
328
+ result = subject.#{method_name}(collection)
329
+ assert_equal expected_value, result[expected_key]
330
+ BODY
331
+ end,
332
+ "pattern_matching_guard" => build(test_name: "verifies_pattern_guard_filters_correctly_in") do |method_name|
333
+ <<~BODY
334
+ # Test with input that matches the pattern but fails the guard condition
335
+ # The guard should prevent matching, routing to a different branch
336
+ result = subject.#{method_name}(input_matching_pattern_but_failing_guard)
337
+ assert_equal expected, result
338
+ BODY
339
+ end,
340
+ "pattern_matching_alternative" => build(test_name: "verifies_each_pattern_alternative_is_reachable_in") do |method_name|
341
+ <<~BODY
342
+ # Test with input that matches only one specific alternative
343
+ # Each alternative should have a dedicated test case
344
+ result = subject.#{method_name}(input_for_specific_alternative)
345
+ assert_equal expected, result
346
+ BODY
347
+ end,
348
+ "collection_return" => build(test_name: "returns_non_empty_collection_from") do |method_name|
349
+ <<~BODY
350
+ # Assert the collection has the expected elements, not just non-empty
351
+ result = subject.#{method_name}(input_value)
352
+ assert_equal expected, result
353
+ BODY
354
+ end,
355
+ "scalar_return" => build(test_name: "returns_non_zero_non_empty_value_from") do |method_name|
356
+ <<~BODY
357
+ # Assert the exact scalar value, not just presence or type
358
+ result = subject.#{method_name}(input_value)
359
+ assert_equal expected, result
360
+ BODY
361
+ end,
362
+ "pattern_matching_array" => build(test_name: "verifies_each_array_pattern_element_matters_in") do |method_name|
363
+ <<~BODY
364
+ # Test with input where changing one element type causes a different match
365
+ # Each position in the array pattern should be validated
366
+ result = subject.#{method_name}(input_with_wrong_element_type)
367
+ assert_equal expected, result
368
+ BODY
369
+ end
658
370
  }.freeze
659
371
  end