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