evilution 0.23.0 → 0.25.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/CHANGELOG.md +51 -0
- data/README.md +81 -4
- data/exe/evil +6 -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 +78 -0
- data/lib/evilution/cli/parser/file_args.rb +41 -0
- data/lib/evilution/cli/parser/options_builder.rb +123 -0
- data/lib/evilution/cli/parser/stdin_reader.rb +28 -0
- data/lib/evilution/cli/parser.rb +27 -196
- 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/normalizer.rb +106 -0
- data/lib/evilution/compare/record.rb +16 -0
- data/lib/evilution/compare.rb +15 -0
- data/lib/evilution/config.rb +178 -3
- data/lib/evilution/example_filter.rb +143 -0
- data/lib/evilution/integration/base.rb +11 -57
- data/lib/evilution/integration/crash_detector.rb +5 -2
- data/lib/evilution/integration/minitest.rb +25 -7
- data/lib/evilution/integration/minitest_crash_detector.rb +5 -2
- data/lib/evilution/integration/rspec.rb +99 -12
- data/lib/evilution/isolation/fork.rb +26 -0
- data/lib/evilution/isolation/in_process.rb +1 -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 +38 -11
- data/lib/evilution/reporter/html/assets/style.css +85 -0
- data/lib/evilution/reporter/html/baseline_keys.rb +28 -0
- data/lib/evilution/reporter/html/diff_formatter.rb +27 -0
- data/lib/evilution/reporter/html/escape.rb +12 -0
- data/lib/evilution/reporter/html/namespace.rb +11 -0
- data/lib/evilution/reporter/html/report.rb +68 -0
- data/lib/evilution/reporter/html/section.rb +21 -0
- data/lib/evilution/reporter/html/sections/baseline_comparison.rb +46 -0
- data/lib/evilution/reporter/html/sections/error_details.rb +30 -0
- data/lib/evilution/reporter/html/sections/error_entry.rb +22 -0
- data/lib/evilution/reporter/html/sections/file_section.rb +62 -0
- data/lib/evilution/reporter/html/sections/header.rb +29 -0
- data/lib/evilution/reporter/html/sections/mutation_map.rb +32 -0
- data/lib/evilution/reporter/html/sections/neutral_details.rb +25 -0
- data/lib/evilution/reporter/html/sections/summary_cards.rb +11 -0
- data/lib/evilution/reporter/html/sections/survived_details.rb +35 -0
- data/lib/evilution/reporter/html/sections/survived_entry.rb +36 -0
- data/lib/evilution/reporter/html/sections/truncation_notice.rb +17 -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/sections.rb +4 -0
- data/lib/evilution/reporter/html/stylesheet.rb +14 -0
- data/lib/evilution/reporter/html/templates/baseline_comparison.html.erb +8 -0
- data/lib/evilution/reporter/html/templates/error_details.html.erb +6 -0
- data/lib/evilution/reporter/html/templates/error_entry.html.erb +10 -0
- data/lib/evilution/reporter/html/templates/file_section.html.erb +12 -0
- data/lib/evilution/reporter/html/templates/header.html.erb +4 -0
- data/lib/evilution/reporter/html/templates/mutation_map.html.erb +6 -0
- data/lib/evilution/reporter/html/templates/neutral_details.html.erb +14 -0
- data/lib/evilution/reporter/html/templates/report.html.erb +17 -0
- data/lib/evilution/reporter/html/templates/summary_cards.html.erb +26 -0
- data/lib/evilution/reporter/html/templates/survived_details.html.erb +21 -0
- data/lib/evilution/reporter/html/templates/survived_entry.html.erb +8 -0
- data/lib/evilution/reporter/html/templates/truncation_notice.html.erb +1 -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/html.rb +11 -390
- data/lib/evilution/reporter/json.rb +19 -9
- 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 +9 -1
- data/lib/evilution/result/summary.rb +21 -1
- data/lib/evilution/runner/baseline_runner.rb +92 -0
- data/lib/evilution/runner/diagnostics.rb +105 -0
- data/lib/evilution/runner/isolation_resolver.rb +134 -0
- data/lib/evilution/runner/mutation_executor.rb +325 -0
- data/lib/evilution/runner/mutation_planner.rb +126 -0
- data/lib/evilution/runner/report_publisher.rb +60 -0
- data/lib/evilution/runner/subject_pipeline.rb +121 -0
- data/lib/evilution/runner.rb +61 -692
- 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 +75 -2
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "prism"
|
|
5
|
+
require_relative "../evilution"
|
|
6
|
+
|
|
7
|
+
# Content-hash-keyed LRU of Prism::ParseResult. Different source bytes always
|
|
8
|
+
# yield a different key, so the cache is valid for the lifetime of the process.
|
|
9
|
+
class Evilution::SourceAstCache
|
|
10
|
+
DEFAULT_MAX_ENTRIES = 50
|
|
11
|
+
private_constant :DEFAULT_MAX_ENTRIES
|
|
12
|
+
|
|
13
|
+
def initialize(max_entries: DEFAULT_MAX_ENTRIES)
|
|
14
|
+
@max_entries = max_entries
|
|
15
|
+
@entries = {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def fetch(source)
|
|
19
|
+
return Prism.parse(source) if @max_entries <= 0
|
|
20
|
+
|
|
21
|
+
key = Digest::SHA256.digest(source)
|
|
22
|
+
if @entries.key?(key)
|
|
23
|
+
result = @entries.delete(key)
|
|
24
|
+
@entries[key] = result
|
|
25
|
+
return result
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
result = Prism.parse(source)
|
|
29
|
+
@entries[key] = result
|
|
30
|
+
evict_until_within_bounds
|
|
31
|
+
result
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def evict_until_within_bounds
|
|
37
|
+
@entries.shift while @entries.length > @max_entries
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
require_relative "../evilution"
|
|
5
|
+
|
|
6
|
+
class Evilution::SpecAstCache
|
|
7
|
+
Block = Struct.new(:kind, :line, :end_line, :body_text)
|
|
8
|
+
|
|
9
|
+
BLOCK_METHODS = %i[
|
|
10
|
+
describe context fcontext xcontext
|
|
11
|
+
it fit xit specify
|
|
12
|
+
before after
|
|
13
|
+
].freeze
|
|
14
|
+
private_constant :BLOCK_METHODS
|
|
15
|
+
|
|
16
|
+
DEFAULT_MAX_FILES = 50
|
|
17
|
+
DEFAULT_MAX_BLOCKS = 10_000
|
|
18
|
+
private_constant :DEFAULT_MAX_FILES, :DEFAULT_MAX_BLOCKS
|
|
19
|
+
|
|
20
|
+
def initialize(max_files: DEFAULT_MAX_FILES, max_blocks: DEFAULT_MAX_BLOCKS)
|
|
21
|
+
@max_files = max_files
|
|
22
|
+
@max_blocks = max_blocks
|
|
23
|
+
@entries = {}
|
|
24
|
+
@total_blocks = 0
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def fetch(path)
|
|
28
|
+
if @entries.key?(path)
|
|
29
|
+
blocks = @entries.delete(path)
|
|
30
|
+
@entries[path] = blocks
|
|
31
|
+
return blocks
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
blocks = parse(path)
|
|
35
|
+
insert(path, blocks)
|
|
36
|
+
blocks
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def cached?(path)
|
|
40
|
+
@entries.key?(path)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def insert(path, blocks)
|
|
46
|
+
@entries[path] = blocks
|
|
47
|
+
@total_blocks += blocks.length
|
|
48
|
+
evict_until_within_bounds
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def evict_until_within_bounds
|
|
52
|
+
while @entries.length > @max_files || @total_blocks > @max_blocks
|
|
53
|
+
break if @entries.empty?
|
|
54
|
+
|
|
55
|
+
oldest_path = @entries.keys.first
|
|
56
|
+
evicted = @entries.delete(oldest_path)
|
|
57
|
+
@total_blocks -= evicted.length
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def parse(path)
|
|
62
|
+
raise Evilution::ParseError.new("file not found: #{path}", file: path) unless File.exist?(path)
|
|
63
|
+
|
|
64
|
+
source = read_source(path)
|
|
65
|
+
result = Prism.parse(source)
|
|
66
|
+
|
|
67
|
+
if result.failure?
|
|
68
|
+
raise Evilution::ParseError.new(
|
|
69
|
+
"failed to parse #{path}: #{result.errors.map(&:message).join(", ")}",
|
|
70
|
+
file: path
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
comment_ranges = result.comments
|
|
75
|
+
.map { |c| c.location.start_offset...c.location.end_offset }
|
|
76
|
+
.sort_by(&:begin)
|
|
77
|
+
collector = BlockCollector.new(source, comment_ranges)
|
|
78
|
+
collector.visit(result.value)
|
|
79
|
+
collector.blocks
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def read_source(path)
|
|
83
|
+
File.read(path)
|
|
84
|
+
rescue SystemCallError => e
|
|
85
|
+
raise Evilution::ParseError.new("cannot read #{path}: #{e.message}", file: path)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class BlockCollector < Prism::Visitor
|
|
89
|
+
attr_reader :blocks
|
|
90
|
+
|
|
91
|
+
def initialize(source, comment_ranges)
|
|
92
|
+
@source = source
|
|
93
|
+
@comment_ranges = comment_ranges
|
|
94
|
+
@blocks = []
|
|
95
|
+
super()
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def visit_call_node(node)
|
|
99
|
+
@blocks << build_block(node) if block_method?(node)
|
|
100
|
+
super
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def block_method?(node)
|
|
106
|
+
BLOCK_METHODS.include?(node.name) && node.block
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def build_block(node)
|
|
110
|
+
location = node.location
|
|
111
|
+
Block.new(
|
|
112
|
+
node.name,
|
|
113
|
+
location.start_line,
|
|
114
|
+
location.end_line,
|
|
115
|
+
extract_body_text(node)
|
|
116
|
+
)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def extract_body_text(node)
|
|
120
|
+
block = node.block
|
|
121
|
+
return "" unless block
|
|
122
|
+
|
|
123
|
+
body = block.body
|
|
124
|
+
return "" unless body
|
|
125
|
+
|
|
126
|
+
start_off = body.location.start_offset
|
|
127
|
+
end_off = body.location.end_offset
|
|
128
|
+
slice = @source.byteslice(start_off, end_off - start_off) || ""
|
|
129
|
+
stripped = strip_comments(slice, start_off)
|
|
130
|
+
stripped.downcase
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def strip_comments(slice, base_offset)
|
|
134
|
+
return slice if @comment_ranges.empty?
|
|
135
|
+
|
|
136
|
+
ranges = comment_ranges_within(base_offset, base_offset + slice.bytesize)
|
|
137
|
+
return slice if ranges.empty?
|
|
138
|
+
|
|
139
|
+
result = +""
|
|
140
|
+
cursor = base_offset
|
|
141
|
+
ranges.each do |range|
|
|
142
|
+
result << @source.byteslice(cursor, range.begin - cursor)
|
|
143
|
+
cursor = range.end
|
|
144
|
+
end
|
|
145
|
+
result << @source.byteslice(cursor, base_offset + slice.bytesize - cursor)
|
|
146
|
+
result
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def comment_ranges_within(start_off, end_off)
|
|
150
|
+
lower = @comment_ranges.bsearch_index { |r| r.begin >= start_off }
|
|
151
|
+
return [] unless lower
|
|
152
|
+
|
|
153
|
+
result = []
|
|
154
|
+
idx = lower
|
|
155
|
+
while idx < @comment_ranges.length
|
|
156
|
+
range = @comment_ranges[idx]
|
|
157
|
+
break if range.begin >= end_off
|
|
158
|
+
|
|
159
|
+
result << range if range.end <= end_off
|
|
160
|
+
idx += 1
|
|
161
|
+
end
|
|
162
|
+
result
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
private_constant :BlockCollector
|
|
166
|
+
end
|
|
@@ -10,11 +10,12 @@ class Evilution::SpecResolver
|
|
|
10
10
|
@request_dir = request_dir
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def call(source_path)
|
|
13
|
+
def call(source_path, spec_pattern: nil)
|
|
14
14
|
return nil if source_path.nil? || source_path.empty?
|
|
15
15
|
|
|
16
16
|
normalized = normalize_path(source_path)
|
|
17
17
|
candidates = candidate_test_paths(normalized)
|
|
18
|
+
candidates = filter_by_pattern(candidates, spec_pattern) if spec_pattern
|
|
18
19
|
candidates.find { |path| File.exist?(path) }
|
|
19
20
|
end
|
|
20
21
|
|
|
@@ -24,6 +25,10 @@ class Evilution::SpecResolver
|
|
|
24
25
|
|
|
25
26
|
private
|
|
26
27
|
|
|
28
|
+
def filter_by_pattern(candidates, pattern)
|
|
29
|
+
candidates.select { |path| File.fnmatch?(pattern, path, File::FNM_PATHNAME | File::FNM_EXTGLOB) }
|
|
30
|
+
end
|
|
31
|
+
|
|
27
32
|
def normalize_path(path)
|
|
28
33
|
path = path.delete_prefix("./")
|
|
29
34
|
path = path.delete_prefix("#{Dir.pwd}/") if path.start_with?("/")
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "spec_resolver"
|
|
4
|
+
|
|
5
|
+
class Evilution::SpecSelector
|
|
6
|
+
def initialize(spec_files: [], spec_mappings: {}, spec_pattern: nil, spec_resolver: Evilution::SpecResolver.new)
|
|
7
|
+
@spec_files = Array(spec_files)
|
|
8
|
+
@spec_mappings = spec_mappings || {}
|
|
9
|
+
@spec_pattern = spec_pattern
|
|
10
|
+
@spec_resolver = spec_resolver
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call(source_path)
|
|
14
|
+
return @spec_files unless @spec_files.empty?
|
|
15
|
+
|
|
16
|
+
mapped = mapping_for(source_path)
|
|
17
|
+
if mapped
|
|
18
|
+
existing = mapped.select { |path| File.exist?(path) }
|
|
19
|
+
return existing unless existing.empty?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
resolved = @spec_resolver.call(source_path, spec_pattern: @spec_pattern)
|
|
23
|
+
resolved ? [resolved] : nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def mapping_for(source_path)
|
|
29
|
+
@spec_mappings[normalize(source_path)]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def normalize(path)
|
|
33
|
+
return path if path.nil?
|
|
34
|
+
|
|
35
|
+
normalized = path.to_s
|
|
36
|
+
normalized = normalized.delete_prefix("#{Dir.pwd}/") if normalized.start_with?("/")
|
|
37
|
+
normalized.delete_prefix("./")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -21,9 +21,15 @@ module Evilution::TempDirTracker
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def self.cleanup_all
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
# Trap-safe: Signal.trap handlers forbid Monitor#synchronize, so both the
|
|
25
|
+
# snapshot and the per-dir tracking removal fall back to a lock-free path
|
|
26
|
+
# when ThreadError is raised. Successful removals drop the entry from
|
|
27
|
+
# @dirs; failures stay tracked so a later cleanup can retry.
|
|
28
|
+
snapshot_tracked_dirs.each do |d|
|
|
29
|
+
FileUtils.rm_rf(d)
|
|
30
|
+
remove_from_tracking(d)
|
|
31
|
+
rescue StandardError
|
|
32
|
+
nil
|
|
27
33
|
end
|
|
28
34
|
end
|
|
29
35
|
|
|
@@ -36,4 +42,18 @@ module Evilution::TempDirTracker
|
|
|
36
42
|
@at_exit_registered = true
|
|
37
43
|
end
|
|
38
44
|
private_class_method :register_at_exit
|
|
45
|
+
|
|
46
|
+
def self.snapshot_tracked_dirs
|
|
47
|
+
@monitor.synchronize { @dirs.to_a }
|
|
48
|
+
rescue ThreadError
|
|
49
|
+
@dirs.to_a
|
|
50
|
+
end
|
|
51
|
+
private_class_method :snapshot_tracked_dirs
|
|
52
|
+
|
|
53
|
+
def self.remove_from_tracking(dir)
|
|
54
|
+
@monitor.synchronize { @dirs.delete(dir) }
|
|
55
|
+
rescue ThreadError
|
|
56
|
+
@dirs.delete(dir)
|
|
57
|
+
end
|
|
58
|
+
private_class_method :remove_from_tracking
|
|
39
59
|
end
|
data/lib/evilution/version.rb
CHANGED
data/script/memory_check
CHANGED
|
@@ -7,8 +7,8 @@ require_relative "../lib/evilution/integration/rspec"
|
|
|
7
7
|
|
|
8
8
|
FIXTURE = File.expand_path("../spec/support/fixtures/simple_class.rb", __dir__)
|
|
9
9
|
FIXTURE_SPEC = File.expand_path("../spec/support/fixtures/simple_class_spec.rb", __dir__)
|
|
10
|
-
COMPLEX_FIXTURE = File.expand_path("../
|
|
11
|
-
COMPLEX_FIXTURE_SPEC = File.expand_path("../spec/
|
|
10
|
+
COMPLEX_FIXTURE = File.expand_path("../spec/support/fixtures/memory_check/target.rb", __dir__)
|
|
11
|
+
COMPLEX_FIXTURE_SPEC = File.expand_path("../spec/support/fixtures/memory_check/target_examples.rb", __dir__)
|
|
12
12
|
ITERATIONS = Integer(ENV.fetch("MEMORY_CHECK_ITERATIONS", 50))
|
|
13
13
|
MAX_GROWTH_KB = Integer(ENV.fetch("MEMORY_CHECK_MAX_GROWTH_KB", 10_240))
|
|
14
14
|
|
|
@@ -95,8 +95,10 @@ if mutations.size >= 2
|
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
# 5. RSpec integration per-mutation with complex fixture
|
|
98
|
-
# Uses
|
|
99
|
-
#
|
|
98
|
+
# Uses a frozen snapshot under spec/support/fixtures/memory_check/ for
|
|
99
|
+
# realistic per-mutation load: deep spec nesting, heavy metadata. Snapshot
|
|
100
|
+
# is intentional - decoupling the budget from project file growth so this
|
|
101
|
+
# check measures integration plumbing memory, not project size.
|
|
100
102
|
complex_parser = Evilution::AST::Parser.new
|
|
101
103
|
complex_registry = Evilution::Mutator::Registry.default
|
|
102
104
|
complex_subjects = complex_parser.call(COMPLEX_FIXTURE)
|
|
@@ -105,7 +107,7 @@ complex_mutations = complex_subjects.flat_map { |s| complex_registry.mutations_f
|
|
|
105
107
|
integration = Evilution::Integration::RSpec.new(test_files: [COMPLEX_FIXTURE_SPEC])
|
|
106
108
|
|
|
107
109
|
# Budget is generous: per-mutation require() adds the file to $LOADED_FEATURES and
|
|
108
|
-
# accumulates constant-redefinition warnings. Local runs land around 20
|
|
110
|
+
# accumulates constant-redefinition warnings. Local runs land around 20 MB; CI
|
|
109
111
|
# varies up to ~30 MB depending on Ruby build and GC pressure, so we leave headroom.
|
|
110
112
|
all_passed &= run_check("RSpec integration per-mutation (Config)", iterations: 20, max_growth_kb: 40_960) do
|
|
111
113
|
mutation = complex_mutations.sample
|
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.25.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-04-
|
|
11
|
+
date: 2026-04-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: diff-lcs
|
|
@@ -56,6 +56,7 @@ description: Evilution is a mutation testing tool for Ruby. It validates test su
|
|
|
56
56
|
email:
|
|
57
57
|
- denis.kiselyov@gmail.com
|
|
58
58
|
executables:
|
|
59
|
+
- evil
|
|
59
60
|
- evilution
|
|
60
61
|
extensions: []
|
|
61
62
|
extra_rdoc_files: []
|
|
@@ -83,6 +84,7 @@ files:
|
|
|
83
84
|
- docs/ast_pattern_syntax.md
|
|
84
85
|
- docs/isolation.md
|
|
85
86
|
- docs/mutation_density_benchmark.md
|
|
87
|
+
- exe/evil
|
|
86
88
|
- exe/evilution
|
|
87
89
|
- lib/evilution.rb
|
|
88
90
|
- lib/evilution/ast.rb
|
|
@@ -99,6 +101,7 @@ files:
|
|
|
99
101
|
- lib/evilution/cli.rb
|
|
100
102
|
- lib/evilution/cli/command.rb
|
|
101
103
|
- lib/evilution/cli/commands.rb
|
|
104
|
+
- lib/evilution/cli/commands/compare.rb
|
|
102
105
|
- lib/evilution/cli/commands/environment_show.rb
|
|
103
106
|
- lib/evilution/cli/commands/init.rb
|
|
104
107
|
- lib/evilution/cli/commands/mcp.rb
|
|
@@ -114,7 +117,12 @@ files:
|
|
|
114
117
|
- lib/evilution/cli/dispatcher.rb
|
|
115
118
|
- lib/evilution/cli/parsed_args.rb
|
|
116
119
|
- lib/evilution/cli/parser.rb
|
|
120
|
+
- lib/evilution/cli/parser/command_extractor.rb
|
|
121
|
+
- lib/evilution/cli/parser/file_args.rb
|
|
122
|
+
- lib/evilution/cli/parser/options_builder.rb
|
|
123
|
+
- lib/evilution/cli/parser/stdin_reader.rb
|
|
117
124
|
- lib/evilution/cli/printers.rb
|
|
125
|
+
- lib/evilution/cli/printers/compare.rb
|
|
118
126
|
- lib/evilution/cli/printers/environment.rb
|
|
119
127
|
- lib/evilution/cli/printers/session_detail.rb
|
|
120
128
|
- lib/evilution/cli/printers/session_diff.rb
|
|
@@ -123,6 +131,12 @@ files:
|
|
|
123
131
|
- lib/evilution/cli/printers/tests_list.rb
|
|
124
132
|
- lib/evilution/cli/printers/util_mutation.rb
|
|
125
133
|
- lib/evilution/cli/result.rb
|
|
134
|
+
- lib/evilution/compare.rb
|
|
135
|
+
- lib/evilution/compare/categorizer.rb
|
|
136
|
+
- lib/evilution/compare/detector.rb
|
|
137
|
+
- lib/evilution/compare/fingerprint.rb
|
|
138
|
+
- lib/evilution/compare/normalizer.rb
|
|
139
|
+
- lib/evilution/compare/record.rb
|
|
126
140
|
- lib/evilution/config.rb
|
|
127
141
|
- lib/evilution/disable_comment.rb
|
|
128
142
|
- lib/evilution/equivalent.rb
|
|
@@ -135,6 +149,7 @@ files:
|
|
|
135
149
|
- lib/evilution/equivalent/heuristic/method_body_nil.rb
|
|
136
150
|
- lib/evilution/equivalent/heuristic/noop_source.rb
|
|
137
151
|
- lib/evilution/equivalent/heuristic/void_context.rb
|
|
152
|
+
- lib/evilution/example_filter.rb
|
|
138
153
|
- lib/evilution/git.rb
|
|
139
154
|
- lib/evilution/git/changed_files.rb
|
|
140
155
|
- lib/evilution/hooks.rb
|
|
@@ -152,6 +167,12 @@ files:
|
|
|
152
167
|
- lib/evilution/mcp.rb
|
|
153
168
|
- lib/evilution/mcp/info_tool.rb
|
|
154
169
|
- lib/evilution/mcp/mutate_tool.rb
|
|
170
|
+
- lib/evilution/mcp/mutate_tool/config_builder.rb
|
|
171
|
+
- lib/evilution/mcp/mutate_tool/error_payload.rb
|
|
172
|
+
- lib/evilution/mcp/mutate_tool/option_parser.rb
|
|
173
|
+
- lib/evilution/mcp/mutate_tool/progress_streamer.rb
|
|
174
|
+
- lib/evilution/mcp/mutate_tool/report_trimmer.rb
|
|
175
|
+
- lib/evilution/mcp/mutate_tool/survived_enricher.rb
|
|
155
176
|
- lib/evilution/mcp/server.rb
|
|
156
177
|
- lib/evilution/mcp/session_diff_tool.rb
|
|
157
178
|
- lib/evilution/mcp/session_list_tool.rb
|
|
@@ -239,24 +260,76 @@ files:
|
|
|
239
260
|
- lib/evilution/parallel.rb
|
|
240
261
|
- lib/evilution/parallel/pool.rb
|
|
241
262
|
- lib/evilution/parallel/work_queue.rb
|
|
263
|
+
- lib/evilution/parallel_db_warning.rb
|
|
242
264
|
- lib/evilution/rails_detector.rb
|
|
243
265
|
- lib/evilution/related_spec_heuristic.rb
|
|
244
266
|
- lib/evilution/reporter.rb
|
|
245
267
|
- lib/evilution/reporter/cli.rb
|
|
246
268
|
- lib/evilution/reporter/html.rb
|
|
269
|
+
- lib/evilution/reporter/html/assets/style.css
|
|
270
|
+
- lib/evilution/reporter/html/baseline_keys.rb
|
|
271
|
+
- lib/evilution/reporter/html/diff_formatter.rb
|
|
272
|
+
- lib/evilution/reporter/html/escape.rb
|
|
273
|
+
- lib/evilution/reporter/html/namespace.rb
|
|
274
|
+
- lib/evilution/reporter/html/report.rb
|
|
275
|
+
- lib/evilution/reporter/html/section.rb
|
|
276
|
+
- lib/evilution/reporter/html/sections.rb
|
|
277
|
+
- lib/evilution/reporter/html/sections/baseline_comparison.rb
|
|
278
|
+
- lib/evilution/reporter/html/sections/error_details.rb
|
|
279
|
+
- lib/evilution/reporter/html/sections/error_entry.rb
|
|
280
|
+
- lib/evilution/reporter/html/sections/file_section.rb
|
|
281
|
+
- lib/evilution/reporter/html/sections/header.rb
|
|
282
|
+
- lib/evilution/reporter/html/sections/mutation_map.rb
|
|
283
|
+
- lib/evilution/reporter/html/sections/neutral_details.rb
|
|
284
|
+
- lib/evilution/reporter/html/sections/summary_cards.rb
|
|
285
|
+
- lib/evilution/reporter/html/sections/survived_details.rb
|
|
286
|
+
- lib/evilution/reporter/html/sections/survived_entry.rb
|
|
287
|
+
- lib/evilution/reporter/html/sections/truncation_notice.rb
|
|
288
|
+
- lib/evilution/reporter/html/sections/unparseable_details.rb
|
|
289
|
+
- lib/evilution/reporter/html/sections/unresolved_details.rb
|
|
290
|
+
- lib/evilution/reporter/html/stylesheet.rb
|
|
291
|
+
- lib/evilution/reporter/html/templates/baseline_comparison.html.erb
|
|
292
|
+
- lib/evilution/reporter/html/templates/error_details.html.erb
|
|
293
|
+
- lib/evilution/reporter/html/templates/error_entry.html.erb
|
|
294
|
+
- lib/evilution/reporter/html/templates/file_section.html.erb
|
|
295
|
+
- lib/evilution/reporter/html/templates/header.html.erb
|
|
296
|
+
- lib/evilution/reporter/html/templates/mutation_map.html.erb
|
|
297
|
+
- lib/evilution/reporter/html/templates/neutral_details.html.erb
|
|
298
|
+
- lib/evilution/reporter/html/templates/report.html.erb
|
|
299
|
+
- lib/evilution/reporter/html/templates/summary_cards.html.erb
|
|
300
|
+
- lib/evilution/reporter/html/templates/survived_details.html.erb
|
|
301
|
+
- lib/evilution/reporter/html/templates/survived_entry.html.erb
|
|
302
|
+
- lib/evilution/reporter/html/templates/truncation_notice.html.erb
|
|
303
|
+
- lib/evilution/reporter/html/templates/unparseable_details.html.erb
|
|
304
|
+
- lib/evilution/reporter/html/templates/unresolved_details.html.erb
|
|
247
305
|
- lib/evilution/reporter/json.rb
|
|
248
306
|
- lib/evilution/reporter/progress_bar.rb
|
|
249
307
|
- lib/evilution/reporter/suggestion.rb
|
|
308
|
+
- lib/evilution/reporter/suggestion/diff_helpers.rb
|
|
309
|
+
- lib/evilution/reporter/suggestion/registry.rb
|
|
310
|
+
- lib/evilution/reporter/suggestion/templates/generic.rb
|
|
311
|
+
- lib/evilution/reporter/suggestion/templates/minitest.rb
|
|
312
|
+
- lib/evilution/reporter/suggestion/templates/rspec.rb
|
|
250
313
|
- lib/evilution/result.rb
|
|
251
314
|
- lib/evilution/result/coverage_gap.rb
|
|
252
315
|
- lib/evilution/result/coverage_gap_grouper.rb
|
|
253
316
|
- lib/evilution/result/mutation_result.rb
|
|
254
317
|
- lib/evilution/result/summary.rb
|
|
255
318
|
- lib/evilution/runner.rb
|
|
319
|
+
- lib/evilution/runner/baseline_runner.rb
|
|
320
|
+
- lib/evilution/runner/diagnostics.rb
|
|
321
|
+
- lib/evilution/runner/isolation_resolver.rb
|
|
322
|
+
- lib/evilution/runner/mutation_executor.rb
|
|
323
|
+
- lib/evilution/runner/mutation_planner.rb
|
|
324
|
+
- lib/evilution/runner/report_publisher.rb
|
|
325
|
+
- lib/evilution/runner/subject_pipeline.rb
|
|
256
326
|
- lib/evilution/session.rb
|
|
257
327
|
- lib/evilution/session/diff.rb
|
|
258
328
|
- lib/evilution/session/store.rb
|
|
329
|
+
- lib/evilution/source_ast_cache.rb
|
|
330
|
+
- lib/evilution/spec_ast_cache.rb
|
|
259
331
|
- lib/evilution/spec_resolver.rb
|
|
332
|
+
- lib/evilution/spec_selector.rb
|
|
260
333
|
- lib/evilution/subject.rb
|
|
261
334
|
- lib/evilution/temp_dir_tracker.rb
|
|
262
335
|
- lib/evilution/version.rb
|