evilution 0.14.0 → 0.16.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/.migration-hint-ts +1 -1
- data/.beads/issues.jsonl +41 -41
- data/CHANGELOG.md +43 -0
- data/lib/evilution/ast/inheritance_scanner.rb +70 -0
- data/lib/evilution/ast/parser.rb +10 -6
- data/lib/evilution/cli.rb +6 -1
- data/lib/evilution/config.rb +7 -1
- data/lib/evilution/equivalent/detector.rb +5 -1
- data/lib/evilution/equivalent/heuristic/alias_swap.rb +5 -2
- data/lib/evilution/equivalent/heuristic/arithmetic_identity.rb +30 -0
- data/lib/evilution/equivalent/heuristic/comment_marking.rb +21 -0
- data/lib/evilution/mutator/base.rb +16 -0
- data/lib/evilution/mutator/operator/bang_method.rb +48 -0
- data/lib/evilution/mutator/operator/bitwise_complement.rb +31 -0
- data/lib/evilution/mutator/operator/bitwise_replacement.rb +30 -0
- data/lib/evilution/mutator/operator/break_statement.rb +50 -0
- data/lib/evilution/mutator/operator/class_variable_write.rb +25 -0
- data/lib/evilution/mutator/operator/collection_replacement.rb +25 -1
- data/lib/evilution/mutator/operator/ensure_removal.rb +27 -0
- data/lib/evilution/mutator/operator/explicit_super_mutation.rb +47 -0
- data/lib/evilution/mutator/operator/global_variable_write.rb +25 -0
- data/lib/evilution/mutator/operator/inline_rescue.rb +39 -0
- data/lib/evilution/mutator/operator/instance_variable_write.rb +25 -0
- data/lib/evilution/mutator/operator/local_variable_assignment.rb +16 -0
- data/lib/evilution/mutator/operator/mixin_removal.rb +80 -0
- data/lib/evilution/mutator/operator/next_statement.rb +50 -0
- data/lib/evilution/mutator/operator/redo_statement.rb +18 -0
- data/lib/evilution/mutator/operator/rescue_body_replacement.rb +94 -0
- data/lib/evilution/mutator/operator/rescue_removal.rb +37 -0
- data/lib/evilution/mutator/operator/send_mutation.rb +11 -2
- data/lib/evilution/mutator/operator/superclass_removal.rb +65 -0
- data/lib/evilution/mutator/operator/zsuper_removal.rb +16 -0
- data/lib/evilution/mutator/registry.rb +19 -1
- data/lib/evilution/reporter/progress_bar.rb +84 -0
- data/lib/evilution/reporter/suggestion.rb +253 -1
- data/lib/evilution/runner.rb +105 -19
- data/lib/evilution/version.rb +1 -1
- data/lib/evilution.rb +20 -0
- metadata +24 -2
data/lib/evilution/runner.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "config"
|
|
4
4
|
require_relative "ast/parser"
|
|
5
|
+
require_relative "ast/inheritance_scanner"
|
|
5
6
|
require_relative "memory"
|
|
6
7
|
require_relative "mutator/registry"
|
|
7
8
|
require_relative "isolation/fork"
|
|
@@ -35,9 +36,7 @@ class Evilution::Runner
|
|
|
35
36
|
def call
|
|
36
37
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
37
38
|
|
|
38
|
-
subjects =
|
|
39
|
-
subjects = filter_by_target(subjects) if config.target?
|
|
40
|
-
subjects = filter_by_line_ranges(subjects) if config.line_ranges?
|
|
39
|
+
subjects = parse_and_filter_subjects
|
|
41
40
|
log_memory("after parse_subjects", "#{subjects.length} subjects")
|
|
42
41
|
|
|
43
42
|
baseline_result = run_baseline(subjects)
|
|
@@ -45,6 +44,7 @@ class Evilution::Runner
|
|
|
45
44
|
mutations = generate_mutations(subjects)
|
|
46
45
|
equivalent_mutations, mutations = filter_equivalent(mutations)
|
|
47
46
|
release_subject_nodes(subjects)
|
|
47
|
+
clear_operator_caches
|
|
48
48
|
results, truncated = run_mutations(mutations, baseline_result)
|
|
49
49
|
results += equivalent_mutations.map do |m|
|
|
50
50
|
m.strip_sources!
|
|
@@ -65,28 +65,98 @@ class Evilution::Runner
|
|
|
65
65
|
|
|
66
66
|
attr_reader :parser, :registry, :isolator, :cache, :on_result
|
|
67
67
|
|
|
68
|
+
def parse_and_filter_subjects
|
|
69
|
+
subjects = parse_subjects
|
|
70
|
+
subjects = filter_by_descendants(subjects) if descendants_target?
|
|
71
|
+
subjects = filter_by_target(subjects) if method_target?
|
|
72
|
+
subjects = filter_by_line_ranges(subjects) if config.line_ranges?
|
|
73
|
+
subjects
|
|
74
|
+
end
|
|
75
|
+
|
|
68
76
|
def parse_subjects
|
|
69
77
|
files = resolve_target_files
|
|
70
78
|
files.flat_map { |file| parser.call(file) }
|
|
71
79
|
end
|
|
72
80
|
|
|
73
81
|
def resolve_target_files
|
|
82
|
+
return resolve_source_glob if source_glob_target?
|
|
74
83
|
return config.target_files unless config.target_files.empty?
|
|
75
84
|
|
|
76
85
|
Evilution::Git::ChangedFiles.new.call
|
|
77
86
|
end
|
|
78
87
|
|
|
88
|
+
def source_glob_target?
|
|
89
|
+
config.target&.start_with?("source:")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def descendants_target?
|
|
93
|
+
config.target&.start_with?("descendants:")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def method_target?
|
|
97
|
+
config.target? && !source_glob_target? && !descendants_target?
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def resolve_source_glob
|
|
101
|
+
pattern = config.target.delete_prefix("source:")
|
|
102
|
+
files = Dir.glob(pattern)
|
|
103
|
+
raise Evilution::Error, "no files found matching '#{pattern}'" if files.empty?
|
|
104
|
+
|
|
105
|
+
files.sort
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def filter_by_descendants(subjects)
|
|
109
|
+
base_name = config.target.delete_prefix("descendants:")
|
|
110
|
+
files = resolve_target_files
|
|
111
|
+
inheritance = Evilution::AST::InheritanceScanner.call(files)
|
|
112
|
+
class_names = resolve_descendant_set(base_name, inheritance)
|
|
113
|
+
raise Evilution::Error, "no classes found matching '#{config.target}'" if class_names.empty?
|
|
114
|
+
|
|
115
|
+
subjects.select { |s| class_names.include?(s.name.split(/[#.]/).first) }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def resolve_descendant_set(base_name, inheritance)
|
|
119
|
+
descendants = Set.new
|
|
120
|
+
known = inheritance.key?(base_name) || inheritance.value?(base_name)
|
|
121
|
+
return descendants unless known
|
|
122
|
+
|
|
123
|
+
descendants.add(base_name)
|
|
124
|
+
changed = true
|
|
125
|
+
while changed
|
|
126
|
+
changed = false
|
|
127
|
+
inheritance.each do |child, parent|
|
|
128
|
+
next unless descendants.include?(parent)
|
|
129
|
+
next if descendants.include?(child)
|
|
130
|
+
|
|
131
|
+
descendants.add(child)
|
|
132
|
+
changed = true
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
descendants
|
|
136
|
+
end
|
|
137
|
+
|
|
79
138
|
def filter_by_target(subjects)
|
|
80
|
-
matched =
|
|
81
|
-
subjects.select { |s| s.name == config.target }
|
|
82
|
-
else
|
|
83
|
-
subjects.select { |s| s.name.start_with?("#{config.target}#") }
|
|
84
|
-
end
|
|
139
|
+
matched = subjects.select(&target_matcher)
|
|
85
140
|
raise Evilution::Error, "no method found matching '#{config.target}'" if matched.empty?
|
|
86
141
|
|
|
87
142
|
matched
|
|
88
143
|
end
|
|
89
144
|
|
|
145
|
+
def target_matcher
|
|
146
|
+
target = config.target
|
|
147
|
+
if target.end_with?("*")
|
|
148
|
+
prefix = target.chomp("*")
|
|
149
|
+
->(s) { s.name.split(/[#.]/).first.start_with?(prefix) }
|
|
150
|
+
elsif target.end_with?("#", ".")
|
|
151
|
+
prefix = target
|
|
152
|
+
->(s) { s.name.start_with?(prefix) }
|
|
153
|
+
elsif target.include?("#") || target.include?(".")
|
|
154
|
+
->(s) { s.name == target }
|
|
155
|
+
else
|
|
156
|
+
->(s) { s.name.start_with?("#{target}#") || s.name.start_with?("#{target}.") }
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
90
160
|
def filter_by_line_ranges(subjects)
|
|
91
161
|
subjects.select do |subject|
|
|
92
162
|
range = config.line_ranges[subject.file_path]
|
|
@@ -112,6 +182,10 @@ class Evilution::Runner
|
|
|
112
182
|
subjects.each(&:release_node!)
|
|
113
183
|
end
|
|
114
184
|
|
|
185
|
+
def clear_operator_caches
|
|
186
|
+
Evilution::Mutator::Base.clear_parse_cache!
|
|
187
|
+
end
|
|
188
|
+
|
|
115
189
|
def equivalent_result(mutation)
|
|
116
190
|
Evilution::Result::MutationResult.new(mutation: mutation, status: :equivalent, duration: 0.0)
|
|
117
191
|
end
|
|
@@ -127,11 +201,14 @@ class Evilution::Runner
|
|
|
127
201
|
end
|
|
128
202
|
|
|
129
203
|
def run_mutations(mutations, baseline_result = nil)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
204
|
+
@progress_bar = build_progress_bar(mutations.length)
|
|
205
|
+
result = if config.jobs > 1
|
|
206
|
+
run_mutations_parallel(mutations, baseline_result)
|
|
207
|
+
else
|
|
208
|
+
run_mutations_sequential(mutations, baseline_result)
|
|
209
|
+
end
|
|
210
|
+
@progress_bar&.finish
|
|
211
|
+
result
|
|
135
212
|
end
|
|
136
213
|
|
|
137
214
|
def run_mutations_sequential(mutations, baseline_result = nil)
|
|
@@ -150,9 +227,7 @@ class Evilution::Runner
|
|
|
150
227
|
result = neutralize_if_baseline_failed(result, baseline_result, spec_resolver)
|
|
151
228
|
results << result
|
|
152
229
|
survived_count += 1 if result.survived?
|
|
153
|
-
|
|
154
|
-
log_progress(index + 1, result.status)
|
|
155
|
-
log_mutation_diagnostics(result)
|
|
230
|
+
notify_result(result, index + 1)
|
|
156
231
|
|
|
157
232
|
if config.fail_fast? && survived_count >= config.fail_fast
|
|
158
233
|
truncated = true
|
|
@@ -207,9 +282,7 @@ class Evilution::Runner
|
|
|
207
282
|
state[:results] << result
|
|
208
283
|
state[:survived_count] += 1 if result.survived?
|
|
209
284
|
state[:completed] += 1
|
|
210
|
-
|
|
211
|
-
log_progress(state[:completed], result.status)
|
|
212
|
-
log_mutation_diagnostics(result)
|
|
285
|
+
notify_result(result, state[:completed])
|
|
213
286
|
end
|
|
214
287
|
|
|
215
288
|
log_memory("after batch", "#{state[:completed]} complete")
|
|
@@ -373,6 +446,19 @@ class Evilution::Runner
|
|
|
373
446
|
warn "[evilution] failed to save session: #{e.message}" unless config.quiet
|
|
374
447
|
end
|
|
375
448
|
|
|
449
|
+
def notify_result(result, index)
|
|
450
|
+
on_result&.call(result)
|
|
451
|
+
@progress_bar&.tick(status: result.status)
|
|
452
|
+
log_progress(index, result.status)
|
|
453
|
+
log_mutation_diagnostics(result)
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def build_progress_bar(total)
|
|
457
|
+
return nil if !config.progress? || config.quiet || config.verbose || !config.text? || !$stderr.tty?
|
|
458
|
+
|
|
459
|
+
Evilution::Reporter::ProgressBar.new(total: total, output: $stderr)
|
|
460
|
+
end
|
|
461
|
+
|
|
376
462
|
def build_reporter
|
|
377
463
|
case config.format
|
|
378
464
|
when :json
|
data/lib/evilution/version.rb
CHANGED
data/lib/evilution.rb
CHANGED
|
@@ -10,6 +10,7 @@ require_relative "evilution/ast"
|
|
|
10
10
|
require_relative "evilution/parallel"
|
|
11
11
|
require_relative "evilution/ast/source_surgeon"
|
|
12
12
|
require_relative "evilution/ast/parser"
|
|
13
|
+
require_relative "evilution/ast/inheritance_scanner"
|
|
13
14
|
require_relative "evilution/mutator"
|
|
14
15
|
require_relative "evilution/mutator/base"
|
|
15
16
|
require_relative "evilution/mutator/operator"
|
|
@@ -41,6 +42,24 @@ require_relative "evilution/mutator/operator/receiver_replacement"
|
|
|
41
42
|
require_relative "evilution/mutator/operator/send_mutation"
|
|
42
43
|
require_relative "evilution/mutator/operator/argument_nil_substitution"
|
|
43
44
|
require_relative "evilution/mutator/operator/compound_assignment"
|
|
45
|
+
require_relative "evilution/mutator/operator/mixin_removal"
|
|
46
|
+
require_relative "evilution/mutator/operator/superclass_removal"
|
|
47
|
+
require_relative "evilution/mutator/operator/local_variable_assignment"
|
|
48
|
+
require_relative "evilution/mutator/operator/instance_variable_write"
|
|
49
|
+
require_relative "evilution/mutator/operator/class_variable_write"
|
|
50
|
+
require_relative "evilution/mutator/operator/global_variable_write"
|
|
51
|
+
require_relative "evilution/mutator/operator/rescue_removal"
|
|
52
|
+
require_relative "evilution/mutator/operator/rescue_body_replacement"
|
|
53
|
+
require_relative "evilution/mutator/operator/inline_rescue"
|
|
54
|
+
require_relative "evilution/mutator/operator/ensure_removal"
|
|
55
|
+
require_relative "evilution/mutator/operator/break_statement"
|
|
56
|
+
require_relative "evilution/mutator/operator/next_statement"
|
|
57
|
+
require_relative "evilution/mutator/operator/redo_statement"
|
|
58
|
+
require_relative "evilution/mutator/operator/bang_method"
|
|
59
|
+
require_relative "evilution/mutator/operator/bitwise_replacement"
|
|
60
|
+
require_relative "evilution/mutator/operator/bitwise_complement"
|
|
61
|
+
require_relative "evilution/mutator/operator/zsuper_removal"
|
|
62
|
+
require_relative "evilution/mutator/operator/explicit_super_mutation"
|
|
44
63
|
require_relative "evilution/mutator/registry"
|
|
45
64
|
require_relative "evilution/equivalent"
|
|
46
65
|
require_relative "evilution/equivalent/heuristic"
|
|
@@ -63,6 +82,7 @@ require_relative "evilution/reporter/json"
|
|
|
63
82
|
require_relative "evilution/reporter/cli"
|
|
64
83
|
require_relative "evilution/reporter/html"
|
|
65
84
|
require_relative "evilution/reporter/suggestion"
|
|
85
|
+
require_relative "evilution/reporter/progress_bar"
|
|
66
86
|
require_relative "evilution/spec_resolver"
|
|
67
87
|
require_relative "evilution/baseline"
|
|
68
88
|
require_relative "evilution/cache"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: evilution
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.16.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Denis Kiselev
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: diff-lcs
|
|
@@ -79,6 +79,7 @@ files:
|
|
|
79
79
|
- exe/evilution
|
|
80
80
|
- lib/evilution.rb
|
|
81
81
|
- lib/evilution/ast.rb
|
|
82
|
+
- lib/evilution/ast/inheritance_scanner.rb
|
|
82
83
|
- lib/evilution/ast/parser.rb
|
|
83
84
|
- lib/evilution/ast/source_surgeon.rb
|
|
84
85
|
- lib/evilution/baseline.rb
|
|
@@ -89,6 +90,8 @@ files:
|
|
|
89
90
|
- lib/evilution/equivalent/detector.rb
|
|
90
91
|
- lib/evilution/equivalent/heuristic.rb
|
|
91
92
|
- lib/evilution/equivalent/heuristic/alias_swap.rb
|
|
93
|
+
- lib/evilution/equivalent/heuristic/arithmetic_identity.rb
|
|
94
|
+
- lib/evilution/equivalent/heuristic/comment_marking.rb
|
|
92
95
|
- lib/evilution/equivalent/heuristic/dead_code.rb
|
|
93
96
|
- lib/evilution/equivalent/heuristic/method_body_nil.rb
|
|
94
97
|
- lib/evilution/equivalent/heuristic/noop_source.rb
|
|
@@ -116,30 +119,48 @@ files:
|
|
|
116
119
|
- lib/evilution/mutator/operator/argument_removal.rb
|
|
117
120
|
- lib/evilution/mutator/operator/arithmetic_replacement.rb
|
|
118
121
|
- lib/evilution/mutator/operator/array_literal.rb
|
|
122
|
+
- lib/evilution/mutator/operator/bang_method.rb
|
|
123
|
+
- lib/evilution/mutator/operator/bitwise_complement.rb
|
|
124
|
+
- lib/evilution/mutator/operator/bitwise_replacement.rb
|
|
119
125
|
- lib/evilution/mutator/operator/block_removal.rb
|
|
120
126
|
- lib/evilution/mutator/operator/boolean_literal_replacement.rb
|
|
121
127
|
- lib/evilution/mutator/operator/boolean_operator_replacement.rb
|
|
128
|
+
- lib/evilution/mutator/operator/break_statement.rb
|
|
129
|
+
- lib/evilution/mutator/operator/class_variable_write.rb
|
|
122
130
|
- lib/evilution/mutator/operator/collection_replacement.rb
|
|
123
131
|
- lib/evilution/mutator/operator/comparison_replacement.rb
|
|
124
132
|
- lib/evilution/mutator/operator/compound_assignment.rb
|
|
125
133
|
- lib/evilution/mutator/operator/conditional_branch.rb
|
|
126
134
|
- lib/evilution/mutator/operator/conditional_flip.rb
|
|
127
135
|
- lib/evilution/mutator/operator/conditional_negation.rb
|
|
136
|
+
- lib/evilution/mutator/operator/ensure_removal.rb
|
|
137
|
+
- lib/evilution/mutator/operator/explicit_super_mutation.rb
|
|
128
138
|
- lib/evilution/mutator/operator/float_literal.rb
|
|
139
|
+
- lib/evilution/mutator/operator/global_variable_write.rb
|
|
129
140
|
- lib/evilution/mutator/operator/hash_literal.rb
|
|
141
|
+
- lib/evilution/mutator/operator/inline_rescue.rb
|
|
142
|
+
- lib/evilution/mutator/operator/instance_variable_write.rb
|
|
130
143
|
- lib/evilution/mutator/operator/integer_literal.rb
|
|
144
|
+
- lib/evilution/mutator/operator/local_variable_assignment.rb
|
|
131
145
|
- lib/evilution/mutator/operator/method_body_replacement.rb
|
|
132
146
|
- lib/evilution/mutator/operator/method_call_removal.rb
|
|
147
|
+
- lib/evilution/mutator/operator/mixin_removal.rb
|
|
133
148
|
- lib/evilution/mutator/operator/negation_insertion.rb
|
|
149
|
+
- lib/evilution/mutator/operator/next_statement.rb
|
|
134
150
|
- lib/evilution/mutator/operator/nil_replacement.rb
|
|
135
151
|
- lib/evilution/mutator/operator/range_replacement.rb
|
|
136
152
|
- lib/evilution/mutator/operator/receiver_replacement.rb
|
|
153
|
+
- lib/evilution/mutator/operator/redo_statement.rb
|
|
137
154
|
- lib/evilution/mutator/operator/regexp_mutation.rb
|
|
155
|
+
- lib/evilution/mutator/operator/rescue_body_replacement.rb
|
|
156
|
+
- lib/evilution/mutator/operator/rescue_removal.rb
|
|
138
157
|
- lib/evilution/mutator/operator/return_value_removal.rb
|
|
139
158
|
- lib/evilution/mutator/operator/send_mutation.rb
|
|
140
159
|
- lib/evilution/mutator/operator/statement_deletion.rb
|
|
141
160
|
- lib/evilution/mutator/operator/string_literal.rb
|
|
161
|
+
- lib/evilution/mutator/operator/superclass_removal.rb
|
|
142
162
|
- lib/evilution/mutator/operator/symbol_literal.rb
|
|
163
|
+
- lib/evilution/mutator/operator/zsuper_removal.rb
|
|
143
164
|
- lib/evilution/mutator/registry.rb
|
|
144
165
|
- lib/evilution/parallel.rb
|
|
145
166
|
- lib/evilution/parallel/pool.rb
|
|
@@ -147,6 +168,7 @@ files:
|
|
|
147
168
|
- lib/evilution/reporter/cli.rb
|
|
148
169
|
- lib/evilution/reporter/html.rb
|
|
149
170
|
- lib/evilution/reporter/json.rb
|
|
171
|
+
- lib/evilution/reporter/progress_bar.rb
|
|
150
172
|
- lib/evilution/reporter/suggestion.rb
|
|
151
173
|
- lib/evilution/result.rb
|
|
152
174
|
- lib/evilution/result/mutation_result.rb
|