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