evilution 0.17.0 → 0.19.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 +103 -33
- data/CHANGELOG.md +50 -0
- data/README.md +144 -50
- data/lib/evilution/ast/sorbet_sig_detector.rb +52 -0
- data/lib/evilution/baseline.rb +9 -1
- data/lib/evilution/cli.rb +398 -23
- data/lib/evilution/config.rb +10 -2
- data/lib/evilution/disable_comment.rb +90 -0
- data/lib/evilution/integration/rspec.rb +74 -5
- data/lib/evilution/isolation/fork.rb +10 -6
- data/lib/evilution/isolation/in_process.rb +14 -10
- data/lib/evilution/mcp/session_diff_tool.rb +5 -35
- data/lib/evilution/mutator/operator/collection_return.rb +33 -0
- data/lib/evilution/mutator/operator/defined_check.rb +16 -0
- data/lib/evilution/mutator/operator/keyword_argument.rb +91 -0
- data/lib/evilution/mutator/operator/multiple_assignment.rb +47 -0
- data/lib/evilution/mutator/operator/regex_capture.rb +43 -0
- data/lib/evilution/mutator/operator/scalar_return.rb +37 -0
- data/lib/evilution/mutator/operator/splat_operator.rb +46 -0
- data/lib/evilution/mutator/operator/yield_statement.rb +51 -0
- data/lib/evilution/mutator/registry.rb +9 -1
- data/lib/evilution/parallel/pool.rb +7 -53
- data/lib/evilution/parallel/work_queue.rb +265 -0
- data/lib/evilution/reporter/cli.rb +21 -1
- data/lib/evilution/reporter/html.rb +69 -3
- data/lib/evilution/reporter/json.rb +23 -2
- data/lib/evilution/reporter/suggestion.rb +29 -1
- data/lib/evilution/result/mutation_result.rb +5 -2
- data/lib/evilution/result/summary.rb +19 -2
- data/lib/evilution/runner.rb +123 -12
- data/lib/evilution/session/diff.rb +85 -0
- data/lib/evilution/spec_resolver.rb +13 -1
- data/lib/evilution/version.rb +1 -1
- data/lib/evilution.rb +11 -0
- data/script/memory_check +22 -0
- metadata +14 -2
data/lib/evilution/runner.rb
CHANGED
|
@@ -21,6 +21,8 @@ require_relative "cache"
|
|
|
21
21
|
require_relative "parallel/pool"
|
|
22
22
|
require_relative "session/store"
|
|
23
23
|
require_relative "ast/pattern/filter"
|
|
24
|
+
require_relative "disable_comment"
|
|
25
|
+
require_relative "ast/sorbet_sig_detector"
|
|
24
26
|
|
|
25
27
|
class Evilution::Runner
|
|
26
28
|
attr_reader :config
|
|
@@ -33,6 +35,10 @@ class Evilution::Runner
|
|
|
33
35
|
@registry = Evilution::Mutator::Registry.default
|
|
34
36
|
@isolator = build_isolator
|
|
35
37
|
@cache = config.incremental? ? Evilution::Cache.new : nil
|
|
38
|
+
@disable_detector = Evilution::DisableComment.new
|
|
39
|
+
@disabled_ranges_cache = {}
|
|
40
|
+
@sig_detector = Evilution::AST::SorbetSigDetector.new
|
|
41
|
+
@sig_ranges_cache = {}
|
|
36
42
|
end
|
|
37
43
|
|
|
38
44
|
def call
|
|
@@ -43,7 +49,7 @@ class Evilution::Runner
|
|
|
43
49
|
|
|
44
50
|
baseline_result = run_baseline(subjects)
|
|
45
51
|
|
|
46
|
-
mutations, skipped_count = generate_mutations(subjects)
|
|
52
|
+
mutations, skipped_count, disabled_mutations = generate_mutations(subjects)
|
|
47
53
|
equivalent_mutations, mutations = filter_equivalent(mutations)
|
|
48
54
|
release_subject_nodes(subjects)
|
|
49
55
|
clear_operator_caches
|
|
@@ -57,17 +63,14 @@ class Evilution::Runner
|
|
|
57
63
|
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
|
|
58
64
|
|
|
59
65
|
summary = Evilution::Result::Summary.new(results: results, duration: duration, truncated: truncated,
|
|
60
|
-
skipped: skipped_count
|
|
66
|
+
skipped: skipped_count,
|
|
67
|
+
disabled_mutations: disabled_mutations)
|
|
61
68
|
output_report(summary)
|
|
62
69
|
save_session(summary)
|
|
63
70
|
|
|
64
71
|
summary
|
|
65
72
|
end
|
|
66
73
|
|
|
67
|
-
private
|
|
68
|
-
|
|
69
|
-
attr_reader :parser, :registry, :isolator, :cache, :on_result, :hooks
|
|
70
|
-
|
|
71
74
|
def parse_and_filter_subjects
|
|
72
75
|
subjects = parse_subjects
|
|
73
76
|
subjects = filter_by_descendants(subjects) if descendants_target?
|
|
@@ -76,6 +79,10 @@ class Evilution::Runner
|
|
|
76
79
|
subjects
|
|
77
80
|
end
|
|
78
81
|
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
attr_reader :parser, :registry, :isolator, :cache, :on_result, :hooks, :disable_detector, :sig_detector
|
|
85
|
+
|
|
79
86
|
def parse_subjects
|
|
80
87
|
files = resolve_target_files
|
|
81
88
|
files.flat_map { |file| parser.call(file) }
|
|
@@ -176,7 +183,73 @@ class Evilution::Runner
|
|
|
176
183
|
mutations = subjects.flat_map do |subject|
|
|
177
184
|
registry.mutations_for(subject, filter: filter)
|
|
178
185
|
end
|
|
179
|
-
|
|
186
|
+
skipped_count = filter ? filter.skipped_count : 0
|
|
187
|
+
|
|
188
|
+
mutations, disabled = filter_disabled(mutations)
|
|
189
|
+
disabled.each(&:strip_sources!) if config.show_disabled?
|
|
190
|
+
disabled_mutations = config.show_disabled? ? disabled : []
|
|
191
|
+
|
|
192
|
+
mutations, sig_skipped = filter_sig_blocks(mutations)
|
|
193
|
+
|
|
194
|
+
[mutations, skipped_count + disabled.length + sig_skipped, disabled_mutations]
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def filter_disabled(mutations)
|
|
198
|
+
enabled = []
|
|
199
|
+
disabled = []
|
|
200
|
+
|
|
201
|
+
mutations.each do |mutation|
|
|
202
|
+
if mutation_disabled?(mutation)
|
|
203
|
+
disabled << mutation
|
|
204
|
+
else
|
|
205
|
+
enabled << mutation
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
[enabled, disabled]
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def mutation_disabled?(mutation)
|
|
213
|
+
ranges = disabled_ranges_for(mutation.file_path)
|
|
214
|
+
ranges.any? { |range| range.cover?(mutation.line) }
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def disabled_ranges_for(file_path)
|
|
218
|
+
@disabled_ranges_cache[file_path] ||= begin
|
|
219
|
+
source = File.read(file_path)
|
|
220
|
+
@disable_detector.call(source)
|
|
221
|
+
rescue SystemCallError
|
|
222
|
+
[]
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def filter_sig_blocks(mutations)
|
|
227
|
+
enabled = []
|
|
228
|
+
skipped = 0
|
|
229
|
+
|
|
230
|
+
mutations.each do |mutation|
|
|
231
|
+
if mutation_in_sig_block?(mutation)
|
|
232
|
+
skipped += 1
|
|
233
|
+
else
|
|
234
|
+
enabled << mutation
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
[enabled, skipped]
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def mutation_in_sig_block?(mutation)
|
|
242
|
+
ranges = sig_line_ranges_for(mutation.file_path)
|
|
243
|
+
ranges.any? { |range| range.cover?(mutation.line) }
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def sig_line_ranges_for(file_path)
|
|
247
|
+
@sig_ranges_cache[file_path] ||= begin
|
|
248
|
+
source = File.read(file_path)
|
|
249
|
+
@sig_detector.line_ranges(source)
|
|
250
|
+
rescue SystemCallError
|
|
251
|
+
[]
|
|
252
|
+
end
|
|
180
253
|
end
|
|
181
254
|
|
|
182
255
|
def build_ignore_filter
|
|
@@ -252,18 +325,23 @@ class Evilution::Runner
|
|
|
252
325
|
|
|
253
326
|
def run_mutations_parallel(mutations, baseline_result = nil)
|
|
254
327
|
integration = build_integration
|
|
255
|
-
pool = Evilution::Parallel::Pool.new(size: config.jobs, hooks: @hooks)
|
|
328
|
+
pool = Evilution::Parallel::Pool.new(size: config.jobs, hooks: @hooks, item_timeout: config.timeout ? config.timeout * 2 : nil)
|
|
256
329
|
worker_isolator = Evilution::Isolation::InProcess.new
|
|
257
330
|
spec_resolver = baseline_result&.failed? ? Evilution::SpecResolver.new : nil
|
|
258
331
|
state = { results: [], survived_count: 0, truncated: false, completed: 0 }
|
|
259
332
|
|
|
333
|
+
all_worker_stats = []
|
|
334
|
+
|
|
260
335
|
mutations.each_slice(config.jobs) do |batch|
|
|
261
336
|
break if state[:truncated]
|
|
262
337
|
|
|
263
338
|
batch_results = run_parallel_batch(batch, pool, worker_isolator, integration)
|
|
339
|
+
all_worker_stats.concat(pool.worker_stats)
|
|
264
340
|
process_batch(batch_results, baseline_result, spec_resolver, state)
|
|
265
341
|
end
|
|
266
342
|
|
|
343
|
+
log_worker_stats(aggregate_worker_stats(all_worker_stats))
|
|
344
|
+
|
|
267
345
|
[state[:results], state[:truncated]]
|
|
268
346
|
end
|
|
269
347
|
|
|
@@ -318,7 +396,8 @@ class Evilution::Runner
|
|
|
318
396
|
duration: result.duration,
|
|
319
397
|
test_command: result.test_command,
|
|
320
398
|
child_rss_kb: result.child_rss_kb,
|
|
321
|
-
memory_delta_kb: result.memory_delta_kb
|
|
399
|
+
memory_delta_kb: result.memory_delta_kb,
|
|
400
|
+
parent_rss_kb: result.parent_rss_kb
|
|
322
401
|
)
|
|
323
402
|
end
|
|
324
403
|
|
|
@@ -329,7 +408,8 @@ class Evilution::Runner
|
|
|
329
408
|
killing_test: result.killing_test,
|
|
330
409
|
test_command: result.test_command,
|
|
331
410
|
child_rss_kb: result.child_rss_kb,
|
|
332
|
-
memory_delta_kb: result.memory_delta_kb
|
|
411
|
+
memory_delta_kb: result.memory_delta_kb,
|
|
412
|
+
parent_rss_kb: result.parent_rss_kb
|
|
333
413
|
}
|
|
334
414
|
end
|
|
335
415
|
|
|
@@ -342,7 +422,8 @@ class Evilution::Runner
|
|
|
342
422
|
killing_test: data[:killing_test],
|
|
343
423
|
test_command: data[:test_command],
|
|
344
424
|
child_rss_kb: data[:child_rss_kb],
|
|
345
|
-
memory_delta_kb: data[:memory_delta_kb]
|
|
425
|
+
memory_delta_kb: data[:memory_delta_kb],
|
|
426
|
+
parent_rss_kb: data[:parent_rss_kb]
|
|
346
427
|
)
|
|
347
428
|
end
|
|
348
429
|
end
|
|
@@ -458,6 +539,28 @@ class Evilution::Runner
|
|
|
458
539
|
warn "[evilution] failed to save session: #{e.message}" unless config.quiet
|
|
459
540
|
end
|
|
460
541
|
|
|
542
|
+
def log_worker_stats(stats)
|
|
543
|
+
return unless config.verbose && !config.quiet && stats.any?
|
|
544
|
+
|
|
545
|
+
stats.each do |stat|
|
|
546
|
+
pct = format("%.1f", stat.utilization * 100)
|
|
547
|
+
$stderr.write("[verbose] worker #{stat.pid}: #{stat.items_completed} items, utilization #{pct}%\n")
|
|
548
|
+
end
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
def aggregate_worker_stats(stats)
|
|
552
|
+
return stats if stats.empty?
|
|
553
|
+
|
|
554
|
+
stats.group_by(&:pid).map do |pid, entries|
|
|
555
|
+
Evilution::Parallel::WorkQueue::WorkerStat.new(
|
|
556
|
+
pid,
|
|
557
|
+
entries.sum(&:items_completed),
|
|
558
|
+
entries.sum(&:busy_time),
|
|
559
|
+
entries.sum(&:wall_time)
|
|
560
|
+
)
|
|
561
|
+
end
|
|
562
|
+
end
|
|
563
|
+
|
|
461
564
|
def notify_result(result, index)
|
|
462
565
|
on_result&.call(result)
|
|
463
566
|
@progress_bar&.tick(status: result.status)
|
|
@@ -478,10 +581,18 @@ class Evilution::Runner
|
|
|
478
581
|
when :text
|
|
479
582
|
Evilution::Reporter::CLI.new
|
|
480
583
|
when :html
|
|
481
|
-
Evilution::Reporter::HTML.new
|
|
584
|
+
Evilution::Reporter::HTML.new(baseline: load_baseline_session)
|
|
482
585
|
end
|
|
483
586
|
end
|
|
484
587
|
|
|
588
|
+
def load_baseline_session
|
|
589
|
+
path = config.baseline_session
|
|
590
|
+
return nil unless path
|
|
591
|
+
|
|
592
|
+
store = Evilution::Session::Store.new
|
|
593
|
+
store.load(path)
|
|
594
|
+
end
|
|
595
|
+
|
|
485
596
|
def partition_cached(batch)
|
|
486
597
|
uncached_indices = []
|
|
487
598
|
cached_results = {}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../session"
|
|
4
|
+
|
|
5
|
+
class Evilution::Session::Diff
|
|
6
|
+
Result = Struct.new(:summary, :fixed, :new_survivors, :persistent) do
|
|
7
|
+
def to_h
|
|
8
|
+
{
|
|
9
|
+
"summary" => summary.to_h,
|
|
10
|
+
"fixed" => fixed,
|
|
11
|
+
"new_survivors" => new_survivors,
|
|
12
|
+
"persistent" => persistent
|
|
13
|
+
}
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
SummaryDiff = Struct.new(
|
|
18
|
+
:base_score, :head_score, :score_delta,
|
|
19
|
+
:base_survived, :head_survived,
|
|
20
|
+
:base_total, :head_total,
|
|
21
|
+
:base_killed, :head_killed
|
|
22
|
+
) do
|
|
23
|
+
def to_h
|
|
24
|
+
{
|
|
25
|
+
"base_score" => base_score,
|
|
26
|
+
"head_score" => head_score,
|
|
27
|
+
"score_delta" => score_delta,
|
|
28
|
+
"base_survived" => base_survived,
|
|
29
|
+
"head_survived" => head_survived,
|
|
30
|
+
"base_total" => base_total,
|
|
31
|
+
"head_total" => head_total,
|
|
32
|
+
"base_killed" => base_killed,
|
|
33
|
+
"head_killed" => head_killed
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def call(base_data, head_data)
|
|
39
|
+
base_survivors = base_data["survived"] || []
|
|
40
|
+
head_survivors = head_data["survived"] || []
|
|
41
|
+
|
|
42
|
+
base_keys = base_survivors.to_set { |m| mutation_key(m) }
|
|
43
|
+
head_keys = head_survivors.to_set { |m| mutation_key(m) }
|
|
44
|
+
|
|
45
|
+
Result.new(
|
|
46
|
+
summary: build_summary_diff(base_data, head_data),
|
|
47
|
+
fixed: base_survivors.reject { |m| head_keys.include?(mutation_key(m)) },
|
|
48
|
+
new_survivors: head_survivors.reject { |m| base_keys.include?(mutation_key(m)) },
|
|
49
|
+
persistent: head_survivors.select { |m| base_keys.include?(mutation_key(m)) }
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def build_summary_diff(base_data, head_data)
|
|
56
|
+
base = extract_summary_values(base_data)
|
|
57
|
+
head = extract_summary_values(head_data)
|
|
58
|
+
|
|
59
|
+
SummaryDiff.new(
|
|
60
|
+
base_score: base[:score],
|
|
61
|
+
head_score: head[:score],
|
|
62
|
+
score_delta: (head[:score] - base[:score]).round(4),
|
|
63
|
+
base_survived: base[:survived],
|
|
64
|
+
head_survived: head[:survived],
|
|
65
|
+
base_total: base[:total],
|
|
66
|
+
head_total: head[:total],
|
|
67
|
+
base_killed: base[:killed],
|
|
68
|
+
head_killed: head[:killed]
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def extract_summary_values(data)
|
|
73
|
+
summary = data["summary"] || {}
|
|
74
|
+
{
|
|
75
|
+
score: summary["score"] || 0.0,
|
|
76
|
+
survived: summary["survived"] || 0,
|
|
77
|
+
total: summary["total"] || 0,
|
|
78
|
+
killed: summary["killed"] || 0
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def mutation_key(mutation)
|
|
83
|
+
[mutation["operator"], mutation["file"], mutation["line"], mutation["subject"]]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
class Evilution::SpecResolver
|
|
4
4
|
STRIPPABLE_PREFIXES = %w[lib/ app/].freeze
|
|
5
|
+
CONTROLLER_PREFIX = "controllers/"
|
|
5
6
|
|
|
6
7
|
def call(source_path)
|
|
7
8
|
return nil if source_path.nil? || source_path.empty?
|
|
@@ -29,7 +30,8 @@ class Evilution::SpecResolver
|
|
|
29
30
|
|
|
30
31
|
candidates = if prefix
|
|
31
32
|
stripped = base.delete_prefix(prefix)
|
|
32
|
-
|
|
33
|
+
request_spec = controller_to_request_spec(stripped)
|
|
34
|
+
[request_spec, "spec/#{stripped}", "spec/#{base}"].compact
|
|
33
35
|
else
|
|
34
36
|
["spec/#{base}"]
|
|
35
37
|
end
|
|
@@ -39,6 +41,16 @@ class Evilution::SpecResolver
|
|
|
39
41
|
candidates + fallbacks
|
|
40
42
|
end
|
|
41
43
|
|
|
44
|
+
def controller_to_request_spec(stripped_path)
|
|
45
|
+
return nil unless stripped_path.start_with?(CONTROLLER_PREFIX)
|
|
46
|
+
return nil unless stripped_path.end_with?("_controller_spec.rb")
|
|
47
|
+
|
|
48
|
+
request_path = stripped_path
|
|
49
|
+
.delete_prefix(CONTROLLER_PREFIX)
|
|
50
|
+
.sub(/_controller_spec\.rb\z/, "_spec.rb")
|
|
51
|
+
"spec/requests/#{request_path}"
|
|
52
|
+
end
|
|
53
|
+
|
|
42
54
|
def parent_fallback_candidates(spec_path)
|
|
43
55
|
parts = spec_path.split("/")
|
|
44
56
|
# parts: ["spec", "foo", "bar_spec.rb"] — need at least 3 parts for fallback
|
data/lib/evilution/version.rb
CHANGED
data/lib/evilution.rb
CHANGED
|
@@ -12,6 +12,7 @@ require_relative "evilution/ast/source_surgeon"
|
|
|
12
12
|
require_relative "evilution/ast/parser"
|
|
13
13
|
require_relative "evilution/ast/inheritance_scanner"
|
|
14
14
|
require_relative "evilution/ast/pattern"
|
|
15
|
+
require_relative "evilution/ast/sorbet_sig_detector"
|
|
15
16
|
require_relative "evilution/ast/pattern/matcher"
|
|
16
17
|
require_relative "evilution/ast/pattern/parser"
|
|
17
18
|
require_relative "evilution/hooks"
|
|
@@ -48,6 +49,8 @@ require_relative "evilution/mutator/operator/receiver_replacement"
|
|
|
48
49
|
require_relative "evilution/mutator/operator/send_mutation"
|
|
49
50
|
require_relative "evilution/mutator/operator/argument_nil_substitution"
|
|
50
51
|
require_relative "evilution/mutator/operator/compound_assignment"
|
|
52
|
+
require_relative "evilution/mutator/operator/keyword_argument"
|
|
53
|
+
require_relative "evilution/mutator/operator/multiple_assignment"
|
|
51
54
|
require_relative "evilution/mutator/operator/mixin_removal"
|
|
52
55
|
require_relative "evilution/mutator/operator/superclass_removal"
|
|
53
56
|
require_relative "evilution/mutator/operator/local_variable_assignment"
|
|
@@ -72,6 +75,12 @@ require_relative "evilution/mutator/operator/index_assignment_removal"
|
|
|
72
75
|
require_relative "evilution/mutator/operator/pattern_matching_guard"
|
|
73
76
|
require_relative "evilution/mutator/operator/pattern_matching_alternative"
|
|
74
77
|
require_relative "evilution/mutator/operator/pattern_matching_array"
|
|
78
|
+
require_relative "evilution/mutator/operator/collection_return"
|
|
79
|
+
require_relative "evilution/mutator/operator/scalar_return"
|
|
80
|
+
require_relative "evilution/mutator/operator/yield_statement"
|
|
81
|
+
require_relative "evilution/mutator/operator/splat_operator"
|
|
82
|
+
require_relative "evilution/mutator/operator/defined_check"
|
|
83
|
+
require_relative "evilution/mutator/operator/regex_capture"
|
|
75
84
|
require_relative "evilution/mutator/registry"
|
|
76
85
|
require_relative "evilution/equivalent"
|
|
77
86
|
require_relative "evilution/equivalent/heuristic"
|
|
@@ -82,6 +91,7 @@ require_relative "evilution/isolation/in_process"
|
|
|
82
91
|
require_relative "evilution/parallel/pool"
|
|
83
92
|
require_relative "evilution/session"
|
|
84
93
|
require_relative "evilution/session/store"
|
|
94
|
+
require_relative "evilution/session/diff"
|
|
85
95
|
require_relative "evilution/git"
|
|
86
96
|
require_relative "evilution/git/changed_files"
|
|
87
97
|
require_relative "evilution/integration"
|
|
@@ -99,6 +109,7 @@ require_relative "evilution/spec_resolver"
|
|
|
99
109
|
require_relative "evilution/baseline"
|
|
100
110
|
require_relative "evilution/cache"
|
|
101
111
|
require_relative "evilution/cli"
|
|
112
|
+
require_relative "evilution/disable_comment"
|
|
102
113
|
require_relative "evilution/runner"
|
|
103
114
|
|
|
104
115
|
module Evilution
|
data/script/memory_check
CHANGED
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
|
|
4
4
|
require_relative "../lib/evilution"
|
|
5
5
|
require_relative "../lib/evilution/memory/leak_check"
|
|
6
|
+
require_relative "../lib/evilution/integration/rspec"
|
|
6
7
|
|
|
7
8
|
FIXTURE = File.expand_path("../spec/support/fixtures/simple_class.rb", __dir__)
|
|
9
|
+
FIXTURE_SPEC = File.expand_path("../spec/support/fixtures/simple_class_spec.rb", __dir__)
|
|
10
|
+
COMPLEX_FIXTURE = File.expand_path("../lib/evilution/config.rb", __dir__)
|
|
11
|
+
COMPLEX_FIXTURE_SPEC = File.expand_path("../spec/evilution/config_spec.rb", __dir__)
|
|
8
12
|
ITERATIONS = Integer(ENV.fetch("MEMORY_CHECK_ITERATIONS", 50))
|
|
9
13
|
MAX_GROWTH_KB = Integer(ENV.fetch("MEMORY_CHECK_MAX_GROWTH_KB", 10_240))
|
|
10
14
|
|
|
@@ -90,5 +94,23 @@ if mutations.size >= 2
|
|
|
90
94
|
end
|
|
91
95
|
end
|
|
92
96
|
|
|
97
|
+
# 5. RSpec integration per-mutation with complex fixture
|
|
98
|
+
# Uses Config (227 LOC, 73 specs, 564 mutations) for realistic per-mutation load:
|
|
99
|
+
# more ExampleGroup subclasses, deeper spec nesting, heavier metadata.
|
|
100
|
+
complex_parser = Evilution::AST::Parser.new
|
|
101
|
+
complex_registry = Evilution::Mutator::Registry.default
|
|
102
|
+
complex_subjects = complex_parser.call(COMPLEX_FIXTURE)
|
|
103
|
+
complex_mutations = complex_subjects.flat_map { |s| complex_registry.mutations_for(s) }
|
|
104
|
+
|
|
105
|
+
integration = Evilution::Integration::RSpec.new(test_files: [COMPLEX_FIXTURE_SPEC])
|
|
106
|
+
|
|
107
|
+
all_passed &= run_check("RSpec integration per-mutation (Config)", iterations: 20, max_growth_kb: 20_480) do
|
|
108
|
+
mutation = complex_mutations.sample
|
|
109
|
+
result = integration.call(mutation)
|
|
110
|
+
raise "RSpec integration memory check failed: #{result[:error]}" if result[:error]
|
|
111
|
+
|
|
112
|
+
result
|
|
113
|
+
end
|
|
114
|
+
|
|
93
115
|
puts all_passed ? "All memory checks passed." : "Some memory checks failed!"
|
|
94
116
|
exit(all_passed ? 0 : 1)
|
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.19.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-
|
|
11
|
+
date: 2026-04-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: diff-lcs
|
|
@@ -86,11 +86,13 @@ files:
|
|
|
86
86
|
- lib/evilution/ast/pattern/filter.rb
|
|
87
87
|
- lib/evilution/ast/pattern/matcher.rb
|
|
88
88
|
- lib/evilution/ast/pattern/parser.rb
|
|
89
|
+
- lib/evilution/ast/sorbet_sig_detector.rb
|
|
89
90
|
- lib/evilution/ast/source_surgeon.rb
|
|
90
91
|
- lib/evilution/baseline.rb
|
|
91
92
|
- lib/evilution/cache.rb
|
|
92
93
|
- lib/evilution/cli.rb
|
|
93
94
|
- lib/evilution/config.rb
|
|
95
|
+
- lib/evilution/disable_comment.rb
|
|
94
96
|
- lib/evilution/equivalent.rb
|
|
95
97
|
- lib/evilution/equivalent/detector.rb
|
|
96
98
|
- lib/evilution/equivalent/heuristic.rb
|
|
@@ -136,11 +138,13 @@ files:
|
|
|
136
138
|
- lib/evilution/mutator/operator/break_statement.rb
|
|
137
139
|
- lib/evilution/mutator/operator/class_variable_write.rb
|
|
138
140
|
- lib/evilution/mutator/operator/collection_replacement.rb
|
|
141
|
+
- lib/evilution/mutator/operator/collection_return.rb
|
|
139
142
|
- lib/evilution/mutator/operator/comparison_replacement.rb
|
|
140
143
|
- lib/evilution/mutator/operator/compound_assignment.rb
|
|
141
144
|
- lib/evilution/mutator/operator/conditional_branch.rb
|
|
142
145
|
- lib/evilution/mutator/operator/conditional_flip.rb
|
|
143
146
|
- lib/evilution/mutator/operator/conditional_negation.rb
|
|
147
|
+
- lib/evilution/mutator/operator/defined_check.rb
|
|
144
148
|
- lib/evilution/mutator/operator/ensure_removal.rb
|
|
145
149
|
- lib/evilution/mutator/operator/explicit_super_mutation.rb
|
|
146
150
|
- lib/evilution/mutator/operator/float_literal.rb
|
|
@@ -152,10 +156,12 @@ files:
|
|
|
152
156
|
- lib/evilution/mutator/operator/inline_rescue.rb
|
|
153
157
|
- lib/evilution/mutator/operator/instance_variable_write.rb
|
|
154
158
|
- lib/evilution/mutator/operator/integer_literal.rb
|
|
159
|
+
- lib/evilution/mutator/operator/keyword_argument.rb
|
|
155
160
|
- lib/evilution/mutator/operator/local_variable_assignment.rb
|
|
156
161
|
- lib/evilution/mutator/operator/method_body_replacement.rb
|
|
157
162
|
- lib/evilution/mutator/operator/method_call_removal.rb
|
|
158
163
|
- lib/evilution/mutator/operator/mixin_removal.rb
|
|
164
|
+
- lib/evilution/mutator/operator/multiple_assignment.rb
|
|
159
165
|
- lib/evilution/mutator/operator/negation_insertion.rb
|
|
160
166
|
- lib/evilution/mutator/operator/next_statement.rb
|
|
161
167
|
- lib/evilution/mutator/operator/nil_replacement.rb
|
|
@@ -165,19 +171,24 @@ files:
|
|
|
165
171
|
- lib/evilution/mutator/operator/range_replacement.rb
|
|
166
172
|
- lib/evilution/mutator/operator/receiver_replacement.rb
|
|
167
173
|
- lib/evilution/mutator/operator/redo_statement.rb
|
|
174
|
+
- lib/evilution/mutator/operator/regex_capture.rb
|
|
168
175
|
- lib/evilution/mutator/operator/regexp_mutation.rb
|
|
169
176
|
- lib/evilution/mutator/operator/rescue_body_replacement.rb
|
|
170
177
|
- lib/evilution/mutator/operator/rescue_removal.rb
|
|
171
178
|
- lib/evilution/mutator/operator/return_value_removal.rb
|
|
179
|
+
- lib/evilution/mutator/operator/scalar_return.rb
|
|
172
180
|
- lib/evilution/mutator/operator/send_mutation.rb
|
|
181
|
+
- lib/evilution/mutator/operator/splat_operator.rb
|
|
173
182
|
- lib/evilution/mutator/operator/statement_deletion.rb
|
|
174
183
|
- lib/evilution/mutator/operator/string_literal.rb
|
|
175
184
|
- lib/evilution/mutator/operator/superclass_removal.rb
|
|
176
185
|
- lib/evilution/mutator/operator/symbol_literal.rb
|
|
186
|
+
- lib/evilution/mutator/operator/yield_statement.rb
|
|
177
187
|
- lib/evilution/mutator/operator/zsuper_removal.rb
|
|
178
188
|
- lib/evilution/mutator/registry.rb
|
|
179
189
|
- lib/evilution/parallel.rb
|
|
180
190
|
- lib/evilution/parallel/pool.rb
|
|
191
|
+
- lib/evilution/parallel/work_queue.rb
|
|
181
192
|
- lib/evilution/reporter.rb
|
|
182
193
|
- lib/evilution/reporter/cli.rb
|
|
183
194
|
- lib/evilution/reporter/html.rb
|
|
@@ -189,6 +200,7 @@ files:
|
|
|
189
200
|
- lib/evilution/result/summary.rb
|
|
190
201
|
- lib/evilution/runner.rb
|
|
191
202
|
- lib/evilution/session.rb
|
|
203
|
+
- lib/evilution/session/diff.rb
|
|
192
204
|
- lib/evilution/session/store.rb
|
|
193
205
|
- lib/evilution/spec_resolver.rb
|
|
194
206
|
- lib/evilution/subject.rb
|