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