evilution 0.27.0 → 0.29.0

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