evilution 0.29.0 → 0.30.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 +54 -0
- data/.rubocop_todo.yml +7 -0
- data/CHANGELOG.md +42 -0
- data/README.md +194 -8
- data/docs/versioning.md +53 -0
- data/lib/evilution/ast/heredoc_span.rb +99 -0
- data/lib/evilution/baseline.rb +15 -2
- data/lib/evilution/cli/commands/compare.rb +13 -0
- data/lib/evilution/cli/parser/command_extractor.rb +3 -1
- data/lib/evilution/cli/parser/options_builder.rb +2 -2
- data/lib/evilution/config/file_loader.rb +40 -1
- data/lib/evilution/config.rb +11 -1
- data/lib/evilution/equivalent/heuristic/dead_code.rb +8 -1
- data/lib/evilution/feedback/setup_warning.rb +79 -0
- data/lib/evilution/gem_detector.rb +132 -0
- data/lib/evilution/integration/loading/body_call_neutralizer.rb +190 -0
- data/lib/evilution/integration/loading/mutation_applier.rb +20 -5
- data/lib/evilution/integration/loading/redefinition_recovery.rb +58 -1
- data/lib/evilution/integration/minitest.rb +37 -2
- data/lib/evilution/integration/rspec/result_builder.rb +20 -1
- data/lib/evilution/integration/rspec.rb +16 -1
- data/lib/evilution/isolation/fork.rb +77 -10
- data/lib/evilution/mcp/info_tool/response_formatter.rb +14 -1
- data/lib/evilution/mcp/info_tool.rb +3 -1
- data/lib/evilution/mcp/mutate_tool/option_parser.rb +1 -1
- data/lib/evilution/mcp/mutate_tool/report_trimmer.rb +58 -1
- data/lib/evilution/mcp/mutate_tool.rb +22 -3
- data/lib/evilution/mcp/session_tool.rb +7 -4
- data/lib/evilution/mcp.rb +6 -0
- data/lib/evilution/mutation.rb +13 -1
- data/lib/evilution/mutator/base.rb +49 -1
- data/lib/evilution/mutator/operator/argument_method_call_replacement.rb +59 -0
- data/lib/evilution/mutator/operator/block_param_removal.rb +32 -0
- data/lib/evilution/mutator/operator/explicit_super_mutation.rb +20 -2
- data/lib/evilution/mutator/operator/index_to_at.rb +13 -1
- data/lib/evilution/mutator/operator/last_expression_removal.rb +46 -0
- data/lib/evilution/mutator/operator/receiver_replacement.rb +29 -1
- data/lib/evilution/mutator/operator/rescue_removal.rb +59 -10
- data/lib/evilution/mutator/operator/splat_operator.rb +28 -1
- data/lib/evilution/mutator/operator/string_literal.rb +83 -6
- data/lib/evilution/mutator/registry.rb +2 -0
- data/lib/evilution/reporter/cli/line_formatters/error_rate_warning.rb +29 -0
- data/lib/evilution/reporter/cli/metrics_block.rb +2 -0
- data/lib/evilution/reporter/json.rb +2 -0
- data/lib/evilution/result/mutation_result.rb +12 -6
- data/lib/evilution/runner/baseline_runner.rb +5 -1
- data/lib/evilution/runner/isolation_resolver.rb +69 -8
- data/lib/evilution/runner/mutation_planner.rb +18 -1
- data/lib/evilution/session/schema.rb +44 -0
- data/lib/evilution/session/store.rb +5 -1
- data/lib/evilution/version.rb +1 -1
- data/lib/evilution.rb +2 -0
- data/schema/evilution.config.schema.json +205 -0
- data/script/build_runtime_snapshot +88 -0
- data/script/run_self_baseline +79 -0
- data/script/run_self_validation +54 -0
- metadata +15 -2
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/marinazzio/evilution/blob/master/schema/evilution.config.schema.json",
|
|
4
|
+
"title": "Evilution Configuration",
|
|
5
|
+
"description": "Schema for .evilution.yml / config/evilution.yml. Declaring `schema_version` opts the file into strict validation at load time.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema_version": {
|
|
10
|
+
"type": "integer",
|
|
11
|
+
"enum": [1],
|
|
12
|
+
"description": "Config schema version. Currently must be 1. Declaring it enables strict validation: unknown keys are rejected and a future schema_version is refused."
|
|
13
|
+
},
|
|
14
|
+
"timeout": {
|
|
15
|
+
"type": "integer",
|
|
16
|
+
"minimum": 1,
|
|
17
|
+
"default": 30,
|
|
18
|
+
"description": "Per-mutation timeout in seconds."
|
|
19
|
+
},
|
|
20
|
+
"format": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"enum": ["text", "json", "html"],
|
|
23
|
+
"default": "text",
|
|
24
|
+
"description": "Output format."
|
|
25
|
+
},
|
|
26
|
+
"target": {
|
|
27
|
+
"type": ["string", "null"],
|
|
28
|
+
"default": null,
|
|
29
|
+
"description": "Filter expression: method (Foo#bar), class (Foo), namespace wildcard (Foo*), method-type selector (Foo# / Foo.), descendants (descendants:Foo), or source glob (source:**/*.rb)."
|
|
30
|
+
},
|
|
31
|
+
"min_score": {
|
|
32
|
+
"type": "number",
|
|
33
|
+
"minimum": 0.0,
|
|
34
|
+
"maximum": 1.0,
|
|
35
|
+
"default": 0.0,
|
|
36
|
+
"description": "Minimum mutation score (0.0–1.0) for exit code 0."
|
|
37
|
+
},
|
|
38
|
+
"integration": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"enum": ["rspec", "minitest"],
|
|
41
|
+
"default": "rspec",
|
|
42
|
+
"description": "Test framework integration."
|
|
43
|
+
},
|
|
44
|
+
"verbose": {
|
|
45
|
+
"type": "boolean",
|
|
46
|
+
"default": false,
|
|
47
|
+
"description": "Verbose output (RSS/GC stats per phase, error details for errored mutations)."
|
|
48
|
+
},
|
|
49
|
+
"quiet": {
|
|
50
|
+
"type": "boolean",
|
|
51
|
+
"default": false,
|
|
52
|
+
"description": "Suppress output."
|
|
53
|
+
},
|
|
54
|
+
"jobs": {
|
|
55
|
+
"type": "integer",
|
|
56
|
+
"minimum": 1,
|
|
57
|
+
"default": 1,
|
|
58
|
+
"description": "Number of parallel workers."
|
|
59
|
+
},
|
|
60
|
+
"fail_fast": {
|
|
61
|
+
"type": ["integer", "null"],
|
|
62
|
+
"minimum": 1,
|
|
63
|
+
"default": null,
|
|
64
|
+
"description": "Stop after N surviving mutants. null or omitted = disabled."
|
|
65
|
+
},
|
|
66
|
+
"baseline": {
|
|
67
|
+
"type": "boolean",
|
|
68
|
+
"default": true,
|
|
69
|
+
"description": "Run baseline test suite first to detect pre-existing failures and mark those mutations :neutral."
|
|
70
|
+
},
|
|
71
|
+
"isolation": {
|
|
72
|
+
"type": "string",
|
|
73
|
+
"enum": ["auto", "fork", "in_process"],
|
|
74
|
+
"default": "auto",
|
|
75
|
+
"description": "Isolation strategy. auto selects fork for Rails projects."
|
|
76
|
+
},
|
|
77
|
+
"incremental": {
|
|
78
|
+
"type": "boolean",
|
|
79
|
+
"default": false,
|
|
80
|
+
"description": "Cache killed/timeout results across runs and skip them when source is unchanged."
|
|
81
|
+
},
|
|
82
|
+
"suggest_tests": {
|
|
83
|
+
"type": "boolean",
|
|
84
|
+
"default": false,
|
|
85
|
+
"description": "Generate concrete test code in survivor suggestions (RSpec or Minitest, matching integration)."
|
|
86
|
+
},
|
|
87
|
+
"progress": {
|
|
88
|
+
"type": "boolean",
|
|
89
|
+
"default": true,
|
|
90
|
+
"description": "TTY progress bar."
|
|
91
|
+
},
|
|
92
|
+
"save_session": {
|
|
93
|
+
"type": "boolean",
|
|
94
|
+
"default": false,
|
|
95
|
+
"description": "Save session JSON under .evilution/results/."
|
|
96
|
+
},
|
|
97
|
+
"line_ranges": {
|
|
98
|
+
"type": "object",
|
|
99
|
+
"default": {},
|
|
100
|
+
"description": "Per-file line-range constraints. Typically set via CLI; rare in YAML.",
|
|
101
|
+
"additionalProperties": true
|
|
102
|
+
},
|
|
103
|
+
"spec_files": {
|
|
104
|
+
"type": "array",
|
|
105
|
+
"items": { "type": "string" },
|
|
106
|
+
"default": [],
|
|
107
|
+
"description": "Explicit spec files to run. Bypasses auto-detection when non-empty."
|
|
108
|
+
},
|
|
109
|
+
"ignore_patterns": {
|
|
110
|
+
"type": "array",
|
|
111
|
+
"items": { "type": "string" },
|
|
112
|
+
"default": [],
|
|
113
|
+
"description": "AST patterns to skip during mutation generation. See docs/ast_pattern_syntax.md."
|
|
114
|
+
},
|
|
115
|
+
"show_disabled": {
|
|
116
|
+
"type": "boolean",
|
|
117
|
+
"default": false,
|
|
118
|
+
"description": "Report mutations skipped by `# evilution:disable` comments."
|
|
119
|
+
},
|
|
120
|
+
"baseline_session": {
|
|
121
|
+
"type": ["string", "null"],
|
|
122
|
+
"default": null,
|
|
123
|
+
"description": "Saved session file path for HTML report comparison."
|
|
124
|
+
},
|
|
125
|
+
"skip_heredoc_literals": {
|
|
126
|
+
"type": "boolean",
|
|
127
|
+
"default": false,
|
|
128
|
+
"description": "Skip all string literal mutations inside heredocs (recommended for Rails: heredoc SQL/templates rarely have coverage)."
|
|
129
|
+
},
|
|
130
|
+
"related_specs_heuristic": {
|
|
131
|
+
"type": "boolean",
|
|
132
|
+
"default": false,
|
|
133
|
+
"description": "When a mutation removes an `includes(...)` call, also run matching specs from spec/{requests,integration,features,system}."
|
|
134
|
+
},
|
|
135
|
+
"fallback_to_full_suite": {
|
|
136
|
+
"type": "boolean",
|
|
137
|
+
"default": false,
|
|
138
|
+
"description": "When no matching spec resolves, run the entire suite instead of marking the mutation :unresolved."
|
|
139
|
+
},
|
|
140
|
+
"preload": {
|
|
141
|
+
"type": ["string", "boolean", "null"],
|
|
142
|
+
"default": null,
|
|
143
|
+
"description": "Path to preload before forking workers. false to disable. null to auto-detect spec/rails_helper.rb -> spec/spec_helper.rb -> test/test_helper.rb for Rails projects."
|
|
144
|
+
},
|
|
145
|
+
"spec_mappings": {
|
|
146
|
+
"type": "object",
|
|
147
|
+
"default": {},
|
|
148
|
+
"description": "Custom mapping from source file to spec file(s). Keys are source paths; values are spec path strings or arrays of spec paths.",
|
|
149
|
+
"additionalProperties": {
|
|
150
|
+
"oneOf": [
|
|
151
|
+
{ "type": "string" },
|
|
152
|
+
{ "type": "array", "items": { "type": "string" } }
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
"spec_pattern": {
|
|
157
|
+
"type": ["string", "null"],
|
|
158
|
+
"default": null,
|
|
159
|
+
"description": "Glob restricting resolved spec candidates to files matching this pattern."
|
|
160
|
+
},
|
|
161
|
+
"example_targeting": {
|
|
162
|
+
"type": "boolean",
|
|
163
|
+
"default": true,
|
|
164
|
+
"description": "Per-mutation example-level targeting via body-token scan."
|
|
165
|
+
},
|
|
166
|
+
"example_targeting_fallback": {
|
|
167
|
+
"type": "string",
|
|
168
|
+
"enum": ["full_file", "unresolved"],
|
|
169
|
+
"default": "full_file",
|
|
170
|
+
"description": "Behavior when targeting finds no matching example."
|
|
171
|
+
},
|
|
172
|
+
"example_targeting_cache": {
|
|
173
|
+
"type": "object",
|
|
174
|
+
"default": { "max_files": 50, "max_blocks": 10000 },
|
|
175
|
+
"additionalProperties": false,
|
|
176
|
+
"description": "LRU cache bounds for the spec AST parser that powers example targeting.",
|
|
177
|
+
"properties": {
|
|
178
|
+
"max_files": { "type": "integer", "minimum": 1 },
|
|
179
|
+
"max_blocks": { "type": "integer", "minimum": 1 }
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
"quiet_children": {
|
|
183
|
+
"type": "boolean",
|
|
184
|
+
"default": false,
|
|
185
|
+
"description": "Redirect each worker's stdout/stderr to per-pid files under quiet_children_dir."
|
|
186
|
+
},
|
|
187
|
+
"quiet_children_dir": {
|
|
188
|
+
"type": "string",
|
|
189
|
+
"default": "tmp/evilution_children",
|
|
190
|
+
"description": "Directory for --quiet-children per-pid log files."
|
|
191
|
+
},
|
|
192
|
+
"profile": {
|
|
193
|
+
"type": "string",
|
|
194
|
+
"enum": ["default", "strict"],
|
|
195
|
+
"default": "default",
|
|
196
|
+
"description": "Operator profile. strict adds aggressive truthiness mutators on top of default."
|
|
197
|
+
},
|
|
198
|
+
"hooks": {
|
|
199
|
+
"type": "object",
|
|
200
|
+
"default": {},
|
|
201
|
+
"additionalProperties": { "type": "string" },
|
|
202
|
+
"description": "Lifecycle hooks: keys are event names (e.g. worker_process_start, mutation_insert_pre); values are paths to Ruby files returning a Proc."
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Builds tmp/evilution-runtime/ — a renamed copy of lib/ + exe/ where the
|
|
5
|
+
# Evilution module is rebranded to EvilutionRuntime. Used by bin/evilution-self
|
|
6
|
+
# so the mutation harness stays out of the namespace that subject `eval` clobbers.
|
|
7
|
+
# Throwaway dogfood infrastructure (EV-yyd8). No tests.
|
|
8
|
+
|
|
9
|
+
require "fileutils"
|
|
10
|
+
|
|
11
|
+
ROOT = File.expand_path("..", __dir__)
|
|
12
|
+
SNAPSHOT = File.join(ROOT, "tmp", "evilution-runtime")
|
|
13
|
+
SUBJECT_LIB = File.join(ROOT, "lib")
|
|
14
|
+
SUBJECT_EXE = File.join(ROOT, "exe")
|
|
15
|
+
|
|
16
|
+
def main
|
|
17
|
+
prepare_destination
|
|
18
|
+
copy_sources
|
|
19
|
+
rename_entrypoints
|
|
20
|
+
rewrite_sources
|
|
21
|
+
write_snapshot_sha
|
|
22
|
+
print_summary
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def prepare_destination
|
|
26
|
+
FileUtils.rm_rf(SNAPSHOT)
|
|
27
|
+
FileUtils.mkdir_p(SNAPSHOT)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def copy_sources
|
|
31
|
+
FileUtils.cp_r(SUBJECT_LIB, SNAPSHOT)
|
|
32
|
+
FileUtils.cp_r(SUBJECT_EXE, SNAPSHOT)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def rename_entrypoints
|
|
36
|
+
File.rename(
|
|
37
|
+
File.join(SNAPSHOT, "lib", "evilution.rb"),
|
|
38
|
+
File.join(SNAPSHOT, "lib", "evilution_runtime.rb")
|
|
39
|
+
)
|
|
40
|
+
File.rename(
|
|
41
|
+
File.join(SNAPSHOT, "lib", "evilution"),
|
|
42
|
+
File.join(SNAPSHOT, "lib", "evilution_runtime")
|
|
43
|
+
)
|
|
44
|
+
File.rename(
|
|
45
|
+
File.join(SNAPSHOT, "exe", "evilution"),
|
|
46
|
+
File.join(SNAPSHOT, "exe", "evilution_runtime")
|
|
47
|
+
)
|
|
48
|
+
# exe/evil alias not needed in snapshot
|
|
49
|
+
evil_alias = File.join(SNAPSHOT, "exe", "evil")
|
|
50
|
+
FileUtils.rm_f(evil_alias)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def rewrite_sources
|
|
54
|
+
files = Dir.glob(File.join(SNAPSHOT, "**", "*.rb")) +
|
|
55
|
+
[File.join(SNAPSHOT, "exe", "evilution_runtime")]
|
|
56
|
+
files.uniq!
|
|
57
|
+
rewritten = 0
|
|
58
|
+
files.each do |path|
|
|
59
|
+
next unless File.file?(path)
|
|
60
|
+
|
|
61
|
+
original = File.read(path)
|
|
62
|
+
updated = rewrite(original)
|
|
63
|
+
next if updated == original
|
|
64
|
+
|
|
65
|
+
File.write(path, updated)
|
|
66
|
+
rewritten += 1
|
|
67
|
+
end
|
|
68
|
+
@rewritten_count = rewritten
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def rewrite(source)
|
|
72
|
+
source
|
|
73
|
+
.gsub(/\bEvilution\b/, "EvilutionRuntime")
|
|
74
|
+
.gsub(%r{(require\s+)(["'])evilution(["'/])}, '\1\2evilution_runtime\3')
|
|
75
|
+
.gsub(%r{(require_relative\s+)(["'])((?:\.\./)*)evilution(["'/])}, '\1\2\3evilution_runtime\4')
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def write_snapshot_sha
|
|
79
|
+
sha = `git -C #{ROOT} rev-parse HEAD`.strip
|
|
80
|
+
File.write(File.join(SNAPSHOT, ".snapshot_sha"), "#{sha}\n")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def print_summary
|
|
84
|
+
puts "snapshot built at #{SNAPSHOT}"
|
|
85
|
+
puts "files rewritten: #{@rewritten_count}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
main
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Throwaway driver (EV-yyd8): runs bin/evilution-self against every lib/evilution
|
|
5
|
+
# subdir to re-baseline self-mutation coverage. Per-dir logs go to
|
|
6
|
+
# tmp/baseline_logs/<dir>.self.log. Aggregate summary printed to stdout.
|
|
7
|
+
|
|
8
|
+
require "English"
|
|
9
|
+
require "fileutils"
|
|
10
|
+
require "shellwords"
|
|
11
|
+
|
|
12
|
+
ROOT = File.expand_path("..", __dir__)
|
|
13
|
+
LOG_DIR = File.join(ROOT, "tmp", "baseline_logs")
|
|
14
|
+
WRAPPER = File.join(ROOT, "bin", "evilution-self")
|
|
15
|
+
|
|
16
|
+
# Originally three Process.fork-using files (isolation/fork.rb,
|
|
17
|
+
# parallel/work_queue/worker.rb, baseline.rb) were skipped because subject
|
|
18
|
+
# grandchildren inherited the runtime marshal pipe write-end and hung the
|
|
19
|
+
# parent. EV-9qh1 fixed the protocol (length-prefix + polling waitpid), so the
|
|
20
|
+
# skip list is now empty.
|
|
21
|
+
SKIP_FILES = [].freeze
|
|
22
|
+
|
|
23
|
+
FileUtils.mkdir_p(LOG_DIR)
|
|
24
|
+
|
|
25
|
+
dirs = Dir.children(File.join(ROOT, "lib", "evilution"))
|
|
26
|
+
.select { |entry| File.directory?(File.join(ROOT, "lib", "evilution", entry)) }
|
|
27
|
+
.sort
|
|
28
|
+
|
|
29
|
+
results = []
|
|
30
|
+
dirs.each do |dir|
|
|
31
|
+
files = Dir.glob(File.join(ROOT, "lib", "evilution", dir, "**", "*.rb"))
|
|
32
|
+
files.reject! { |f| SKIP_FILES.include?(f) }
|
|
33
|
+
|
|
34
|
+
if files.empty?
|
|
35
|
+
puts "==> #{dir} (skipped — only fork-using files)"
|
|
36
|
+
results << { dir: dir, exitstatus: nil, log: nil, skipped: true }
|
|
37
|
+
next
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
log = File.join(LOG_DIR, "#{dir}.self.log")
|
|
41
|
+
cmd = [WRAPPER, "--strict", "--jobs=4", "--timeout=15", "--quiet-children", *files]
|
|
42
|
+
puts "==> #{dir} (#{files.length} files)"
|
|
43
|
+
pid = spawn(*cmd, out: log, err: %i[child out])
|
|
44
|
+
Process.wait(pid)
|
|
45
|
+
status = $CHILD_STATUS
|
|
46
|
+
results << { dir: dir, exitstatus: status.exitstatus, log: log, skipped: false }
|
|
47
|
+
puts " exit=#{status.exitstatus} log=#{log}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Toplevel files (lib/evilution/*.rb directly under lib/evilution/ — those that
|
|
51
|
+
# aren't in a subdir). EV-j2kz tagged this set as 'toplevel'.
|
|
52
|
+
toplevel_files = Dir.glob(File.join(ROOT, "lib", "evilution", "*.rb"))
|
|
53
|
+
toplevel_files.reject! { |f| SKIP_FILES.include?(f) }
|
|
54
|
+
unless toplevel_files.empty?
|
|
55
|
+
log = File.join(LOG_DIR, "toplevel.self.log")
|
|
56
|
+
cmd = [WRAPPER, "--strict", "--jobs=4", "--timeout=15", "--quiet-children", *toplevel_files]
|
|
57
|
+
puts "==> toplevel (#{toplevel_files.length} files)"
|
|
58
|
+
pid = spawn(*cmd, out: log, err: %i[child out])
|
|
59
|
+
Process.wait(pid)
|
|
60
|
+
status = $CHILD_STATUS
|
|
61
|
+
results << { dir: "toplevel", exitstatus: status.exitstatus, log: log, skipped: false }
|
|
62
|
+
puts " exit=#{status.exitstatus} log=#{log}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
puts
|
|
66
|
+
puts "=== summary ==="
|
|
67
|
+
results.each do |r|
|
|
68
|
+
if r[:skipped]
|
|
69
|
+
puts format("%<dir>-15s SKIPPED", dir: r[:dir])
|
|
70
|
+
next
|
|
71
|
+
end
|
|
72
|
+
measurable = `grep -E '^Mutations: ' #{Shellwords.escape(r[:log])} 2>/dev/null | tail -1`.strip
|
|
73
|
+
measurable = "NO_SUMMARY" if measurable.empty?
|
|
74
|
+
puts format("%<dir>-15s exit=%<exit>s %<measurable>s",
|
|
75
|
+
dir: r[:dir], exit: r[:exitstatus], measurable: measurable)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
puts
|
|
79
|
+
puts "skipped files (fork-bug workaround): #{SKIP_FILES.map { |p| p.sub("#{ROOT}/", "") }.join(", ")}"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# EV-yyd8 fast validation: pick ONE small representative file per originally-
|
|
5
|
+
# crashing dir from EV-j2kz baseline, run bin/evilution-self against it, log
|
|
6
|
+
# results. Demonstrates the dual-runtime harness lifts the parent-crash issue
|
|
7
|
+
# for each previously-broken dir without requiring a multi-hour full re-run.
|
|
8
|
+
|
|
9
|
+
require "English"
|
|
10
|
+
require "fileutils"
|
|
11
|
+
require "shellwords"
|
|
12
|
+
|
|
13
|
+
ROOT = File.expand_path("..", __dir__)
|
|
14
|
+
LOG_DIR = File.join(ROOT, "tmp", "baseline_logs")
|
|
15
|
+
WRAPPER = File.join(ROOT, "bin", "evilution-self")
|
|
16
|
+
|
|
17
|
+
# One representative file per originally-crashing dir from EV-j2kz baseline.
|
|
18
|
+
# isolation/in_process.rb is the same file used in the spec verification step.
|
|
19
|
+
TARGETS = {
|
|
20
|
+
"isolation" => "lib/evilution/isolation/in_process.rb",
|
|
21
|
+
"result" => "lib/evilution/result/error_info.rb",
|
|
22
|
+
"compare" => "lib/evilution/compare/invalid_input.rb",
|
|
23
|
+
"parallel" => "lib/evilution/parallel/work_queue/collection_state.rb",
|
|
24
|
+
"ast" => "lib/evilution/ast/source_surgeon.rb",
|
|
25
|
+
"integration" => "lib/evilution/integration/loading.rb",
|
|
26
|
+
"runner" => "lib/evilution/runner/mutation_executor/neutralizer.rb",
|
|
27
|
+
"cli" => "lib/evilution/cli/result.rb",
|
|
28
|
+
"mutator" => "lib/evilution/mutator/operator.rb",
|
|
29
|
+
"toplevel" => "lib/evilution/memory.rb"
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
FileUtils.mkdir_p(LOG_DIR)
|
|
33
|
+
|
|
34
|
+
results = []
|
|
35
|
+
TARGETS.each do |dir, file|
|
|
36
|
+
full = File.join(ROOT, file)
|
|
37
|
+
log = File.join(LOG_DIR, "#{dir}.validation.log")
|
|
38
|
+
cmd = [WRAPPER, "--strict", "--jobs=1", "--timeout=15", "--quiet-children", full]
|
|
39
|
+
puts "==> #{dir}: #{file}"
|
|
40
|
+
pid = spawn(*cmd, out: log, err: %i[child out])
|
|
41
|
+
Process.wait(pid)
|
|
42
|
+
status = $CHILD_STATUS
|
|
43
|
+
results << { dir: dir, file: file, exitstatus: status.exitstatus, log: log }
|
|
44
|
+
puts " exit=#{status.exitstatus} log=#{log}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
puts
|
|
48
|
+
puts "=== validation summary ==="
|
|
49
|
+
results.each do |r|
|
|
50
|
+
measurable = `grep -E '^Mutations: ' #{Shellwords.escape(r[:log])} 2>/dev/null | tail -1`.strip
|
|
51
|
+
measurable = "NO_SUMMARY" if measurable.empty?
|
|
52
|
+
puts format("%<dir>-12s %<file>-55s exit=%<exit>s %<measurable>s",
|
|
53
|
+
dir: r[:dir], file: r[:file], exit: r[:exitstatus], measurable: measurable)
|
|
54
|
+
end
|
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.30.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-05-
|
|
11
|
+
date: 2026-05-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: diff-lcs
|
|
@@ -106,11 +106,13 @@ files:
|
|
|
106
106
|
- docs/ast_pattern_syntax.md
|
|
107
107
|
- docs/isolation.md
|
|
108
108
|
- docs/mutation_density_benchmark.md
|
|
109
|
+
- docs/versioning.md
|
|
109
110
|
- exe/evil
|
|
110
111
|
- exe/evilution
|
|
111
112
|
- lib/evilution.rb
|
|
112
113
|
- lib/evilution/ast.rb
|
|
113
114
|
- lib/evilution/ast/constant_names.rb
|
|
115
|
+
- lib/evilution/ast/heredoc_span.rb
|
|
114
116
|
- lib/evilution/ast/inheritance_scanner.rb
|
|
115
117
|
- lib/evilution/ast/parser.rb
|
|
116
118
|
- lib/evilution/ast/pattern.rb
|
|
@@ -202,6 +204,8 @@ files:
|
|
|
202
204
|
- lib/evilution/feedback.rb
|
|
203
205
|
- lib/evilution/feedback/detector.rb
|
|
204
206
|
- lib/evilution/feedback/messages.rb
|
|
207
|
+
- lib/evilution/feedback/setup_warning.rb
|
|
208
|
+
- lib/evilution/gem_detector.rb
|
|
205
209
|
- lib/evilution/git.rb
|
|
206
210
|
- lib/evilution/git/changed_files.rb
|
|
207
211
|
- lib/evilution/hooks.rb
|
|
@@ -211,6 +215,7 @@ files:
|
|
|
211
215
|
- lib/evilution/integration/base.rb
|
|
212
216
|
- lib/evilution/integration/crash_detector.rb
|
|
213
217
|
- lib/evilution/integration/loading.rb
|
|
218
|
+
- lib/evilution/integration/loading/body_call_neutralizer.rb
|
|
214
219
|
- lib/evilution/integration/loading/concern_state_cleaner.rb
|
|
215
220
|
- lib/evilution/integration/loading/constant_pinner.rb
|
|
216
221
|
- lib/evilution/integration/loading/mutation_applier.rb
|
|
@@ -269,6 +274,7 @@ files:
|
|
|
269
274
|
- lib/evilution/mutator.rb
|
|
270
275
|
- lib/evilution/mutator/base.rb
|
|
271
276
|
- lib/evilution/mutator/operator.rb
|
|
277
|
+
- lib/evilution/mutator/operator/argument_method_call_replacement.rb
|
|
272
278
|
- lib/evilution/mutator/operator/argument_nil_substitution.rb
|
|
273
279
|
- lib/evilution/mutator/operator/argument_removal.rb
|
|
274
280
|
- lib/evilution/mutator/operator/arithmetic_replacement.rb
|
|
@@ -308,6 +314,7 @@ files:
|
|
|
308
314
|
- lib/evilution/mutator/operator/integer_literal.rb
|
|
309
315
|
- lib/evilution/mutator/operator/keyword_argument.rb
|
|
310
316
|
- lib/evilution/mutator/operator/lambda_body.rb
|
|
317
|
+
- lib/evilution/mutator/operator/last_expression_removal.rb
|
|
311
318
|
- lib/evilution/mutator/operator/local_variable_assignment.rb
|
|
312
319
|
- lib/evilution/mutator/operator/loop_flip.rb
|
|
313
320
|
- lib/evilution/mutator/operator/method_body_replacement.rb
|
|
@@ -371,6 +378,7 @@ files:
|
|
|
371
378
|
- lib/evilution/reporter/cli/line_formatters.rb
|
|
372
379
|
- lib/evilution/reporter/cli/line_formatters/duration.rb
|
|
373
380
|
- lib/evilution/reporter/cli/line_formatters/efficiency.rb
|
|
381
|
+
- lib/evilution/reporter/cli/line_formatters/error_rate_warning.rb
|
|
374
382
|
- lib/evilution/reporter/cli/line_formatters/feedback_footer.rb
|
|
375
383
|
- lib/evilution/reporter/cli/line_formatters/header.rb
|
|
376
384
|
- lib/evilution/reporter/cli/line_formatters/mutations.rb
|
|
@@ -457,6 +465,7 @@ files:
|
|
|
457
465
|
- lib/evilution/runner/subject_pipeline.rb
|
|
458
466
|
- lib/evilution/session.rb
|
|
459
467
|
- lib/evilution/session/diff.rb
|
|
468
|
+
- lib/evilution/session/schema.rb
|
|
460
469
|
- lib/evilution/session/store.rb
|
|
461
470
|
- lib/evilution/source_ast_cache.rb
|
|
462
471
|
- lib/evilution/spec_ast_cache.rb
|
|
@@ -466,7 +475,11 @@ files:
|
|
|
466
475
|
- lib/evilution/temp_dir_tracker.rb
|
|
467
476
|
- lib/evilution/version.rb
|
|
468
477
|
- lib/tasks/memory_check.rake
|
|
478
|
+
- schema/evilution.config.schema.json
|
|
479
|
+
- script/build_runtime_snapshot
|
|
469
480
|
- script/memory_check
|
|
481
|
+
- script/run_self_baseline
|
|
482
|
+
- script/run_self_validation
|
|
470
483
|
- scripts/benchmark_density
|
|
471
484
|
- scripts/benchmark_density.yml
|
|
472
485
|
- scripts/compare_mutations
|