evilution 0.26.0 → 0.28.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 (159) hide show
  1. checksums.yaml +4 -4
  2. data/.beads/interactions.jsonl +23 -0
  3. data/.rubocop_todo.yml +6 -0
  4. data/CHANGELOG.md +54 -0
  5. data/README.md +76 -3
  6. data/lib/evilution/baseline.rb +5 -4
  7. data/lib/evilution/cache.rb +2 -0
  8. data/lib/evilution/child_output.rb +24 -0
  9. data/lib/evilution/cli/commands/run.rb +9 -0
  10. data/lib/evilution/cli/commands/version.rb +2 -0
  11. data/lib/evilution/cli/parser/options_builder.rb +23 -2
  12. data/lib/evilution/compare/diff_extractor/evilution.rb +22 -0
  13. data/lib/evilution/compare/diff_extractor/mutant.rb +30 -0
  14. data/lib/evilution/compare/diff_extractor.rb +6 -0
  15. data/lib/evilution/compare/fingerprint.rb +15 -72
  16. data/lib/evilution/compare/line_normalizer.rb +72 -0
  17. data/lib/evilution/compare/normalizer.rb +17 -4
  18. data/lib/evilution/config/builders/spec_resolver.rb +15 -0
  19. data/lib/evilution/config/builders/spec_selector.rb +16 -0
  20. data/lib/evilution/config/builders.rb +4 -0
  21. data/lib/evilution/config/env_loader.rb +12 -0
  22. data/lib/evilution/config/file_loader.rb +22 -0
  23. data/lib/evilution/config/sources.rb +14 -0
  24. data/lib/evilution/config/validators/base.rb +37 -0
  25. data/lib/evilution/config/validators/example_targeting_cache.rb +37 -0
  26. data/lib/evilution/config/validators/example_targeting_fallback.rb +22 -0
  27. data/lib/evilution/config/validators/fail_fast.rb +11 -0
  28. data/lib/evilution/config/validators/hooks.rb +12 -0
  29. data/lib/evilution/config/validators/ignore_patterns.rb +16 -0
  30. data/lib/evilution/config/validators/integration.rb +11 -0
  31. data/lib/evilution/config/validators/isolation.rb +19 -0
  32. data/lib/evilution/config/validators/jobs.rb +9 -0
  33. data/lib/evilution/config/validators/preload.rb +13 -0
  34. data/lib/evilution/config/validators/profile.rb +11 -0
  35. data/lib/evilution/config/validators/spec_mappings.rb +56 -0
  36. data/lib/evilution/config/validators/spec_pattern.rb +12 -0
  37. data/lib/evilution/config/validators.rb +4 -0
  38. data/lib/evilution/config.rb +93 -266
  39. data/lib/evilution/feedback/detector.rb +15 -0
  40. data/lib/evilution/feedback/messages.rb +42 -0
  41. data/lib/evilution/feedback.rb +5 -0
  42. data/lib/evilution/integration/crash_detector.rb +2 -2
  43. data/lib/evilution/integration/loading/source_evaluator.rb +6 -2
  44. data/lib/evilution/integration/minitest_crash_detector.rb +2 -2
  45. data/lib/evilution/integration/rspec/baseline_runner.rb +16 -0
  46. data/lib/evilution/integration/rspec/crash_detector_lifecycle.rb +17 -0
  47. data/lib/evilution/integration/rspec/example_filter_applier.rb +21 -0
  48. data/lib/evilution/integration/rspec/framework_loader.rb +28 -0
  49. data/lib/evilution/integration/rspec/result_builder.rb +40 -0
  50. data/lib/evilution/integration/rspec/state_guard/example_groups_constants.rb +28 -0
  51. data/lib/evilution/integration/rspec/state_guard/internals.rb +19 -0
  52. data/lib/evilution/integration/rspec/state_guard/object_space_example_groups.rb +43 -0
  53. data/lib/evilution/integration/rspec/state_guard/reporter_arrays.rb +32 -0
  54. data/lib/evilution/integration/rspec/state_guard/world_example_groups.rb +20 -0
  55. data/lib/evilution/integration/rspec/state_guard/world_filtered_examples.rb +20 -0
  56. data/lib/evilution/integration/rspec/state_guard/world_sources_by_path.rb +20 -0
  57. data/lib/evilution/integration/rspec/state_guard.rb +40 -0
  58. data/lib/evilution/integration/rspec/test_file_resolver.rb +30 -0
  59. data/lib/evilution/integration/rspec/unresolved_spec_warner.rb +18 -0
  60. data/lib/evilution/integration/rspec.rb +61 -232
  61. data/lib/evilution/isolation/fork.rb +23 -13
  62. data/lib/evilution/isolation/in_process.rb +10 -6
  63. data/lib/evilution/mcp/info_tool/actions/base.rb +22 -0
  64. data/lib/evilution/mcp/info_tool/actions/environment.rb +42 -0
  65. data/lib/evilution/mcp/info_tool/actions/feedback.rb +16 -0
  66. data/lib/evilution/mcp/info_tool/actions/statuses.rb +10 -0
  67. data/lib/evilution/mcp/info_tool/actions/subjects.rb +47 -0
  68. data/lib/evilution/mcp/info_tool/actions/tests.rb +60 -0
  69. data/lib/evilution/mcp/info_tool/actions.rb +16 -0
  70. data/lib/evilution/mcp/info_tool/config_factory.rb +24 -0
  71. data/lib/evilution/mcp/info_tool/error_mapper.rb +15 -0
  72. data/lib/evilution/mcp/info_tool/request_parser.rb +34 -0
  73. data/lib/evilution/mcp/info_tool/response_formatter.rb +24 -0
  74. data/lib/evilution/mcp/info_tool/status_glossary.rb +75 -0
  75. data/lib/evilution/mcp/info_tool.rb +43 -263
  76. data/lib/evilution/mcp/mutate_tool/error_payload.rb +8 -1
  77. data/lib/evilution/mcp/mutate_tool/progress_streamer.rb +5 -1
  78. data/lib/evilution/mcp/mutate_tool/report_trimmer.rb +13 -1
  79. data/lib/evilution/mcp/mutate_tool.rb +5 -2
  80. data/lib/evilution/mcp/session_tool.rb +0 -2
  81. data/lib/evilution/mutation.rb +47 -27
  82. data/lib/evilution/mutator/base.rb +8 -8
  83. data/lib/evilution/mutator/operator/block_removal.rb +1 -1
  84. data/lib/evilution/mutator/operator/method_body_replacement.rb +18 -2
  85. data/lib/evilution/mutator/operator/predicate_to_nil.rb +20 -0
  86. data/lib/evilution/mutator/registry.rb +20 -0
  87. data/lib/evilution/parallel/work_queue/channel/frame.rb +25 -0
  88. data/lib/evilution/parallel/work_queue/channel.rb +23 -0
  89. data/lib/evilution/parallel/work_queue/collection_state.rb +14 -0
  90. data/lib/evilution/parallel/work_queue/dispatcher.rb +133 -0
  91. data/lib/evilution/parallel/work_queue/validators/optional_positive_int.rb +11 -0
  92. data/lib/evilution/parallel/work_queue/validators/optional_positive_number.rb +11 -0
  93. data/lib/evilution/parallel/work_queue/validators/positive_int.rb +11 -0
  94. data/lib/evilution/parallel/work_queue/validators.rb +6 -0
  95. data/lib/evilution/parallel/work_queue/worker/loop.rb +45 -0
  96. data/lib/evilution/parallel/work_queue/worker.rb +114 -0
  97. data/lib/evilution/parallel/work_queue/worker_stat.rb +17 -0
  98. data/lib/evilution/parallel/work_queue.rb +42 -327
  99. data/lib/evilution/process_cleanup.rb +19 -0
  100. data/lib/evilution/reporter/cli/item_formatters/coverage_gap.rb +18 -0
  101. data/lib/evilution/reporter/cli/item_formatters/disabled.rb +9 -0
  102. data/lib/evilution/reporter/cli/item_formatters/error.rb +14 -0
  103. data/lib/evilution/reporter/cli/item_formatters/result_location.rb +10 -0
  104. data/lib/evilution/reporter/cli/item_formatters.rb +6 -0
  105. data/lib/evilution/reporter/cli/line_formatters/duration.rb +9 -0
  106. data/lib/evilution/reporter/cli/line_formatters/efficiency.rb +18 -0
  107. data/lib/evilution/reporter/cli/line_formatters/feedback_footer.rb +13 -0
  108. data/lib/evilution/reporter/cli/line_formatters/header.rb +10 -0
  109. data/lib/evilution/reporter/cli/line_formatters/mutations.rb +16 -0
  110. data/lib/evilution/reporter/cli/line_formatters/peak_memory.rb +12 -0
  111. data/lib/evilution/reporter/cli/line_formatters/result_line.rb +20 -0
  112. data/lib/evilution/reporter/cli/line_formatters/score.rb +14 -0
  113. data/lib/evilution/reporter/cli/line_formatters/truncation_notice.rb +11 -0
  114. data/lib/evilution/reporter/cli/line_formatters.rb +6 -0
  115. data/lib/evilution/reporter/cli/metrics_block.rb +26 -0
  116. data/lib/evilution/reporter/cli/pct.rb +9 -0
  117. data/lib/evilution/reporter/cli/section.rb +13 -0
  118. data/lib/evilution/reporter/cli/section_renderer.rb +15 -0
  119. data/lib/evilution/reporter/cli/trailer.rb +22 -0
  120. data/lib/evilution/reporter/cli.rb +79 -162
  121. data/lib/evilution/reporter/html/baseline_keys.rb +1 -1
  122. data/lib/evilution/reporter/html/diff_formatter.rb +1 -1
  123. data/lib/evilution/reporter/html/escape.rb +1 -1
  124. data/lib/evilution/reporter/html/section.rb +1 -1
  125. data/lib/evilution/reporter/html/sections.rb +4 -2
  126. data/lib/evilution/reporter/html/stylesheet.rb +1 -1
  127. data/lib/evilution/reporter/html.rb +8 -3
  128. data/lib/evilution/reporter/suggestion/registry.rb +1 -5
  129. data/lib/evilution/reporter/suggestion/templates/generic.rb +1 -1
  130. data/lib/evilution/reporter/suggestion/templates/minitest.rb +349 -643
  131. data/lib/evilution/reporter/suggestion/templates/rspec.rb +351 -598
  132. data/lib/evilution/reporter/suggestion/templates.rb +6 -0
  133. data/lib/evilution/result/error_info.rb +20 -0
  134. data/lib/evilution/result/memory_stats.rb +20 -0
  135. data/lib/evilution/result/mutation_result.rb +30 -14
  136. data/lib/evilution/runner/baseline_runner.rb +1 -2
  137. data/lib/evilution/runner/diagnostics.rb +1 -2
  138. data/lib/evilution/runner/isolation_resolver.rb +10 -4
  139. data/lib/evilution/runner/mutation_executor/mutation_runner.rb +30 -0
  140. data/lib/evilution/runner/mutation_executor/neutralization_pipeline.rb +15 -0
  141. data/lib/evilution/runner/mutation_executor/neutralizer/baseline_failed.rb +39 -0
  142. data/lib/evilution/runner/mutation_executor/neutralizer/infra_error.rb +68 -0
  143. data/lib/evilution/runner/mutation_executor/neutralizer.rb +11 -0
  144. data/lib/evilution/runner/mutation_executor/result_cache.rb +67 -0
  145. data/lib/evilution/runner/mutation_executor/result_notifier.rb +46 -0
  146. data/lib/evilution/runner/mutation_executor/result_packer.rb +41 -0
  147. data/lib/evilution/runner/mutation_executor/strategy/parallel.rb +78 -0
  148. data/lib/evilution/runner/mutation_executor/strategy/sequential.rb +32 -0
  149. data/lib/evilution/runner/mutation_executor/strategy.rb +11 -0
  150. data/lib/evilution/runner/mutation_executor.rb +53 -292
  151. data/lib/evilution/runner/mutation_planner.rb +1 -2
  152. data/lib/evilution/runner/report_publisher.rb +1 -2
  153. data/lib/evilution/runner/subject_pipeline.rb +1 -2
  154. data/lib/evilution/runner.rb +53 -30
  155. data/lib/evilution/version.rb +1 -1
  156. data/lib/evilution.rb +1 -0
  157. data/script/memory_check +3 -1
  158. metadata +125 -3
  159. data/lib/evilution/reporter/html/namespace.rb +0 -11
