evilution 1.0.0 → 1.2.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 +41 -0
- data/CHANGELOG.md +65 -0
- data/README.md +149 -13
- data/docs/architecture.md +20 -5
- data/docs/isolation.md +3 -4
- data/exe/evil +8 -1
- data/exe/evilution +8 -1
- data/lib/evilution/ast/local_reads.rb +43 -0
- data/lib/evilution/cli/exit_guard.rb +59 -0
- data/lib/evilution/cli/parser/options_builder.rb +1 -0
- data/lib/evilution/cli.rb +1 -0
- data/lib/evilution/config.rb +3 -2
- data/lib/evilution/diagnostic.rb +22 -0
- data/lib/evilution/integration/loading/concern_state_cleaner.rb +20 -4
- data/lib/evilution/integration/loading/redefinition_recovery.rb +1 -1
- data/lib/evilution/integration/loading/reeval_warning_filter.rb +72 -0
- data/lib/evilution/integration/loading/source_evaluator.rb +4 -1
- data/lib/evilution/integration/minitest.rb +2 -1
- data/lib/evilution/integration/rspec/crash_detector_lifecycle.rb +9 -1
- data/lib/evilution/integration/rspec/state_guard/configuration_streams.rb +4 -1
- data/lib/evilution/integration/rspec/unresolved_spec_warner.rb +2 -1
- data/lib/evilution/integration/rspec.rb +48 -2
- data/lib/evilution/integration/test_unit/test_file_resolver.rb +2 -1
- data/lib/evilution/isolation/fork.rb +29 -8
- data/lib/evilution/mcp/complete_result_server.rb +41 -0
- data/lib/evilution/mcp/server.rb +2 -1
- data/lib/evilution/mutator/base.rb +14 -2
- data/lib/evilution/mutator/operator/block_destructuring_expansion.rb +85 -0
- data/lib/evilution/mutator/operator/block_parameter_drop.rb +96 -0
- data/lib/evilution/mutator/operator/boolean_expression_to_nil.rb +22 -0
- data/lib/evilution/mutator/operator/boolean_operand_promotion.rb +31 -0
- data/lib/evilution/mutator/operator/case_in.rb +64 -0
- data/lib/evilution/mutator/operator/case_when.rb +72 -1
- data/lib/evilution/mutator/operator/conditional_branch.rb +20 -7
- data/lib/evilution/mutator/operator/forwarding_super_to_explicit.rb +71 -0
- data/lib/evilution/mutator/operator/if_branch_swap.rb +48 -0
- data/lib/evilution/mutator/operator/loop_body_to_raise.rb +68 -0
- data/lib/evilution/mutator/operator/method_body_replacement.rb +10 -1
- data/lib/evilution/mutator/operator/method_body_to_raise.rb +59 -0
- data/lib/evilution/mutator/operator/method_body_to_super.rb +150 -0
- data/lib/evilution/mutator/operator/optional_default_injection.rb +71 -0
- data/lib/evilution/mutator/operator/optional_parameter_to_required.rb +41 -0
- data/lib/evilution/mutator/operator/pattern_predicate.rb +28 -0
- data/lib/evilution/mutator/operator/typed_default_return.rb +84 -0
- data/lib/evilution/mutator/primitives.rb +52 -0
- data/lib/evilution/mutator/registry.rb +14 -0
- data/lib/evilution/process_supervisor.rb +20 -8
- data/lib/evilution/reporter/cli/item_formatters/neutral_group.rb +23 -0
- data/lib/evilution/reporter/cli/item_formatters/subject_score.rb +42 -0
- data/lib/evilution/reporter/cli/item_formatters/subject_score_group.rb +16 -0
- data/lib/evilution/reporter/cli/line_formatters/infra_retry_notice.rb +19 -0
- data/lib/evilution/reporter/cli/line_formatters/result_line.rb +27 -3
- data/lib/evilution/reporter/cli/line_formatters/score.rb +19 -1
- data/lib/evilution/reporter/cli/line_formatters/unresolved_targets.rb +35 -0
- data/lib/evilution/reporter/cli/metrics_block.rb +4 -0
- data/lib/evilution/reporter/cli/trailer.rb +11 -7
- data/lib/evilution/reporter/cli.rb +20 -4
- data/lib/evilution/reporter/json/subjects.rb +29 -0
- data/lib/evilution/reporter/json.rb +23 -1
- data/lib/evilution/result/mutation_result.rb +3 -2
- data/lib/evilution/result/neutral_reason.rb +35 -0
- data/lib/evilution/result/subject_score.rb +28 -0
- data/lib/evilution/result/subject_scorer.rb +37 -0
- data/lib/evilution/result/summary.rb +44 -2
- data/lib/evilution/runner/canary.rb +52 -5
- data/lib/evilution/runner/mutation_executor/infra_retry.rb +54 -0
- data/lib/evilution/runner/mutation_executor/neutralizer/baseline_failed.rb +9 -3
- data/lib/evilution/runner/mutation_executor/neutralizer/infra_error.rb +18 -1
- data/lib/evilution/runner/mutation_executor/result_cache.rb +11 -0
- data/lib/evilution/runner/mutation_executor/strategy/parallel.rb +19 -1
- data/lib/evilution/runner/mutation_executor.rb +32 -4
- data/lib/evilution/runner/report_publisher.rb +31 -9
- data/lib/evilution/runner/target_spec_audit.rb +43 -0
- data/lib/evilution/runner.rb +10 -1
- data/lib/evilution/version.rb +1 -1
- data/lib/evilution.rb +18 -0
- data/scripts/compare_targeting +4 -2
- metadata +33 -2
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../neutralizer"
|
|
4
4
|
require_relative "../../../result/mutation_result"
|
|
5
|
+
require_relative "../../../result/neutral_reason"
|
|
5
6
|
|
|
6
7
|
class Evilution::Runner::MutationExecutor::Neutralizer::BaselineFailed
|
|
7
8
|
def initialize(config:, spec_resolver:, fallback_dir:)
|
|
@@ -15,25 +16,30 @@ class Evilution::Runner::MutationExecutor::Neutralizer::BaselineFailed
|
|
|
15
16
|
|
|
16
17
|
if @config.spec_files.any?
|
|
17
18
|
should_neutralize = true
|
|
19
|
+
spec_file = nil
|
|
18
20
|
else
|
|
19
21
|
spec_file = @spec_resolver.call(result.mutation.file_path) || @fallback_dir
|
|
20
22
|
should_neutralize = baseline_result.failed_spec_files.include?(spec_file)
|
|
21
23
|
end
|
|
22
24
|
return result unless should_neutralize
|
|
23
25
|
|
|
24
|
-
neutralize(result)
|
|
26
|
+
neutralize(result, spec_file)
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
private
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
# The spec that was already red is the fact a reader needs: it says what to
|
|
32
|
+
# fix before the run can say anything about these mutations
|
|
33
|
+
# (EV-5pob / GH #1606).
|
|
34
|
+
def neutralize(result, spec_file)
|
|
30
35
|
Evilution::Result::MutationResult.new(
|
|
31
36
|
mutation: result.mutation,
|
|
32
37
|
status: :neutral,
|
|
33
38
|
duration: result.duration,
|
|
34
39
|
test_command: result.test_command,
|
|
35
40
|
memory: result.memory,
|
|
36
|
-
error: result.error
|
|
41
|
+
error: result.error,
|
|
42
|
+
neutral_reason: Evilution::Result::NeutralReason.baseline_failure(spec_file)
|
|
37
43
|
)
|
|
38
44
|
end
|
|
39
45
|
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../neutralizer"
|
|
4
4
|
require_relative "../../../result/mutation_result"
|
|
5
|
+
require_relative "../../../result/neutral_reason"
|
|
5
6
|
|
|
6
7
|
# Reclassify results as :neutral when the failure was caused by test
|
|
7
8
|
# infrastructure rather than by the mutation. Two independent paths:
|
|
@@ -33,6 +34,21 @@ class Evilution::Runner::MutationExecutor::Neutralizer::InfraError
|
|
|
33
34
|
].freeze
|
|
34
35
|
private_constant :INFRA_ERROR_CLASSES, :INFRA_BACKTRACE_PATHS, :INFRA_CRASH_CLASSES
|
|
35
36
|
|
|
37
|
+
# Whether a crash class is one of the infrastructure failures that say
|
|
38
|
+
# nothing about the mutation. The parallel strategy asks this before the
|
|
39
|
+
# neutralisation pipeline has run, while the result is still a `:killed`
|
|
40
|
+
# crash (EV-j0bv / GH #1607).
|
|
41
|
+
def self.infra_crash_class?(error_class)
|
|
42
|
+
INFRA_CRASH_CLASSES.include?(error_class)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Whether this result is a kill that was demoted because the test process
|
|
46
|
+
# crashed on infrastructure rather than on the mutation. Such a mutation got
|
|
47
|
+
# no verdict, so a parallel run can re-run it once the contention is over.
|
|
48
|
+
def self.infra_neutral?(result)
|
|
49
|
+
result.neutral? && infra_crash_class?(result.error_class)
|
|
50
|
+
end
|
|
51
|
+
|
|
36
52
|
def call(result, **_ctx)
|
|
37
53
|
return neutralize(result) if infra_crash?(result)
|
|
38
54
|
return result unless result.error?
|
|
@@ -62,7 +78,8 @@ class Evilution::Runner::MutationExecutor::Neutralizer::InfraError
|
|
|
62
78
|
duration: result.duration,
|
|
63
79
|
test_command: result.test_command,
|
|
64
80
|
memory: result.memory,
|
|
65
|
-
error: result.error
|
|
81
|
+
error: result.error,
|
|
82
|
+
neutral_reason: Evilution::Result::NeutralReason.infra_error(result.error_class)
|
|
66
83
|
)
|
|
67
84
|
end
|
|
68
85
|
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../mutation_executor"
|
|
4
4
|
require_relative "../../result/mutation_result"
|
|
5
|
+
require_relative "neutralizer/infra_error"
|
|
5
6
|
|
|
6
7
|
class Evilution::Runner::MutationExecutor::ResultCache
|
|
7
8
|
CACHEABLE_STATUSES = %i[killed timeout].freeze
|
|
@@ -32,6 +33,7 @@ class Evilution::Runner::MutationExecutor::ResultCache
|
|
|
32
33
|
def store(mutation, result)
|
|
33
34
|
return unless @backend
|
|
34
35
|
return unless result.killed? || result.timeout?
|
|
36
|
+
return if infra_crash?(result)
|
|
35
37
|
|
|
36
38
|
@backend.store(mutation,
|
|
37
39
|
status: result.status,
|
|
@@ -40,6 +42,15 @@ class Evilution::Runner::MutationExecutor::ResultCache
|
|
|
40
42
|
test_command: result.test_command)
|
|
41
43
|
end
|
|
42
44
|
|
|
45
|
+
# A kill that came from an infrastructure crash is not a verdict about the
|
|
46
|
+
# mutation, and the cache keeps no error class to tell them apart later. It
|
|
47
|
+
# is left out so the next fetch does not hand back a plain `:killed`, which
|
|
48
|
+
# would defeat both the demotion to neutral and the serial retry
|
|
49
|
+
# (EV-j0bv / GH #1607).
|
|
50
|
+
def infra_crash?(result)
|
|
51
|
+
Evilution::Runner::MutationExecutor::Neutralizer::InfraError.infra_crash_class?(result.error_class)
|
|
52
|
+
end
|
|
53
|
+
|
|
43
54
|
def partition(batch, packer:)
|
|
44
55
|
uncached_indices = []
|
|
45
56
|
cached_results = {}
|
|
@@ -65,10 +65,28 @@ class Evilution::Runner::MutationExecutor::Strategy::Parallel
|
|
|
65
65
|
compact_results = merge(batch, partition.uncached_indices, partition.cached_results, worker_results)
|
|
66
66
|
batch_results = rebuild_results(batch, compact_results)
|
|
67
67
|
cache_results(batch_results, partition.uncached_indices)
|
|
68
|
-
batch
|
|
68
|
+
release_sources(batch, batch_results)
|
|
69
69
|
batch_results
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
+
# Sources are dropped as soon as a mutation has been judged, to keep a long
|
|
73
|
+
# run's memory flat. A mutation the workers could not judge because of
|
|
74
|
+
# infrastructure contention is re-run serially once the pool is done
|
|
75
|
+
# (InfraRetry), which needs its sources, so it keeps them until then. The
|
|
76
|
+
# neutralisation pipeline has not run yet here, so the crash is still a kill.
|
|
77
|
+
def release_sources(batch, batch_results)
|
|
78
|
+
batch.each_with_index do |mutation, index|
|
|
79
|
+
result = batch_results[index]
|
|
80
|
+
next if result && infra_crash?(result)
|
|
81
|
+
|
|
82
|
+
mutation.strip_sources!
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def infra_crash?(result)
|
|
87
|
+
Evilution::Runner::MutationExecutor::Neutralizer::InfraError.infra_crash_class?(result.error_class)
|
|
88
|
+
end
|
|
89
|
+
|
|
72
90
|
def rebuild_results(batch, compact_results)
|
|
73
91
|
batch.zip(compact_results).map { |m, h| @packer.rebuild(m, h) }
|
|
74
92
|
end
|
|
@@ -3,13 +3,18 @@
|
|
|
3
3
|
require_relative "../runner"
|
|
4
4
|
|
|
5
5
|
class Evilution::Runner::MutationExecutor
|
|
6
|
-
ExecutionResult = Data.define(:results, :truncated)
|
|
6
|
+
ExecutionResult = Data.define(:results, :truncated, :infra_retried) do
|
|
7
|
+
def initialize(results:, truncated:, infra_retried: 0)
|
|
8
|
+
super
|
|
9
|
+
end
|
|
10
|
+
end
|
|
7
11
|
|
|
8
12
|
autoload :ResultCache, File.expand_path("mutation_executor/result_cache", __dir__)
|
|
9
13
|
autoload :ResultPacker, File.expand_path("mutation_executor/result_packer", __dir__)
|
|
10
14
|
autoload :ResultNotifier, File.expand_path("mutation_executor/result_notifier", __dir__)
|
|
11
15
|
autoload :MutationRunner, File.expand_path("mutation_executor/mutation_runner", __dir__)
|
|
12
16
|
autoload :NeutralizationPipeline, File.expand_path("mutation_executor/neutralization_pipeline", __dir__)
|
|
17
|
+
autoload :InfraRetry, File.expand_path("mutation_executor/infra_retry", __dir__)
|
|
13
18
|
autoload :Strategy, File.expand_path("mutation_executor/strategy", __dir__)
|
|
14
19
|
autoload :Neutralizer, File.expand_path("mutation_executor/neutralizer", __dir__)
|
|
15
20
|
|
|
@@ -29,13 +34,36 @@ class Evilution::Runner::MutationExecutor
|
|
|
29
34
|
spec_resolver = baseline_failed?(baseline_result) ? @baseline_runner.neutralization_resolver : nil
|
|
30
35
|
notifier = build_notifier
|
|
31
36
|
pipeline = build_pipeline(spec_resolver)
|
|
32
|
-
|
|
37
|
+
return sequential_run(notifier, pipeline, mutations, baseline_result, integration) if @config.jobs <= 1
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
parallel_run(notifier, pipeline, mutations, baseline_result, integration)
|
|
35
40
|
end
|
|
36
41
|
|
|
37
42
|
private
|
|
38
43
|
|
|
44
|
+
def sequential_run(notifier, pipeline, mutations, baseline_result, integration)
|
|
45
|
+
build_sequential(notifier, pipeline)
|
|
46
|
+
.call(mutations, baseline_result: baseline_result, integration: integration)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Infrastructure contention only exists while the pool is running, so the
|
|
50
|
+
# mutations it left without a verdict are re-run once it is done. Without
|
|
51
|
+
# this the neutral bucket swings with --jobs on identical input
|
|
52
|
+
# (EV-j0bv / GH #1607).
|
|
53
|
+
def parallel_run(notifier, pipeline, mutations, baseline_result, integration)
|
|
54
|
+
execution = build_parallel(notifier, pipeline)
|
|
55
|
+
.call(mutations, baseline_result: baseline_result, integration: integration)
|
|
56
|
+
retry_pass = InfraRetry.new(runner: build_mutation_runner, pipeline: pipeline)
|
|
57
|
+
results = retry_pass.call(execution.results, baseline_result: baseline_result, integration: integration)
|
|
58
|
+
|
|
59
|
+
ExecutionResult.new(results: results, truncated: execution.truncated,
|
|
60
|
+
infra_retried: retry_pass.retried_count)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def build_mutation_runner
|
|
64
|
+
MutationRunner.new(config: @config, cache: @cache, isolator: @isolator)
|
|
65
|
+
end
|
|
66
|
+
|
|
39
67
|
def baseline_failed?(baseline_result)
|
|
40
68
|
baseline_result && baseline_result.failed?
|
|
41
69
|
end
|
|
@@ -59,7 +87,7 @@ class Evilution::Runner::MutationExecutor
|
|
|
59
87
|
|
|
60
88
|
def build_sequential(notifier, pipeline)
|
|
61
89
|
Strategy::Sequential.new(
|
|
62
|
-
runner:
|
|
90
|
+
runner: build_mutation_runner,
|
|
63
91
|
pipeline: pipeline,
|
|
64
92
|
notifier: notifier
|
|
65
93
|
)
|
|
@@ -11,20 +11,42 @@ class Evilution::Runner::ReportPublisher
|
|
|
11
11
|
@config = config
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def publish(summary)
|
|
14
|
+
def publish(summary, stdout: $stdout)
|
|
15
15
|
reporter = build_reporter
|
|
16
16
|
return unless reporter
|
|
17
17
|
|
|
18
18
|
output = reporter.call(summary)
|
|
19
19
|
return if config.quiet
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
deliver(output, stdout)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def deliver(output, stdout)
|
|
25
|
+
return publish_html(output) if config.html?
|
|
26
|
+
return File.write(config.output, output) if config.output
|
|
27
|
+
|
|
28
|
+
stdout.puts(output)
|
|
29
|
+
claim_stdout(stdout) if config.json?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# `--preload` brings the project's at_exit hooks into this process, and
|
|
33
|
+
# SimpleCov's prints its coverage report on the way out. Once the document is
|
|
34
|
+
# written, stdout is pointed at stderr so nothing can append to it and leave a
|
|
35
|
+
# consumer with unparseable JSON (EV-g8ya / GH #1608). Text reports are read
|
|
36
|
+
# by people, not parsers, so they keep the stream as it was.
|
|
37
|
+
def claim_stdout(stdout)
|
|
38
|
+
stdout.flush
|
|
39
|
+
stdout.reopen($stderr)
|
|
40
|
+
rescue TypeError
|
|
41
|
+
# A harness that captures output swaps $stdout for a StringIO, which cannot
|
|
42
|
+
# be reopened onto an IO. There is no file descriptor to protect there.
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def publish_html(output)
|
|
47
|
+
path = "evilution-report.html"
|
|
48
|
+
File.write(path, output)
|
|
49
|
+
warn "HTML report written to #{path}"
|
|
28
50
|
end
|
|
29
51
|
|
|
30
52
|
def save_session(summary)
|
|
@@ -44,7 +66,7 @@ class Evilution::Runner::ReportPublisher
|
|
|
44
66
|
when :json
|
|
45
67
|
Evilution::Reporter::JSON.new(integration: config.integration)
|
|
46
68
|
when :text
|
|
47
|
-
Evilution::Reporter::CLI.new
|
|
69
|
+
Evilution::Reporter::CLI.new(min_score: config.min_score)
|
|
48
70
|
when :html
|
|
49
71
|
Evilution::Reporter::HTML.new(baseline: load_baseline_session, integration: config.integration)
|
|
50
72
|
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../runner"
|
|
4
|
+
|
|
5
|
+
# Answers, in the parent process and before any mutation runs, which of the
|
|
6
|
+
# files evilution was pointed at resolve to no test file at all.
|
|
7
|
+
#
|
|
8
|
+
# The integrations resolve specs per mutation inside the worker, which is what
|
|
9
|
+
# produces `:unresolved` results, but that is a per-mutation fact diluted by
|
|
10
|
+
# every other file in the run: a target with no spec at all contributes a
|
|
11
|
+
# handful of unresolved mutations and disappears behind a healthy score
|
|
12
|
+
# (EV-p4sm / GH #1603). Asking the same selector once per file, up front, keeps
|
|
13
|
+
# the stronger fact — "you named this file and it was never tested" — intact.
|
|
14
|
+
#
|
|
15
|
+
# Only files that produced a subject are audited: a file evilution found
|
|
16
|
+
# nothing to mutate in — a constants-only file, say — has no untested behaviour
|
|
17
|
+
# to report, and failing a run over one would be noise.
|
|
18
|
+
#
|
|
19
|
+
# With `fallback_to_full_suite` a file without its own spec is run against the
|
|
20
|
+
# whole suite rather than skipped, so nothing goes untested and there is
|
|
21
|
+
# nothing to report.
|
|
22
|
+
class Evilution::Runner::TargetSpecAudit
|
|
23
|
+
def initialize(config)
|
|
24
|
+
@config = config
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def call(file_paths)
|
|
28
|
+
return [] if @config.fallback_to_full_suite?
|
|
29
|
+
|
|
30
|
+
file_paths.uniq.sort.reject { |path| resolved?(path) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
# SpecSelector answers nil when nothing resolves; a custom resolver plugged
|
|
36
|
+
# in through it may answer an empty list instead.
|
|
37
|
+
def resolved?(path)
|
|
38
|
+
specs = @config.spec_selector.call(path)
|
|
39
|
+
return false if specs.nil?
|
|
40
|
+
|
|
41
|
+
!specs.empty?
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/evilution/runner.rb
CHANGED
|
@@ -46,9 +46,13 @@ class Evilution::Runner
|
|
|
46
46
|
|
|
47
47
|
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
|
|
48
48
|
|
|
49
|
+
audited_files = subjects.map(&:file_path).uniq.sort
|
|
49
50
|
summary = Evilution::Result::Summary.new(results: results, duration: duration, truncated: execution.truncated,
|
|
50
51
|
skipped: plan.skipped_count,
|
|
51
|
-
disabled_mutations: plan.disabled_mutations
|
|
52
|
+
disabled_mutations: plan.disabled_mutations,
|
|
53
|
+
unresolved_target_files: target_spec_audit.call(audited_files),
|
|
54
|
+
target_file_count: audited_files.length,
|
|
55
|
+
infra_retried: execution.infra_retried)
|
|
52
56
|
output_report(summary)
|
|
53
57
|
save_session(summary)
|
|
54
58
|
|
|
@@ -71,6 +75,10 @@ class Evilution::Runner
|
|
|
71
75
|
@mutation_planner ||= Evilution::Runner::MutationPlanner.new(config, registry: registry)
|
|
72
76
|
end
|
|
73
77
|
|
|
78
|
+
def target_spec_audit
|
|
79
|
+
@target_spec_audit ||= Evilution::Runner::TargetSpecAudit.new(config)
|
|
80
|
+
end
|
|
81
|
+
|
|
74
82
|
def isolation_resolver
|
|
75
83
|
@isolation_resolver ||= Evilution::Runner::IsolationResolver.new(
|
|
76
84
|
config,
|
|
@@ -240,6 +248,7 @@ require_relative "rails_detector"
|
|
|
240
248
|
require_relative "parallel_db_warning"
|
|
241
249
|
require_relative "child_output"
|
|
242
250
|
require_relative "runner/subject_pipeline"
|
|
251
|
+
require_relative "runner/target_spec_audit"
|
|
243
252
|
require_relative "runner/mutation_planner"
|
|
244
253
|
require_relative "runner/isolation_resolver"
|
|
245
254
|
require_relative "runner/baseline_runner"
|
data/lib/evilution/version.rb
CHANGED
data/lib/evilution.rb
CHANGED
|
@@ -13,6 +13,7 @@ require_relative "evilution/ast/parser"
|
|
|
13
13
|
require_relative "evilution/ast/inheritance_scanner"
|
|
14
14
|
require_relative "evilution/ast/pattern"
|
|
15
15
|
require_relative "evilution/ast/sorbet_sig_detector"
|
|
16
|
+
require_relative "evilution/ast/local_reads"
|
|
16
17
|
require_relative "evilution/ast/pattern/matcher"
|
|
17
18
|
require_relative "evilution/ast/pattern/parser"
|
|
18
19
|
require_relative "evilution/hooks"
|
|
@@ -27,11 +28,14 @@ require_relative "evilution/mutator/operator/integer_literal"
|
|
|
27
28
|
require_relative "evilution/mutator/operator/float_literal"
|
|
28
29
|
require_relative "evilution/mutator/operator/nil_replacement"
|
|
29
30
|
require_relative "evilution/mutator/operator/boolean_operator_replacement"
|
|
31
|
+
require_relative "evilution/mutator/operator/boolean_operand_promotion"
|
|
32
|
+
require_relative "evilution/mutator/operator/boolean_expression_to_nil"
|
|
30
33
|
require_relative "evilution/mutator/operator/arithmetic_replacement"
|
|
31
34
|
require_relative "evilution/mutator/operator/string_literal"
|
|
32
35
|
require_relative "evilution/mutator/operator/array_literal"
|
|
33
36
|
require_relative "evilution/mutator/operator/hash_literal"
|
|
34
37
|
require_relative "evilution/mutator/operator/conditional_branch"
|
|
38
|
+
require_relative "evilution/mutator/operator/if_branch_swap"
|
|
35
39
|
require_relative "evilution/mutator/operator/symbol_literal"
|
|
36
40
|
require_relative "evilution/mutator/operator/conditional_negation"
|
|
37
41
|
require_relative "evilution/mutator/operator/negation_insertion"
|
|
@@ -85,9 +89,20 @@ require_relative "evilution/mutator/operator/splat_operator"
|
|
|
85
89
|
require_relative "evilution/mutator/operator/defined_check"
|
|
86
90
|
require_relative "evilution/mutator/operator/regex_capture"
|
|
87
91
|
require_relative "evilution/mutator/operator/loop_flip"
|
|
92
|
+
require_relative "evilution/mutator/operator/loop_body_to_raise"
|
|
93
|
+
require_relative "evilution/mutator/operator/method_body_to_raise"
|
|
94
|
+
require_relative "evilution/mutator/operator/method_body_to_super"
|
|
95
|
+
require_relative "evilution/mutator/operator/typed_default_return"
|
|
96
|
+
require_relative "evilution/mutator/operator/block_parameter_drop"
|
|
97
|
+
require_relative "evilution/mutator/operator/optional_parameter_to_required"
|
|
98
|
+
require_relative "evilution/mutator/operator/optional_default_injection"
|
|
99
|
+
require_relative "evilution/mutator/operator/block_destructuring_expansion"
|
|
100
|
+
require_relative "evilution/mutator/operator/forwarding_super_to_explicit"
|
|
88
101
|
require_relative "evilution/mutator/operator/string_interpolation"
|
|
89
102
|
require_relative "evilution/mutator/operator/retry_removal"
|
|
90
103
|
require_relative "evilution/mutator/operator/case_when"
|
|
104
|
+
require_relative "evilution/mutator/operator/case_in"
|
|
105
|
+
require_relative "evilution/mutator/operator/pattern_predicate"
|
|
91
106
|
require_relative "evilution/mutator/operator/predicate_replacement"
|
|
92
107
|
require_relative "evilution/mutator/operator/predicate_to_nil"
|
|
93
108
|
require_relative "evilution/mutator/operator/equality_to_identity"
|
|
@@ -115,6 +130,9 @@ require_relative "evilution/integration/base"
|
|
|
115
130
|
require_relative "evilution/integration/rspec"
|
|
116
131
|
require_relative "evilution/integration/minitest"
|
|
117
132
|
require_relative "evilution/result/mutation_result"
|
|
133
|
+
require_relative "evilution/result/neutral_reason"
|
|
134
|
+
require_relative "evilution/result/subject_score"
|
|
135
|
+
require_relative "evilution/result/subject_scorer"
|
|
118
136
|
require_relative "evilution/result/summary"
|
|
119
137
|
require_relative "evilution/reporter"
|
|
120
138
|
require_relative "evilution/reporter/json"
|
data/scripts/compare_targeting
CHANGED
|
@@ -128,8 +128,10 @@ module CompareTargeting
|
|
|
128
128
|
def lost_kills(mode = "coverage")
|
|
129
129
|
full = @modes.fetch("full_file")
|
|
130
130
|
other = @modes.fetch(mode)
|
|
131
|
-
full
|
|
132
|
-
|
|
131
|
+
full
|
|
132
|
+
.keys
|
|
133
|
+
.select { |key| full.status(key) == "killed" && other.status(key) != "killed" }
|
|
134
|
+
.map { |key| full.label(key) }
|
|
133
135
|
end
|
|
134
136
|
|
|
135
137
|
def to_row(repo)
|
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: 1.
|
|
4
|
+
version: 1.2.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-09-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: diff-lcs
|
|
@@ -123,6 +123,7 @@ files:
|
|
|
123
123
|
- lib/evilution/ast/constant_names.rb
|
|
124
124
|
- lib/evilution/ast/heredoc_span.rb
|
|
125
125
|
- lib/evilution/ast/inheritance_scanner.rb
|
|
126
|
+
- lib/evilution/ast/local_reads.rb
|
|
126
127
|
- lib/evilution/ast/parser.rb
|
|
127
128
|
- lib/evilution/ast/pattern.rb
|
|
128
129
|
- lib/evilution/ast/pattern/filter.rb
|
|
@@ -150,6 +151,7 @@ files:
|
|
|
150
151
|
- lib/evilution/cli/commands/util_mutation.rb
|
|
151
152
|
- lib/evilution/cli/commands/version.rb
|
|
152
153
|
- lib/evilution/cli/dispatcher.rb
|
|
154
|
+
- lib/evilution/cli/exit_guard.rb
|
|
153
155
|
- lib/evilution/cli/parsed_args.rb
|
|
154
156
|
- lib/evilution/cli/parser.rb
|
|
155
157
|
- lib/evilution/cli/parser/command_extractor.rb
|
|
@@ -206,6 +208,7 @@ files:
|
|
|
206
208
|
- lib/evilution/coverage/map_store.rb
|
|
207
209
|
- lib/evilution/coverage/recorder.rb
|
|
208
210
|
- lib/evilution/coverage_example_filter.rb
|
|
211
|
+
- lib/evilution/diagnostic.rb
|
|
209
212
|
- lib/evilution/disable_comment.rb
|
|
210
213
|
- lib/evilution/equivalent.rb
|
|
211
214
|
- lib/evilution/equivalent/detector.rb
|
|
@@ -237,6 +240,7 @@ files:
|
|
|
237
240
|
- lib/evilution/integration/loading/constant_pinner.rb
|
|
238
241
|
- lib/evilution/integration/loading/mutation_applier.rb
|
|
239
242
|
- lib/evilution/integration/loading/redefinition_recovery.rb
|
|
243
|
+
- lib/evilution/integration/loading/reeval_warning_filter.rb
|
|
240
244
|
- lib/evilution/integration/loading/source_evaluator.rb
|
|
241
245
|
- lib/evilution/integration/loading/syntax_validator.rb
|
|
242
246
|
- lib/evilution/integration/loading/test_load_path.rb
|
|
@@ -273,6 +277,7 @@ files:
|
|
|
273
277
|
- lib/evilution/load_path.rb
|
|
274
278
|
- lib/evilution/load_path/subpath_resolver.rb
|
|
275
279
|
- lib/evilution/mcp.rb
|
|
280
|
+
- lib/evilution/mcp/complete_result_server.rb
|
|
276
281
|
- lib/evilution/mcp/info_tool.rb
|
|
277
282
|
- lib/evilution/mcp/info_tool/actions.rb
|
|
278
283
|
- lib/evilution/mcp/info_tool/actions/base.rb
|
|
@@ -310,12 +315,17 @@ files:
|
|
|
310
315
|
- lib/evilution/mutator/operator/begin_unwrap.rb
|
|
311
316
|
- lib/evilution/mutator/operator/bitwise_complement.rb
|
|
312
317
|
- lib/evilution/mutator/operator/bitwise_replacement.rb
|
|
318
|
+
- lib/evilution/mutator/operator/block_destructuring_expansion.rb
|
|
313
319
|
- lib/evilution/mutator/operator/block_param_removal.rb
|
|
320
|
+
- lib/evilution/mutator/operator/block_parameter_drop.rb
|
|
314
321
|
- lib/evilution/mutator/operator/block_pass_removal.rb
|
|
315
322
|
- lib/evilution/mutator/operator/block_removal.rb
|
|
323
|
+
- lib/evilution/mutator/operator/boolean_expression_to_nil.rb
|
|
316
324
|
- lib/evilution/mutator/operator/boolean_literal_replacement.rb
|
|
325
|
+
- lib/evilution/mutator/operator/boolean_operand_promotion.rb
|
|
317
326
|
- lib/evilution/mutator/operator/boolean_operator_replacement.rb
|
|
318
327
|
- lib/evilution/mutator/operator/break_statement.rb
|
|
328
|
+
- lib/evilution/mutator/operator/case_in.rb
|
|
319
329
|
- lib/evilution/mutator/operator/case_when.rb
|
|
320
330
|
- lib/evilution/mutator/operator/class_variable_write.rb
|
|
321
331
|
- lib/evilution/mutator/operator/collection_replacement.rb
|
|
@@ -330,8 +340,10 @@ files:
|
|
|
330
340
|
- lib/evilution/mutator/operator/equality_to_identity.rb
|
|
331
341
|
- lib/evilution/mutator/operator/explicit_super_mutation.rb
|
|
332
342
|
- lib/evilution/mutator/operator/float_literal.rb
|
|
343
|
+
- lib/evilution/mutator/operator/forwarding_super_to_explicit.rb
|
|
333
344
|
- lib/evilution/mutator/operator/global_variable_write.rb
|
|
334
345
|
- lib/evilution/mutator/operator/hash_literal.rb
|
|
346
|
+
- lib/evilution/mutator/operator/if_branch_swap.rb
|
|
335
347
|
- lib/evilution/mutator/operator/index_assignment_removal.rb
|
|
336
348
|
- lib/evilution/mutator/operator/index_to_at.rb
|
|
337
349
|
- lib/evilution/mutator/operator/index_to_dig.rb
|
|
@@ -343,17 +355,23 @@ files:
|
|
|
343
355
|
- lib/evilution/mutator/operator/lambda_body.rb
|
|
344
356
|
- lib/evilution/mutator/operator/last_expression_removal.rb
|
|
345
357
|
- lib/evilution/mutator/operator/local_variable_assignment.rb
|
|
358
|
+
- lib/evilution/mutator/operator/loop_body_to_raise.rb
|
|
346
359
|
- lib/evilution/mutator/operator/loop_flip.rb
|
|
347
360
|
- lib/evilution/mutator/operator/method_body_replacement.rb
|
|
361
|
+
- lib/evilution/mutator/operator/method_body_to_raise.rb
|
|
362
|
+
- lib/evilution/mutator/operator/method_body_to_super.rb
|
|
348
363
|
- lib/evilution/mutator/operator/method_call_removal.rb
|
|
349
364
|
- lib/evilution/mutator/operator/mixin_removal.rb
|
|
350
365
|
- lib/evilution/mutator/operator/multiple_assignment.rb
|
|
351
366
|
- lib/evilution/mutator/operator/negation_insertion.rb
|
|
352
367
|
- lib/evilution/mutator/operator/next_statement.rb
|
|
353
368
|
- lib/evilution/mutator/operator/nil_replacement.rb
|
|
369
|
+
- lib/evilution/mutator/operator/optional_default_injection.rb
|
|
370
|
+
- lib/evilution/mutator/operator/optional_parameter_to_required.rb
|
|
354
371
|
- lib/evilution/mutator/operator/pattern_matching_alternative.rb
|
|
355
372
|
- lib/evilution/mutator/operator/pattern_matching_array.rb
|
|
356
373
|
- lib/evilution/mutator/operator/pattern_matching_guard.rb
|
|
374
|
+
- lib/evilution/mutator/operator/pattern_predicate.rb
|
|
357
375
|
- lib/evilution/mutator/operator/predicate_replacement.rb
|
|
358
376
|
- lib/evilution/mutator/operator/predicate_to_nil.rb
|
|
359
377
|
- lib/evilution/mutator/operator/range_replacement.rb
|
|
@@ -374,8 +392,10 @@ files:
|
|
|
374
392
|
- lib/evilution/mutator/operator/string_literal.rb
|
|
375
393
|
- lib/evilution/mutator/operator/superclass_removal.rb
|
|
376
394
|
- lib/evilution/mutator/operator/symbol_literal.rb
|
|
395
|
+
- lib/evilution/mutator/operator/typed_default_return.rb
|
|
377
396
|
- lib/evilution/mutator/operator/yield_statement.rb
|
|
378
397
|
- lib/evilution/mutator/operator/zsuper_removal.rb
|
|
398
|
+
- lib/evilution/mutator/primitives.rb
|
|
379
399
|
- lib/evilution/mutator/registry.rb
|
|
380
400
|
- lib/evilution/parallel.rb
|
|
381
401
|
- lib/evilution/parallel/pool.rb
|
|
@@ -403,19 +423,24 @@ files:
|
|
|
403
423
|
- lib/evilution/reporter/cli/item_formatters/coverage_gap.rb
|
|
404
424
|
- lib/evilution/reporter/cli/item_formatters/disabled.rb
|
|
405
425
|
- lib/evilution/reporter/cli/item_formatters/error.rb
|
|
426
|
+
- lib/evilution/reporter/cli/item_formatters/neutral_group.rb
|
|
406
427
|
- lib/evilution/reporter/cli/item_formatters/result_location.rb
|
|
428
|
+
- lib/evilution/reporter/cli/item_formatters/subject_score.rb
|
|
429
|
+
- lib/evilution/reporter/cli/item_formatters/subject_score_group.rb
|
|
407
430
|
- lib/evilution/reporter/cli/line_formatters.rb
|
|
408
431
|
- lib/evilution/reporter/cli/line_formatters/duration.rb
|
|
409
432
|
- lib/evilution/reporter/cli/line_formatters/efficiency.rb
|
|
410
433
|
- lib/evilution/reporter/cli/line_formatters/error_rate_warning.rb
|
|
411
434
|
- lib/evilution/reporter/cli/line_formatters/feedback_footer.rb
|
|
412
435
|
- lib/evilution/reporter/cli/line_formatters/header.rb
|
|
436
|
+
- lib/evilution/reporter/cli/line_formatters/infra_retry_notice.rb
|
|
413
437
|
- lib/evilution/reporter/cli/line_formatters/mutations.rb
|
|
414
438
|
- lib/evilution/reporter/cli/line_formatters/peak_memory.rb
|
|
415
439
|
- lib/evilution/reporter/cli/line_formatters/result_line.rb
|
|
416
440
|
- lib/evilution/reporter/cli/line_formatters/score.rb
|
|
417
441
|
- lib/evilution/reporter/cli/line_formatters/truncation_notice.rb
|
|
418
442
|
- lib/evilution/reporter/cli/line_formatters/unresolved_rate_warning.rb
|
|
443
|
+
- lib/evilution/reporter/cli/line_formatters/unresolved_targets.rb
|
|
419
444
|
- lib/evilution/reporter/cli/metrics_block.rb
|
|
420
445
|
- lib/evilution/reporter/cli/pct.rb
|
|
421
446
|
- lib/evilution/reporter/cli/section.rb
|
|
@@ -458,6 +483,7 @@ files:
|
|
|
458
483
|
- lib/evilution/reporter/html/templates/unparseable_details.html.erb
|
|
459
484
|
- lib/evilution/reporter/html/templates/unresolved_details.html.erb
|
|
460
485
|
- lib/evilution/reporter/json.rb
|
|
486
|
+
- lib/evilution/reporter/json/subjects.rb
|
|
461
487
|
- lib/evilution/reporter/progress_bar.rb
|
|
462
488
|
- lib/evilution/reporter/suggestion.rb
|
|
463
489
|
- lib/evilution/reporter/suggestion/diff_helpers.rb
|
|
@@ -473,6 +499,9 @@ files:
|
|
|
473
499
|
- lib/evilution/result/error_info.rb
|
|
474
500
|
- lib/evilution/result/memory_stats.rb
|
|
475
501
|
- lib/evilution/result/mutation_result.rb
|
|
502
|
+
- lib/evilution/result/neutral_reason.rb
|
|
503
|
+
- lib/evilution/result/subject_score.rb
|
|
504
|
+
- lib/evilution/result/subject_scorer.rb
|
|
476
505
|
- lib/evilution/result/summary.rb
|
|
477
506
|
- lib/evilution/runner.rb
|
|
478
507
|
- lib/evilution/runner/baseline_runner.rb
|
|
@@ -480,6 +509,7 @@ files:
|
|
|
480
509
|
- lib/evilution/runner/diagnostics.rb
|
|
481
510
|
- lib/evilution/runner/isolation_resolver.rb
|
|
482
511
|
- lib/evilution/runner/mutation_executor.rb
|
|
512
|
+
- lib/evilution/runner/mutation_executor/infra_retry.rb
|
|
483
513
|
- lib/evilution/runner/mutation_executor/mutation_runner.rb
|
|
484
514
|
- lib/evilution/runner/mutation_executor/neutralization_pipeline.rb
|
|
485
515
|
- lib/evilution/runner/mutation_executor/neutralizer.rb
|
|
@@ -494,6 +524,7 @@ files:
|
|
|
494
524
|
- lib/evilution/runner/mutation_planner.rb
|
|
495
525
|
- lib/evilution/runner/report_publisher.rb
|
|
496
526
|
- lib/evilution/runner/subject_pipeline.rb
|
|
527
|
+
- lib/evilution/runner/target_spec_audit.rb
|
|
497
528
|
- lib/evilution/session.rb
|
|
498
529
|
- lib/evilution/session/diff.rb
|
|
499
530
|
- lib/evilution/session/schema.rb
|