evilution 0.30.4 → 0.32.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 +22 -0
- data/.rubocop_todo.yml +6 -0
- data/CHANGELOG.md +22 -0
- data/README.md +9 -7
- data/docs/integrations.md +126 -0
- data/docs/isolation.md +28 -0
- data/lib/evilution/cli/parser/options_builder.rb +6 -1
- data/lib/evilution/config/validators/integration.rb +5 -1
- data/lib/evilution/config.rb +14 -4
- data/lib/evilution/integration/loading/mutation_applier.rb +16 -8
- data/lib/evilution/integration/loading/source_evaluator.rb +4 -1
- data/lib/evilution/integration/minitest.rb +1 -1
- data/lib/evilution/integration/rspec/baseline_runner.rb +3 -1
- data/lib/evilution/integration/rspec/framework_loader.rb +5 -1
- data/lib/evilution/integration/rspec.rb +38 -1
- data/lib/evilution/integration/test_unit/dispatcher.rb +26 -0
- data/lib/evilution/integration/test_unit/framework_loader.rb +33 -0
- data/lib/evilution/integration/test_unit/result_builder.rb +53 -0
- data/lib/evilution/integration/test_unit/subject_class_registry.rb +26 -0
- data/lib/evilution/integration/test_unit/test_file_resolver.rb +48 -0
- data/lib/evilution/integration/test_unit.rb +124 -0
- data/lib/evilution/integration/test_unit_crash_detector.rb +61 -0
- data/lib/evilution/isolation/fork.rb +26 -1
- data/lib/evilution/isolation/in_process.rb +20 -3
- data/lib/evilution/mcp/info_tool.rb +2 -2
- data/lib/evilution/mcp/mutate_tool.rb +3 -2
- data/lib/evilution/runner/baseline_runner.rb +3 -1
- data/lib/evilution/runner/canary.rb +130 -0
- data/lib/evilution/runner.rb +24 -1
- data/lib/evilution/spec_ast_cache.rb +20 -3
- data/lib/evilution/spec_resolver.rb +16 -2
- data/lib/evilution/spec_selector.rb +14 -2
- data/lib/evilution/version.rb +1 -1
- data/lib/evilution.rb +39 -0
- data/script/run_self_baseline +2 -2
- metadata +11 -2
|
@@ -59,13 +59,30 @@ class Evilution::SpecAstCache
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def parse(path)
|
|
62
|
-
|
|
62
|
+
resolved = resolve_path(path)
|
|
63
|
+
raise Evilution::ParseError.new("file not found: #{path}", file: path) unless resolved
|
|
63
64
|
|
|
64
|
-
source = read_source(
|
|
65
|
-
result = parse_source(
|
|
65
|
+
source = read_source(resolved)
|
|
66
|
+
result = parse_source(resolved, source)
|
|
66
67
|
collect_blocks(source, result, extract_comment_ranges(result))
|
|
67
68
|
end
|
|
68
69
|
|
|
70
|
+
# Accept either a CWD-relative path (historical) or one resolvable against
|
|
71
|
+
# Evilution::PROJECT_ROOT — needed for isolators chdir'd into a per-mutation
|
|
72
|
+
# sandbox (EV-wqxu / GH #1278). The PROJECT_ROOT fallback is gated on the
|
|
73
|
+
# isolated-worker flag so unrelated callers that chdir intentionally (e.g.
|
|
74
|
+
# tests using a fixture project layout) do not accidentally resolve into
|
|
75
|
+
# the evilution dev tree.
|
|
76
|
+
def resolve_path(path)
|
|
77
|
+
return path if File.exist?(path)
|
|
78
|
+
return nil unless Evilution.in_isolated_worker?
|
|
79
|
+
|
|
80
|
+
expanded = File.expand_path(path, Evilution::PROJECT_ROOT)
|
|
81
|
+
return expanded if File.exist?(expanded)
|
|
82
|
+
|
|
83
|
+
nil
|
|
84
|
+
end
|
|
85
|
+
|
|
69
86
|
def parse_source(path, source)
|
|
70
87
|
result = Prism.parse(source)
|
|
71
88
|
return result unless result.failure?
|
|
@@ -16,7 +16,7 @@ class Evilution::SpecResolver
|
|
|
16
16
|
normalized = normalize_path(source_path)
|
|
17
17
|
candidates = candidate_test_paths(normalized)
|
|
18
18
|
candidates = filter_by_pattern(candidates, spec_pattern) if spec_pattern
|
|
19
|
-
candidates.find { |path|
|
|
19
|
+
candidates.find { |path| project_relative_exists?(path) }
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def resolve_all(source_paths)
|
|
@@ -25,13 +25,27 @@ class Evilution::SpecResolver
|
|
|
25
25
|
|
|
26
26
|
private
|
|
27
27
|
|
|
28
|
+
# Existence check that succeeds against the current CWD. When the caller
|
|
29
|
+
# is an isolated worker that chdir'd into a per-mutation sandbox (Evilution
|
|
30
|
+
# signals this via in_isolated_worker?), also try PROJECT_ROOT so the
|
|
31
|
+
# sandbox CWD does not break spec resolution (EV-wqxu / GH #1278).
|
|
32
|
+
def project_relative_exists?(path)
|
|
33
|
+
return true if File.exist?(path)
|
|
34
|
+
return false unless Evilution.in_isolated_worker?
|
|
35
|
+
|
|
36
|
+
File.exist?(File.expand_path(path, Evilution::PROJECT_ROOT))
|
|
37
|
+
end
|
|
38
|
+
|
|
28
39
|
def filter_by_pattern(candidates, pattern)
|
|
29
40
|
candidates.select { |path| File.fnmatch?(pattern, path, File::FNM_PATHNAME | File::FNM_EXTGLOB) }
|
|
30
41
|
end
|
|
31
42
|
|
|
32
43
|
def normalize_path(path)
|
|
33
44
|
path = path.delete_prefix("./")
|
|
34
|
-
|
|
45
|
+
if path.start_with?("/")
|
|
46
|
+
path = path.delete_prefix("#{Dir.pwd}/")
|
|
47
|
+
path = path.delete_prefix("#{Evilution::PROJECT_ROOT}/") if Evilution.in_isolated_worker?
|
|
48
|
+
end
|
|
35
49
|
path
|
|
36
50
|
end
|
|
37
51
|
|
|
@@ -15,7 +15,7 @@ class Evilution::SpecSelector
|
|
|
15
15
|
|
|
16
16
|
mapped = mapping_for(source_path)
|
|
17
17
|
if mapped
|
|
18
|
-
existing = mapped.select { |path|
|
|
18
|
+
existing = mapped.select { |path| project_relative_exists?(path) }
|
|
19
19
|
return existing unless existing.empty?
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -33,7 +33,19 @@ class Evilution::SpecSelector
|
|
|
33
33
|
return path if path.nil?
|
|
34
34
|
|
|
35
35
|
normalized = path.to_s
|
|
36
|
-
|
|
36
|
+
if normalized.start_with?("/")
|
|
37
|
+
normalized = normalized.delete_prefix("#{Dir.pwd}/")
|
|
38
|
+
normalized = normalized.delete_prefix("#{Evilution::PROJECT_ROOT}/") if Evilution.in_isolated_worker?
|
|
39
|
+
end
|
|
37
40
|
normalized.delete_prefix("./")
|
|
38
41
|
end
|
|
42
|
+
|
|
43
|
+
# Same semantics as Evilution::SpecResolver#project_relative_exists? — see
|
|
44
|
+
# that method for the EV-wqxu / GH #1278 rationale.
|
|
45
|
+
def project_relative_exists?(path)
|
|
46
|
+
return true if File.exist?(path)
|
|
47
|
+
return false unless Evilution.in_isolated_worker?
|
|
48
|
+
|
|
49
|
+
File.exist?(File.expand_path(path, Evilution::PROJECT_ROOT))
|
|
50
|
+
end
|
|
39
51
|
end
|
data/lib/evilution/version.rb
CHANGED
data/lib/evilution.rb
CHANGED
|
@@ -129,6 +129,45 @@ require_relative "evilution/disable_comment"
|
|
|
129
129
|
require_relative "evilution/runner"
|
|
130
130
|
|
|
131
131
|
module Evilution
|
|
132
|
+
# Captured at load time, before any isolator can chdir into a per-mutation
|
|
133
|
+
# sandbox. Used as the anchor for resolving project-relative paths (spec
|
|
134
|
+
# files, source files for eval) from inside a chdir'd child so the CWD
|
|
135
|
+
# sandbox (EV-wqxu / GH #1278) cannot break spec resolution or eval __FILE__.
|
|
136
|
+
PROJECT_ROOT = Dir.pwd.freeze unless defined?(PROJECT_ROOT)
|
|
137
|
+
|
|
138
|
+
# Flag set by isolators (Evilution::Isolation::Fork in the forked child,
|
|
139
|
+
# Evilution::Isolation::InProcess around the test_command) so spec
|
|
140
|
+
# resolution and source eval anchor relative paths to PROJECT_ROOT instead
|
|
141
|
+
# of Dir.pwd. Without this gate, a caller that intentionally chdirs to a
|
|
142
|
+
# different project (e.g. a fixture layout in tests) would have its lookups
|
|
143
|
+
# inadvertently fall back to the evilution dev tree.
|
|
144
|
+
def self.in_isolated_worker!
|
|
145
|
+
@in_isolated_worker = true
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def self.in_isolated_worker?
|
|
149
|
+
@in_isolated_worker == true
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def self.with_isolated_worker
|
|
153
|
+
previous = @in_isolated_worker
|
|
154
|
+
@in_isolated_worker = true
|
|
155
|
+
yield
|
|
156
|
+
ensure
|
|
157
|
+
@in_isolated_worker = previous
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Base directory for resolving project-relative paths. An isolated worker
|
|
161
|
+
# has chdir'd into a per-mutation sandbox (EV-wqxu / GH #1278), so callers
|
|
162
|
+
# in that context must anchor against PROJECT_ROOT rather than Dir.pwd —
|
|
163
|
+
# otherwise spec files, source eval __FILE__, and $LOAD_PATH entries
|
|
164
|
+
# resolve into the sandbox and break the run. In any other context (normal
|
|
165
|
+
# use, tests that intentionally chdir into a fixture project layout, etc.)
|
|
166
|
+
# the caller's Dir.pwd remains the truth.
|
|
167
|
+
def self.project_base_dir
|
|
168
|
+
in_isolated_worker? ? PROJECT_ROOT : Dir.pwd
|
|
169
|
+
end
|
|
170
|
+
|
|
132
171
|
class Error < StandardError
|
|
133
172
|
attr_reader :file
|
|
134
173
|
|
data/script/run_self_baseline
CHANGED
|
@@ -38,7 +38,7 @@ dirs.each do |dir|
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
log = File.join(LOG_DIR, "#{dir}.self.log")
|
|
41
|
-
cmd = [WRAPPER, "--strict", "--jobs=4", "--timeout=15", "--quiet-children", *files]
|
|
41
|
+
cmd = [WRAPPER, "--strict", "--jobs=4", "--timeout=15", "--quiet-children", "--isolation=fork", *files]
|
|
42
42
|
puts "==> #{dir} (#{files.length} files)"
|
|
43
43
|
pid = spawn(*cmd, out: log, err: %i[child out])
|
|
44
44
|
Process.wait(pid)
|
|
@@ -53,7 +53,7 @@ toplevel_files = Dir.glob(File.join(ROOT, "lib", "evilution", "*.rb"))
|
|
|
53
53
|
toplevel_files.reject! { |f| SKIP_FILES.include?(f) }
|
|
54
54
|
unless toplevel_files.empty?
|
|
55
55
|
log = File.join(LOG_DIR, "toplevel.self.log")
|
|
56
|
-
cmd = [WRAPPER, "--strict", "--jobs=4", "--timeout=15", "--quiet-children", *toplevel_files]
|
|
56
|
+
cmd = [WRAPPER, "--strict", "--jobs=4", "--timeout=15", "--quiet-children", "--isolation=fork", *toplevel_files]
|
|
57
57
|
puts "==> toplevel (#{toplevel_files.length} files)"
|
|
58
58
|
pid = spawn(*cmd, out: log, err: %i[child out])
|
|
59
59
|
Process.wait(pid)
|
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.32.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-31 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: diff-lcs
|
|
@@ -104,6 +104,7 @@ files:
|
|
|
104
104
|
- comparison_results/operator_classification.md
|
|
105
105
|
- comparison_results/operator_prioritization.md
|
|
106
106
|
- docs/ast_pattern_syntax.md
|
|
107
|
+
- docs/integrations.md
|
|
107
108
|
- docs/isolation.md
|
|
108
109
|
- docs/mutation_density_benchmark.md
|
|
109
110
|
- docs/versioning.md
|
|
@@ -240,6 +241,13 @@ files:
|
|
|
240
241
|
- lib/evilution/integration/rspec/state_guard/world_sources_by_path.rb
|
|
241
242
|
- lib/evilution/integration/rspec/test_file_resolver.rb
|
|
242
243
|
- lib/evilution/integration/rspec/unresolved_spec_warner.rb
|
|
244
|
+
- lib/evilution/integration/test_unit.rb
|
|
245
|
+
- lib/evilution/integration/test_unit/dispatcher.rb
|
|
246
|
+
- lib/evilution/integration/test_unit/framework_loader.rb
|
|
247
|
+
- lib/evilution/integration/test_unit/result_builder.rb
|
|
248
|
+
- lib/evilution/integration/test_unit/subject_class_registry.rb
|
|
249
|
+
- lib/evilution/integration/test_unit/test_file_resolver.rb
|
|
250
|
+
- lib/evilution/integration/test_unit_crash_detector.rb
|
|
243
251
|
- lib/evilution/isolation.rb
|
|
244
252
|
- lib/evilution/isolation/fork.rb
|
|
245
253
|
- lib/evilution/isolation/in_process.rb
|
|
@@ -446,6 +454,7 @@ files:
|
|
|
446
454
|
- lib/evilution/result/summary.rb
|
|
447
455
|
- lib/evilution/runner.rb
|
|
448
456
|
- lib/evilution/runner/baseline_runner.rb
|
|
457
|
+
- lib/evilution/runner/canary.rb
|
|
449
458
|
- lib/evilution/runner/diagnostics.rb
|
|
450
459
|
- lib/evilution/runner/isolation_resolver.rb
|
|
451
460
|
- lib/evilution/runner/mutation_executor.rb
|