@@ -1,659 +1,365 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../registry"
3
+ require_relative "../templates"
4
4
  require_relative "../diff_helpers"
5
5
 
6
6
  module Evilution::Reporter::Suggestion::Templates::Minitest
7
7
  H = Evilution::Reporter::Suggestion::DiffHelpers
8
8
 
9
- 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|
9
+ def self.format_header(action, original, mutated, subject_name)
10
+ case action
11
+ when :changed then "changed `#{original}` to `#{mutated}` in #{subject_name}"
12
+ when :deleted then "deleted `#{original}` in #{subject_name}"
13
+ when :removed then "removed `#{original}` in #{subject_name}"
14
+ when :removed_superclass then "removed superclass from `#{original}` in #{subject_name}"
15
+ when :removed_ensure then "removed ensure block `#{original}` in #{subject_name}"
16
+ end
17
+ end
18
+
19
+ def self.build(test_name:, action: :changed, &body_block)
20
+ lambda do |mutation|
644
21
  method_name = H.parse_method_name(mutation.subject.name)
645
22
  safe_name = H.sanitize_method_name(method_name)
646
23
  original_line, mutated_line = H.extract_diff_lines(mutation.diff)
24
+ body = body_block.call(method_name)
25
+ indented = body.lines.map { |l| " #{l}" }.join.chomp
26
+
647
27
  <<~MINITEST.strip
648
- # Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
28
+ # Mutation: #{format_header(action, original_line, mutated_line, mutation.subject.name)}
649
29
  # #{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
30
+ def test_#{test_name}_#{safe_name}
31
+ #{indented}
655
32
  end
656
33
  MINITEST
657
- }
34
+ end
35
+ end
36
+
37
+ MINITEST_ENTRIES = {
38
+ "comparison_replacement" => build(test_name: "returns_correct_result_at_comparison_boundary_in") do |method_name|
39
+ <<~BODY
40
+ # Test with values where the original operator and mutated operator
41
+ # produce different results (e.g., equal values for > vs >=)
42
+ result = subject.#{method_name}(boundary_value)
43
+ assert_equal expected, result
44
+ BODY
45
+ end,
46
+ "arithmetic_replacement" => build(test_name: "computes_correct_arithmetic_result_in") do |method_name|
47
+ <<~BODY
48
+ # Assert the exact numeric result, not just truthiness or sign
49
+ result = subject.#{method_name}(input_value)
50
+ assert_equal expected, result
51
+ BODY
52
+ end,
53
+ "boolean_operator_replacement" => build(test_name: "returns_correct_result_when_one_condition_differs_in") do |method_name|
54
+ <<~BODY
55
+ # Use inputs where only one operand is truthy to distinguish && from ||
56
+ result = subject.#{method_name}(input_value)
57
+ assert_equal expected, result
58
+ BODY
59
+ end,
60
+ "boolean_literal_replacement" => build(test_name: "returns_expected_boolean_value_from") do |method_name|
61
+ <<~BODY
62
+ # Assert the exact true/false/nil value, not just truthiness
63
+ result = subject.#{method_name}(input_value)
64
+ assert_equal expected, result
65
+ BODY
66
+ end,
67
+ "negation_insertion" => build(test_name: "returns_correct_boolean_from_predicate_in") do |method_name|
68
+ <<~BODY
69
+ # Assert the exact true/false result, not just truthiness
70
+ result = subject.#{method_name}(input_value)
71
+ assert_includes [true, false], result
72
+ BODY
73
+ end,
74
+ "integer_literal" => build(test_name: "returns_exact_integer_value_from") do |method_name|
75
+ <<~BODY
76
+ # Assert the exact numeric value, not just > 0 or truthy
77
+ result = subject.#{method_name}(input_value)
78
+ assert_equal expected, result
79
+ BODY
80
+ end,
81
+ "float_literal" => build(test_name: "returns_exact_float_value_from") do |method_name|
82
+ <<~BODY
83
+ # Assert the exact floating-point result
84
+ result = subject.#{method_name}(input_value)
85
+ assert_in_delta expected, result
86
+ BODY
87
+ end,
88
+ "string_literal" => build(test_name: "returns_exact_string_content_from") do |method_name|
89
+ <<~BODY
90
+ # Assert the exact string value, not just presence or non-empty
91
+ result = subject.#{method_name}(input_value)
92
+ assert_equal expected, result
93
+ BODY
94
+ end,
95
+ "symbol_literal" => build(test_name: "returns_exact_symbol_from") do |method_name|
96
+ <<~BODY
97
+ # Assert the exact symbol value, not just that it is a Symbol
98
+ result = subject.#{method_name}(input_value)
99
+ assert_equal expected, result
100
+ BODY
101
+ end,
102
+ "array_literal" => build(test_name: "returns_expected_array_contents_from") do |method_name|
103
+ <<~BODY
104
+ # Assert the exact array elements, not just non-empty or truthy
105
+ result = subject.#{method_name}(input_value)
106
+ assert_equal expected, result
107
+ BODY
108
+ end,
109
+ "hash_literal" => build(test_name: "returns_expected_hash_contents_from") do |method_name|
110
+ <<~BODY
111
+ # Assert the exact keys and values, not just non-empty or truthy
112
+ result = subject.#{method_name}(input_value)
113
+ assert_equal expected, result
114
+ BODY
115
+ end,
116
+ "collection_replacement" => build(test_name: "uses_return_value_of_collection_operation_in") do |method_name|
117
+ <<~BODY
118
+ # Assert the return value of the collection method, not just side effects
119
+ result = subject.#{method_name}(input_value)
120
+ assert_equal expected, result
121
+ BODY
122
+ end,
123
+ "conditional_negation" => build(test_name: "exercises_both_branches_of_conditional_in") do |method_name|
124
+ <<~BODY
125
+ # Test with inputs that make the condition true AND false
126
+ result = subject.#{method_name}(input_value)
127
+ assert_equal expected, result
128
+ BODY
129
+ end,
130
+ "conditional_branch" => build(test_name: "exercises_removed_branch_of_conditional_in") do |method_name|
131
+ <<~BODY
132
+ # Test with inputs that trigger the branch removed by this mutation
133
+ result = subject.#{method_name}(input_value)
134
+ assert_equal expected, result
135
+ BODY
136
+ end,
137
+ "statement_deletion" => build(test_name: "depends_on_side_effect_of_deleted_statement_in", action: :deleted) do |method_name|
138
+ <<~BODY
139
+ # Assert a side effect or return value that changes when this statement is removed
140
+ subject.#{method_name}(input_value)
141
+ assert_equal expected, observable_side_effect
142
+ BODY
143
+ end,
144
+ "method_body_replacement" => build(test_name: "verifies_return_value_or_side_effects_of") do |method_name|
145
+ <<~BODY
146
+ # Assert the method produces a meaningful result, not just nil
147
+ result = subject.#{method_name}(input_value)
148
+ assert_equal expected, result
149
+ BODY
150
+ end,
151
+ "return_value_removal" => build(test_name: "uses_return_value_of") do |method_name|
152
+ <<~BODY
153
+ # Assert the caller depends on the return value, not just side effects
154
+ result = subject.#{method_name}(input_value)
155
+ assert_equal expected, result
156
+ BODY
157
+ end,
158
+ "method_call_removal" => build(test_name: "depends_on_return_value_or_side_effect_of_call_in") do |method_name|
159
+ <<~BODY
160
+ # Assert the method call's effect is observable
161
+ result = subject.#{method_name}(input_value)
162
+ assert_equal expected, result
163
+ BODY
164
+ end,
165
+ "compound_assignment" => build(test_name: "verifies_compound_assignment_side_effect_in") do |method_name|
166
+ <<~BODY
167
+ # Assert the accumulated value after the compound assignment
168
+ # The mutation changes the operator, so the final value will differ
169
+ subject.#{method_name}(input_value)
170
+ assert_equal expected, observable_side_effect
171
+ BODY
172
+ end,
173
+ "nil_replacement" => build(test_name: "asserts_nil_return_value_from") do |method_name|
174
+ <<~BODY
175
+ # Assert the method returns nil, not a substituted value
176
+ result = subject.#{method_name}(input_value)
177
+ assert_nil result
178
+ BODY
179
+ end,
180
+ "superclass_removal" => build(test_name: "depends_on_inherited_behavior_in", action: :removed_superclass) do |method_name|
181
+ <<~BODY
182
+ # Assert behavior that comes from the superclass
183
+ result = subject.#{method_name}(input_value)
184
+ assert_equal expected, result
185
+ BODY
186
+ end,
187
+ "local_variable_assignment" => build(test_name: "verifies_local_variable_assignment_is_used_in") do |method_name|
188
+ <<~BODY
189
+ # Assert that the assigned variable is read later, not just the value expression
190
+ result = subject.#{method_name}(input_value)
191
+ assert_equal expected, result
192
+ BODY
193
+ end,
194
+ "instance_variable_write" => build(test_name: "verifies_instance_variable_is_set_correctly_in") do |method_name|
195
+ <<~BODY
196
+ # Assert that the instance variable holds the expected value after the method runs
197
+ subject.#{method_name}(input_value)
198
+ assert_equal expected, subject.instance_variable_get(:@variable)
199
+ BODY
200
+ end,
201
+ "class_variable_write" => build(test_name: "verifies_class_variable_shared_state_in") do |method_name|
202
+ <<~BODY
203
+ # Assert that the class variable holds the expected value and affects shared state
204
+ subject.#{method_name}(input_value)
205
+ assert_equal expected, klass.class_variable_get(:@@variable)
206
+ BODY
207
+ end,
208
+ "global_variable_write" => build(test_name: "verifies_global_variable_is_set_correctly_in") do |method_name|
209
+ <<~BODY
210
+ # Assert that the global variable holds the expected value after the method runs
211
+ subject.#{method_name}(input_value)
212
+ assert_equal expected, $variable
213
+ BODY
214
+ end,
215
+ "mixin_removal" => build(test_name: "depends_on_behavior_from_included_module_in", action: :removed) do |method_name|
216
+ <<~BODY
217
+ # Assert behavior provided by the mixin
218
+ result = subject.#{method_name}(input_value)
219
+ assert_equal expected, result
220
+ BODY
221
+ end,
222
+ "rescue_removal" => build(test_name: "verifies_rescue_handler_is_needed_in", action: :removed) do |method_name|
223
+ <<~BODY
224
+ # Trigger the rescued exception and assert the handler's effect
225
+ result = subject.#{method_name}(input_that_raises)
226
+ assert_equal expected, result
227
+ BODY
228
+ end,
229
+ "rescue_body_replacement" => build(test_name: "verifies_rescue_handler_produces_correct_result_in") do |method_name|
230
+ <<~BODY
231
+ # Trigger the exception and assert the rescue body's return value or side effect
232
+ result = subject.#{method_name}(input_that_raises)
233
+ assert_equal expected, result
234
+ BODY
235
+ end,
236
+ "inline_rescue" => build(test_name: "verifies_inline_rescue_fallback_value_in") do |method_name|
237
+ <<~BODY
238
+ # Trigger the exception and assert the fallback value is correct
239
+ result = subject.#{method_name}(input_that_raises)
240
+ assert_equal expected, result
241
+ BODY
242
+ end,
243
+ "ensure_removal" => build(test_name: "verifies_ensure_cleanup_runs_in", action: :removed_ensure) do |method_name|
244
+ <<~BODY
245
+ # Assert that the cleanup side effect is observable after the method runs
246
+ subject.#{method_name}(input_value)
247
+ assert_equal expected, observable_cleanup_effect
248
+ BODY
249
+ end,
250
+ "break_statement" => build(test_name: "verifies_break_exits_loop_correctly_in") do |method_name|
251
+ <<~BODY
252
+ # Assert the loop exits early and returns the expected value
253
+ result = subject.#{method_name}(input_value)
254
+ assert_equal expected, result
255
+ BODY
256
+ end,
257
+ "next_statement" => build(test_name: "verifies_next_skips_iteration_correctly_in") do |method_name|
258
+ <<~BODY
259
+ # Assert the iteration is skipped and the expected value is yielded
260
+ result = subject.#{method_name}(input_value)
261
+ assert_equal expected, result
262
+ BODY
263
+ end,
264
+ "redo_statement" => build(test_name: "verifies_redo_retry_logic_is_necessary_in") do |method_name|
265
+ <<~BODY
266
+ # Assert the iteration restart changes the outcome
267
+ result = subject.#{method_name}(input_value)
268
+ assert_equal expected, result
269
+ BODY
270
+ end,
271
+ "bitwise_replacement" => build(test_name: "verifies_exact_bitwise_result_in") do |method_name|
272
+ <<~BODY
273
+ # Assert the exact bit-level result to distinguish &, |, and ^ operators
274
+ result = subject.#{method_name}(input_value)
275
+ assert_equal expected, result
276
+ BODY
277
+ end,
278
+ "bitwise_complement" => build(test_name: "verifies_bitwise_complement_result_in") do |method_name|
279
+ <<~BODY
280
+ # Assert the exact complement (~) value, not just sign or magnitude
281
+ result = subject.#{method_name}(input_value)
282
+ assert_equal expected, result
283
+ BODY
284
+ end,
285
+ "bang_method" => build(test_name: "verifies_in_place_vs_copy_semantics_matter_in") do |method_name|
286
+ <<~BODY
287
+ # Assert that the original object is or is not modified
288
+ result = subject.#{method_name}(input_value)
289
+ assert_equal expected, result
290
+ BODY
291
+ end,
292
+ "zsuper_removal" => build(test_name: "verifies_inherited_behavior_from_super_in") do |method_name|
293
+ <<~BODY
294
+ # Assert that the result depends on the superclass implementation
295
+ result = subject.#{method_name}(input_value)
296
+ assert_equal expected, result
297
+ BODY
298
+ end,
299
+ "explicit_super_mutation" => build(test_name: "verifies_correct_arguments_passed_to_super_in") do |method_name|
300
+ <<~BODY
301
+ # Assert the inherited method receives the expected arguments
302
+ result = subject.#{method_name}(input_value)
303
+ assert_equal expected, result
304
+ BODY
305
+ end,
306
+ "index_to_fetch" => build(test_name: "distinguishes_bracket_from_fetch_for_missing_keys_in") do |method_name|
307
+ <<~BODY
308
+ # Access a missing key: [] returns nil, .fetch raises KeyError
309
+ assert_raises(KeyError) { subject.#{method_name}(collection_with_missing_key) }
310
+ BODY
311
+ end,
312
+ "index_to_dig" => build(test_name: "verifies_chained_bracket_access_returns_correct_nested_value_in") do |method_name|
313
+ <<~BODY
314
+ # Assert the nested lookup produces the expected value
315
+ result = subject.#{method_name}(nested_collection)
316
+ assert_equal expected, result
317
+ BODY
318
+ end,
319
+ "index_assignment_removal" => build(test_name: "verifies_bracket_assignment_modifies_collection_in") do |method_name|
320
+ <<~BODY
321
+ # Assert the collection contains the assigned value at the expected key after the method runs
322
+ result = subject.#{method_name}(collection)
323
+ assert_equal expected_value, result[expected_key]
324
+ BODY
325
+ end,
326
+ "pattern_matching_guard" => build(test_name: "verifies_pattern_guard_filters_correctly_in") do |method_name|
327
+ <<~BODY
328
+ # Test with input that matches the pattern but fails the guard condition
329
+ # The guard should prevent matching, routing to a different branch
330
+ result = subject.#{method_name}(input_matching_pattern_but_failing_guard)
331
+ assert_equal expected, result
332
+ BODY
333
+ end,
334
+ "pattern_matching_alternative" => build(test_name: "verifies_each_pattern_alternative_is_reachable_in") do |method_name|
335
+ <<~BODY
336
+ # Test with input that matches only one specific alternative
337
+ # Each alternative should have a dedicated test case
338
+ result = subject.#{method_name}(input_for_specific_alternative)
339
+ assert_equal expected, result
340
+ BODY
341
+ end,
342
+ "collection_return" => build(test_name: "returns_non_empty_collection_from") do |method_name|
343
+ <<~BODY
344
+ # Assert the collection has the expected elements, not just non-empty
345
+ result = subject.#{method_name}(input_value)
346
+ assert_equal expected, result
347
+ BODY
348
+ end,
349
+ "scalar_return" => build(test_name: "returns_non_zero_non_empty_value_from") do |method_name|
350
+ <<~BODY
351
+ # Assert the exact scalar value, not just presence or type
352
+ result = subject.#{method_name}(input_value)
353
+ assert_equal expected, result
354
+ BODY
355
+ end,
356
+ "pattern_matching_array" => build(test_name: "verifies_each_array_pattern_element_matters_in") do |method_name|
357
+ <<~BODY
358
+ # Test with input where changing one element type causes a different match
359
+ # Each position in the array pattern should be validated
360
+ result = subject.#{method_name}(input_with_wrong_element_type)
361
+ assert_equal expected, result
362
+ BODY
363
+ end
658
364
  }.freeze
659
365
  end