evilution 1.1.0 → 1.3.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 +58 -0
- data/.rubocop_todo.yml +9 -0
- data/CHANGELOG.md +86 -0
- data/README.md +202 -14
- data/docs/architecture.md +54 -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/ast/parser.rb +1 -0
- data/lib/evilution/ast/pattern/filter.rb +1 -0
- data/lib/evilution/ast/pattern/parser.rb +1 -0
- data/lib/evilution/ast/pattern.rb +2 -0
- data/lib/evilution/ast/sorbet_sig_detector.rb +1 -0
- data/lib/evilution/baseline.rb +37 -15
- data/lib/evilution/child_output.rb +1 -1
- data/lib/evilution/cli/command.rb +1 -0
- data/lib/evilution/cli/commands/tests_list.rb +4 -3
- data/lib/evilution/cli/commands.rb +2 -0
- data/lib/evilution/cli/dispatcher.rb +2 -0
- data/lib/evilution/cli/exit_guard.rb +61 -0
- data/lib/evilution/cli/parsed_args.rb +2 -0
- data/lib/evilution/cli/parser/command_extractor.rb +2 -0
- data/lib/evilution/cli/parser/file_args.rb +2 -0
- data/lib/evilution/cli/parser/options_builder.rb +4 -0
- data/lib/evilution/cli/parser/stdin_reader.rb +1 -0
- data/lib/evilution/cli/parser.rb +1 -0
- data/lib/evilution/cli/printers/tests_list.rb +5 -4
- data/lib/evilution/cli/printers.rb +2 -0
- data/lib/evilution/cli/result.rb +2 -0
- data/lib/evilution/cli.rb +6 -0
- data/lib/evilution/config/builders.rb +2 -0
- data/lib/evilution/config/env_loader.rb +2 -0
- data/lib/evilution/config/file_loader.rb +1 -0
- data/lib/evilution/config/sources.rb +1 -0
- data/lib/evilution/config/validators/example_targeting_cache.rb +1 -0
- data/lib/evilution/config/validators/example_targeting_fallback.rb +1 -0
- data/lib/evilution/config/validators/example_targeting_strategy.rb +1 -0
- data/lib/evilution/config/validators/fail_fast.rb +1 -0
- data/lib/evilution/config/validators/hooks.rb +1 -0
- data/lib/evilution/config/validators/ignore_patterns.rb +1 -0
- data/lib/evilution/config/validators/integration.rb +1 -0
- data/lib/evilution/config/validators/isolation.rb +1 -0
- data/lib/evilution/config/validators/jobs.rb +1 -0
- data/lib/evilution/config/validators/preload.rb +1 -0
- data/lib/evilution/config/validators/profile.rb +1 -0
- data/lib/evilution/config/validators/spec_mappings.rb +1 -0
- data/lib/evilution/config/validators/spec_pattern.rb +1 -0
- data/lib/evilution/config/validators/warmup.rb +17 -0
- data/lib/evilution/config/validators.rb +2 -0
- data/lib/evilution/config.rb +7 -5
- data/lib/evilution/coverage.rb +1 -1
- data/lib/evilution/coverage_example_filter.rb +1 -1
- data/lib/evilution/diagnostic.rb +22 -0
- data/lib/evilution/example_filter.rb +1 -1
- data/lib/evilution/integration/loading/body_call_neutralizer.rb +10 -2
- data/lib/evilution/integration/loading/concern_state_cleaner.rb +20 -4
- 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/loading/test_load_path.rb +21 -13
- 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_state.rb +1 -0
- data/lib/evilution/integration/rspec/state_guard/configuration_streams.rb +5 -1
- data/lib/evilution/integration/rspec/state_guard/example_groups_constants.rb +1 -2
- data/lib/evilution/integration/rspec/state_guard/internals.rb +1 -2
- data/lib/evilution/integration/rspec/state_guard/object_space_example_groups.rb +1 -2
- data/lib/evilution/integration/rspec/state_guard/reporter_arrays.rb +1 -0
- data/lib/evilution/integration/rspec/state_guard/world_example_groups.rb +1 -0
- data/lib/evilution/integration/rspec/state_guard/world_filtered_examples.rb +1 -0
- data/lib/evilution/integration/rspec/state_guard/world_sources_by_path.rb +1 -0
- data/lib/evilution/integration/rspec/state_guard.rb +6 -0
- 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/info_tool/actions/environment.rb +1 -0
- data/lib/evilution/mcp/info_tool/actions/feedback.rb +1 -0
- data/lib/evilution/mcp/info_tool/actions/statuses.rb +1 -0
- data/lib/evilution/mcp/info_tool/actions/subjects.rb +1 -0
- data/lib/evilution/mcp/info_tool/actions/tests.rb +1 -0
- data/lib/evilution/mutation.rb +1 -1
- data/lib/evilution/mutator/operator/argument_list_removal.rb +52 -0
- data/lib/evilution/mutator/operator/argument_propagation.rb +54 -0
- data/lib/evilution/mutator/operator/array_coercion_to_literal.rb +34 -0
- data/lib/evilution/mutator/operator/attribute_write_to_read.rb +34 -0
- data/lib/evilution/mutator/operator/bang_method.rb +11 -3
- data/lib/evilution/mutator/operator/binary_operand_promotion.rb +63 -0
- data/lib/evilution/mutator/operator/block_body_promotion.rb +45 -0
- data/lib/evilution/mutator/operator/block_body_to_nil.rb +52 -0
- data/lib/evilution/mutator/operator/block_body_to_raise.rb +34 -0
- 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/call_to_nil.rb +34 -0
- data/lib/evilution/mutator/operator/coercion_emptying.rb +60 -0
- data/lib/evilution/mutator/operator/collection_replacement.rb +19 -9
- data/lib/evilution/mutator/operator/comparison_replacement.rb +37 -12
- data/lib/evilution/mutator/operator/const_get_to_constant_path.rb +50 -0
- data/lib/evilution/mutator/operator/dig_to_fetch_chain.rb +45 -0
- data/lib/evilution/mutator/operator/double_negation_removal.rb +22 -0
- data/lib/evilution/mutator/operator/dynamic_dispatch_resolution.rb +79 -0
- data/lib/evilution/mutator/operator/forwarding_super_to_explicit.rb +71 -0
- data/lib/evilution/mutator/operator/inequality_to_negated_identity.rb +43 -0
- data/lib/evilution/mutator/operator/keyword_argument_removal.rb +47 -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/proc_to_lambda.rb +43 -0
- data/lib/evilution/mutator/operator/receiver_constructor_swap.rb +51 -0
- data/lib/evilution/mutator/operator/reduce_to_sum.rb +58 -0
- data/lib/evilution/mutator/operator/regexp_anchor_to_predicate.rb +136 -0
- data/lib/evilution/mutator/operator/safe_navigation_removal.rb +67 -0
- data/lib/evilution/mutator/operator/send_mutation.rb +42 -6
- data/lib/evilution/mutator/operator/symbol_to_proc_replacement.rb +59 -0
- data/lib/evilution/mutator/operator/to_i_to_integer.rb +38 -0
- data/lib/evilution/mutator/operator/typed_default_return.rb +84 -0
- data/lib/evilution/mutator/primitives.rb +25 -0
- data/lib/evilution/mutator/registry.rb +32 -1
- data/lib/evilution/parallel/pool.rb +1 -0
- data/lib/evilution/parallel_db_warning.rb +1 -1
- data/lib/evilution/process_supervisor.rb +20 -8
- data/lib/evilution/rails_warmup.rb +61 -0
- 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/html/report.rb +1 -0
- data/lib/evilution/reporter/json/subjects.rb +29 -0
- data/lib/evilution/reporter/json.rb +23 -1
- data/lib/evilution/result/coverage_gap_grouper.rb +1 -0
- 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 +38 -0
- data/lib/evilution/result/summary.rb +44 -2
- data/lib/evilution/runner/baseline_runner.rb +17 -7
- data/lib/evilution/runner/canary.rb +52 -5
- data/lib/evilution/runner/isolation_resolver.rb +23 -2
- data/lib/evilution/runner/mutation_executor/infra_retry.rb +54 -0
- data/lib/evilution/runner/mutation_executor/neutralizer/baseline_failed.rb +23 -10
- 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/source_ast_cache.rb +1 -1
- data/lib/evilution/spec_ast_cache.rb +1 -1
- data/lib/evilution/version.rb +1 -1
- data/lib/evilution.rb +35 -0
- metadata +50 -2
data/lib/evilution/baseline.rb
CHANGED
|
@@ -15,13 +15,24 @@ class Evilution::Baseline
|
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
# spec_selector: the object `run` resolves with (integration layout plus
|
|
19
|
+
# spec_mappings / spec_pattern), returning every spec covering a source.
|
|
20
|
+
# Without one, the bare spec_resolver is used. spec_resolver also supplies
|
|
21
|
+
# the best-guess hint for an unresolved source.
|
|
22
|
+
#
|
|
23
|
+
# fallback_to_full_suite: whether an unresolved source runs the whole
|
|
24
|
+
# fallback_dir. When false its mutations are reported unresolved, so the
|
|
25
|
+
# baseline skips it rather than running -- and often timing out on -- the
|
|
26
|
+
# entire test directory.
|
|
18
27
|
def initialize(spec_resolver: Evilution::SpecResolver.new, timeout: 30, runner: nil,
|
|
19
|
-
fallback_dir: "spec", test_files: nil)
|
|
28
|
+
fallback_dir: "spec", test_files: nil, spec_selector: nil, fallback_to_full_suite: true)
|
|
20
29
|
@spec_resolver = spec_resolver
|
|
21
30
|
@timeout = timeout
|
|
22
31
|
@runner = runner
|
|
23
32
|
@fallback_dir = fallback_dir
|
|
24
33
|
@test_files = test_files
|
|
34
|
+
@spec_selector = spec_selector
|
|
35
|
+
@fallback_to_full_suite = fallback_to_full_suite
|
|
25
36
|
end
|
|
26
37
|
|
|
27
38
|
def call(subjects)
|
|
@@ -43,7 +54,7 @@ class Evilution::Baseline
|
|
|
43
54
|
read_io, write_io = IO.pipe
|
|
44
55
|
pid = fork_spec_runner(spec_file, read_io, write_io)
|
|
45
56
|
write_io.close
|
|
46
|
-
read_result(read_io, pid)
|
|
57
|
+
read_result(read_io, pid, spec_file)
|
|
47
58
|
rescue Evilution::Error
|
|
48
59
|
raise
|
|
49
60
|
rescue StandardError
|
|
@@ -69,7 +80,7 @@ class Evilution::Baseline
|
|
|
69
80
|
|
|
70
81
|
GRACE_PERIOD = 0.5
|
|
71
82
|
|
|
72
|
-
def read_result(read_io, pid)
|
|
83
|
+
def read_result(read_io, pid, spec_file)
|
|
73
84
|
if read_io.wait_readable(@timeout)
|
|
74
85
|
data = read_io.read
|
|
75
86
|
Process.wait(pid)
|
|
@@ -79,10 +90,15 @@ class Evilution::Baseline
|
|
|
79
90
|
result[:passed]
|
|
80
91
|
else
|
|
81
92
|
terminate_child(pid)
|
|
93
|
+
warn_timeout(spec_file)
|
|
82
94
|
false
|
|
83
95
|
end
|
|
84
96
|
end
|
|
85
97
|
|
|
98
|
+
def warn_timeout(spec_file)
|
|
99
|
+
warn "[evilution] Baseline for #{spec_file} timed out after #{@timeout}s; treating it as failing."
|
|
100
|
+
end
|
|
101
|
+
|
|
86
102
|
def terminate_child(pid)
|
|
87
103
|
Evilution::ProcessCleanup.safe_kill("TERM", pid)
|
|
88
104
|
_, status = Process.waitpid2(pid, Process::WNOHANG)
|
|
@@ -98,11 +114,6 @@ class Evilution::Baseline
|
|
|
98
114
|
|
|
99
115
|
private
|
|
100
116
|
|
|
101
|
-
# When --spec was provided, run those files only. Auto-discovery is skipped
|
|
102
|
-
# entirely — the user has declared what covers their subjects and any
|
|
103
|
-
# mismatch between auto-discovery and their declaration is what produced
|
|
104
|
-
# the misleading "No matching test found" warning users have reported even
|
|
105
|
-
# while passing --spec.
|
|
106
117
|
def baseline_spec_files(subjects)
|
|
107
118
|
return Array(@test_files).uniq if @test_files && !@test_files.empty?
|
|
108
119
|
|
|
@@ -111,20 +122,31 @@ class Evilution::Baseline
|
|
|
111
122
|
|
|
112
123
|
def resolve_unique_spec_files(subjects)
|
|
113
124
|
warned = Set.new
|
|
114
|
-
subjects.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
125
|
+
subjects.flat_map do |s|
|
|
126
|
+
specs = specs_for(s.file_path)
|
|
127
|
+
next specs unless specs.empty?
|
|
128
|
+
|
|
129
|
+
warn_no_matching_test(s.file_path) if warned.add?(s.file_path)
|
|
130
|
+
@fallback_to_full_suite ? [@fallback_dir] : []
|
|
118
131
|
end.uniq
|
|
119
132
|
end
|
|
120
133
|
|
|
134
|
+
def specs_for(file_path)
|
|
135
|
+
Array(@spec_selector ? @spec_selector.call(file_path) : @spec_resolver.call(file_path))
|
|
136
|
+
end
|
|
137
|
+
|
|
121
138
|
def warn_no_matching_test(file_path)
|
|
139
|
+
action = @fallback_to_full_suite ? "running full suite" : "marking its mutations unresolved"
|
|
140
|
+
warn "[evilution] No matching test found for #{file_path}, #{action}. #{no_match_hint(file_path)}"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def no_match_hint(file_path)
|
|
122
144
|
suggestion = @spec_resolver.suggest(file_path)
|
|
123
145
|
hint = if suggestion
|
|
124
|
-
"Pass --spec #{suggestion} (best guess) or the correct test file
|
|
146
|
+
"Pass --spec #{suggestion} (best guess) or the correct test file"
|
|
125
147
|
else
|
|
126
|
-
"Use --spec to specify the test file
|
|
148
|
+
"Use --spec to specify the test file"
|
|
127
149
|
end
|
|
128
|
-
|
|
150
|
+
@fallback_to_full_suite ? "#{hint}." : "#{hint}, or --fallback-full-suite to run the whole suite."
|
|
129
151
|
end
|
|
130
152
|
end
|
|
@@ -5,7 +5,6 @@ require_relative "../command"
|
|
|
5
5
|
require_relative "../dispatcher"
|
|
6
6
|
require_relative "../printers/tests_list"
|
|
7
7
|
require_relative "../../config"
|
|
8
|
-
require_relative "../../spec_resolver"
|
|
9
8
|
require_relative "../../git/changed_files"
|
|
10
9
|
|
|
11
10
|
class Evilution::CLI::Commands::TestsList < Evilution::CLI::Command
|
|
@@ -25,8 +24,10 @@ class Evilution::CLI::Commands::TestsList < Evilution::CLI::Command
|
|
|
25
24
|
return 0
|
|
26
25
|
end
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
# The same selector `run` uses: the integration's test layout (test/*_test.rb
|
|
28
|
+
# for minitest and test-unit) plus spec_mappings and spec_pattern, so the
|
|
29
|
+
# two commands cannot disagree about which specs cover a source (GH #1596).
|
|
30
|
+
entries = source_files.map { |source| { source: source, specs: Array(config.spec_selector.call(source)) } }
|
|
30
31
|
Evilution::CLI::Printers::TestsList.new(mode: :resolved, entries: entries).render(@stdout)
|
|
31
32
|
0
|
|
32
33
|
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../cli"
|
|
4
|
+
|
|
5
|
+
# Gives evilution the final word on the process exit status.
|
|
6
|
+
#
|
|
7
|
+
# `--preload` loads the project's own spec helper into the parent process, and
|
|
8
|
+
# whatever that helper installs comes along with it. SimpleCov's at_exit hook
|
|
9
|
+
# calls `exit` with its own status when coverage is below the configured
|
|
10
|
+
# minimum, which replaces the status evilution computed and breaks a CI step
|
|
11
|
+
# reading it (EV-g8ya / GH #1608).
|
|
12
|
+
#
|
|
13
|
+
# at_exit hooks run last-registered-first, so a hook installed before the
|
|
14
|
+
# preload is the last one standing. It exits with `exit!`, which is immediate
|
|
15
|
+
# and cannot be overridden by anything left in the queue — by then every other
|
|
16
|
+
# hook, evilution's own temp-directory cleanup included, has already run.
|
|
17
|
+
#
|
|
18
|
+
# With no status recorded, evilution did not finish normally: an exception is on
|
|
19
|
+
# its way out, and Ruby's own handling decides the status instead.
|
|
20
|
+
#
|
|
21
|
+
# The hook belongs to the process that installed it. Evilution forks workers,
|
|
22
|
+
# and a fork inherits its parent's at_exit hooks; a worker running this one
|
|
23
|
+
# would exit with the parent's status and skip its own ending. The guard
|
|
24
|
+
# therefore remembers its process and stands down anywhere else.
|
|
25
|
+
class Evilution::CLI::ExitGuard
|
|
26
|
+
attr_accessor :status
|
|
27
|
+
|
|
28
|
+
def initialize(register: nil, exiter: nil, pid_source: nil, streams: nil)
|
|
29
|
+
@register = register || ->(&hook) { at_exit(&hook) }
|
|
30
|
+
@exiter = exiter || ->(code) { exit!(code) }
|
|
31
|
+
@streams = streams || -> { [$stdout, $stderr] }
|
|
32
|
+
@pid_source = pid_source || -> { Process.pid }
|
|
33
|
+
@status = nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def install
|
|
37
|
+
@pid = @pid_source.call
|
|
38
|
+
@register.call { fire }
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def fire
|
|
45
|
+
return if @status.nil?
|
|
46
|
+
return unless @pid_source.call == @pid
|
|
47
|
+
|
|
48
|
+
flush_streams
|
|
49
|
+
@exiter.call(@status)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# `exit!` leaves buffered output where it stands, so anything written by the
|
|
53
|
+
# report, or by a hook that ran before this one, has to be pushed out first.
|
|
54
|
+
def flush_streams
|
|
55
|
+
@streams.call.each do |stream|
|
|
56
|
+
stream.flush
|
|
57
|
+
rescue IOError, Errno::EBADF
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "optparse"
|
|
4
|
+
require_relative "../parser"
|
|
4
5
|
require_relative "../../version"
|
|
5
6
|
require_relative "file_args"
|
|
6
7
|
|
|
@@ -55,6 +56,7 @@ class Evilution::CLI::Parser::OptionsBuilder
|
|
|
55
56
|
|
|
56
57
|
def add_spec_filter_options(opts)
|
|
57
58
|
opts.on("--min-score FLOAT", Float, "Minimum mutation score to pass") { |s| @options[:min_score] = s }
|
|
59
|
+
opts.on("--output FILE", "Write the report to FILE instead of stdout") { |f| @options[:output] = f }
|
|
58
60
|
opts.on("--spec FILES", Array, "Spec files to run (comma-separated)") { |f| @options[:spec_files] = f }
|
|
59
61
|
opts.on("--spec-dir DIR", "Include all specs in DIR") { |d| expand_spec_dir(d) }
|
|
60
62
|
opts.on("--spec-pattern GLOB",
|
|
@@ -118,6 +120,8 @@ class Evilution::CLI::Parser::OptionsBuilder
|
|
|
118
120
|
"(default: auto-detect spec/rails_helper.rb -> spec/spec_helper.rb -> " \
|
|
119
121
|
"test/test_helper.rb for Rails projects)") { |f| @options[:preload] = f }
|
|
120
122
|
opts.on("--no-preload", "Disable parent-process preload even for Rails projects") { @options[:preload] = false }
|
|
123
|
+
opts.on("--warmup NAME", "After preload, warm lazily-initialised framework state once " \
|
|
124
|
+
"in the parent: none, rails (default: none)") { |w| @options[:warmup] = w }
|
|
121
125
|
end
|
|
122
126
|
|
|
123
127
|
def add_output_options(opts)
|
data/lib/evilution/cli/parser.rb
CHANGED
|
@@ -24,16 +24,17 @@ class Evilution::CLI::Printers::TestsList
|
|
|
24
24
|
io.puts(@specs.length == 1 ? "1 spec file" : "#{@specs.length} spec files")
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
# Each entry is `{ source:, specs: [...] }`; a source can resolve to several
|
|
28
|
+
# spec files (spec_mappings), and each gets its own line.
|
|
27
29
|
def render_resolved(io)
|
|
28
30
|
unique_specs = []
|
|
29
31
|
@entries.each do |entry|
|
|
30
32
|
source = entry[:source]
|
|
31
|
-
|
|
32
|
-
if
|
|
33
|
+
specs = entry[:specs]
|
|
34
|
+
io.puts(" #{source} (no spec found)") if specs.empty?
|
|
35
|
+
specs.each do |spec|
|
|
33
36
|
unique_specs << spec
|
|
34
37
|
io.puts(" #{spec} (#{source})")
|
|
35
|
-
else
|
|
36
|
-
io.puts(" #{source} (no spec found)")
|
|
37
38
|
end
|
|
38
39
|
end
|
|
39
40
|
|
data/lib/evilution/cli/result.rb
CHANGED
data/lib/evilution/cli.rb
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "version"
|
|
4
|
+
|
|
5
|
+
# Declared before the children are required: each child requires this file
|
|
6
|
+
# and nests under Evilution::CLI, so the namespace must exist first.
|
|
7
|
+
class Evilution::CLI; end # rubocop:disable Lint/EmptyClass
|
|
8
|
+
|
|
4
9
|
require_relative "cli/parser"
|
|
5
10
|
require_relative "cli/parsed_args"
|
|
6
11
|
require_relative "cli/printers/subjects"
|
|
@@ -18,6 +23,7 @@ require_relative "cli/commands/session_diff"
|
|
|
18
23
|
require_relative "cli/commands/session_gc"
|
|
19
24
|
require_relative "cli/commands/compare"
|
|
20
25
|
require_relative "cli/commands/run"
|
|
26
|
+
require_relative "cli/exit_guard"
|
|
21
27
|
|
|
22
28
|
# @api private
|
|
23
29
|
class Evilution::CLI
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../validators"
|
|
4
|
+
require_relative "base"
|
|
5
|
+
|
|
6
|
+
# `warmup: rails` warms Rails' lazily-initialised state in the parent after
|
|
7
|
+
# the preload; `none` (the default) skips it. `false` is accepted as `none`
|
|
8
|
+
# so `warmup: false` reads naturally in .evilution.yml.
|
|
9
|
+
class Evilution::Config::Validators::Warmup < Evilution::Config::Validators::Base
|
|
10
|
+
ALLOWED = %i[none rails].freeze
|
|
11
|
+
|
|
12
|
+
def self.call(value)
|
|
13
|
+
return :none if value == false
|
|
14
|
+
|
|
15
|
+
coerce_symbol!(value, allowed: ALLOWED, name: "warmup")
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/evilution/config.rb
CHANGED
|
@@ -17,15 +17,15 @@ class Evilution::Config
|
|
|
17
17
|
save_session: false, line_ranges: {}, spec_files: [], ignore_patterns: [],
|
|
18
18
|
show_disabled: false, baseline_session: nil, skip_heredoc_literals: false,
|
|
19
19
|
related_specs_heuristic: false, fallback_to_full_suite: false, preload: nil,
|
|
20
|
-
spec_mappings: {}, spec_pattern: nil, example_targeting: true,
|
|
20
|
+
output: nil, spec_mappings: {}, spec_pattern: nil, example_targeting: true,
|
|
21
21
|
example_targeting_fallback: :full_file, example_targeting_strategy: :lexical,
|
|
22
22
|
example_targeting_cache: { max_files: 50, max_blocks: 10_000 },
|
|
23
23
|
quiet_children: false, quiet_children_dir: "tmp/evilution_children",
|
|
24
|
-
profile: :default, canary: true
|
|
24
|
+
profile: :default, canary: true, warmup: :none
|
|
25
25
|
}.freeze
|
|
26
26
|
|
|
27
27
|
attr_reader :target_files, :schema_version, :timeout, :format,
|
|
28
|
-
:target, :min_score, :integration, :verbose, :quiet,
|
|
28
|
+
:target, :min_score, :integration, :verbose, :quiet, :output,
|
|
29
29
|
:jobs, :fail_fast, :baseline, :isolation, :incremental, :suggest_tests,
|
|
30
30
|
:progress, :save_session, :line_ranges, :spec_files, :hooks,
|
|
31
31
|
:ignore_patterns, :show_disabled, :baseline_session,
|
|
@@ -33,7 +33,7 @@ class Evilution::Config
|
|
|
33
33
|
:fallback_to_full_suite, :preload, :spec_mappings, :spec_pattern,
|
|
34
34
|
:example_targeting, :example_targeting_fallback, :example_targeting_strategy,
|
|
35
35
|
:example_targeting_cache,
|
|
36
|
-
:spec_selector, :quiet_children, :quiet_children_dir, :profile, :canary
|
|
36
|
+
:spec_selector, :quiet_children, :quiet_children_dir, :profile, :canary, :warmup
|
|
37
37
|
|
|
38
38
|
def initialize(**options)
|
|
39
39
|
skip_file = options.delete(:skip_config_file) ? true : false
|
|
@@ -243,6 +243,7 @@ class Evilution::Config
|
|
|
243
243
|
format: :to_sym.to_proc,
|
|
244
244
|
target: nil,
|
|
245
245
|
min_score: :to_f.to_proc,
|
|
246
|
+
output: nil,
|
|
246
247
|
verbose: nil,
|
|
247
248
|
quiet: nil,
|
|
248
249
|
baseline: nil,
|
|
@@ -273,7 +274,7 @@ class Evilution::Config
|
|
|
273
274
|
|
|
274
275
|
VALIDATED_ATTRS = %i[
|
|
275
276
|
integration jobs fail_fast isolation ignore_patterns
|
|
276
|
-
hooks preload spec_mappings spec_pattern profile
|
|
277
|
+
hooks preload spec_mappings spec_pattern profile warmup
|
|
277
278
|
].freeze
|
|
278
279
|
private_constant :VALIDATED_ATTRS
|
|
279
280
|
|
|
@@ -310,6 +311,7 @@ require_relative "config/validators/example_targeting_fallback"
|
|
|
310
311
|
require_relative "config/validators/example_targeting_strategy"
|
|
311
312
|
require_relative "config/validators/example_targeting_cache"
|
|
312
313
|
require_relative "config/validators/profile"
|
|
314
|
+
require_relative "config/validators/warmup"
|
|
313
315
|
require_relative "config/builders"
|
|
314
316
|
require_relative "config/builders/spec_resolver"
|
|
315
317
|
require_relative "config/builders/spec_selector"
|
data/lib/evilution/coverage.rb
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "version"
|
|
4
|
+
|
|
5
|
+
# Evilution's own advisory messages -- "no matching spec found", and the like.
|
|
6
|
+
#
|
|
7
|
+
# These deliberately bypass Kernel#warn. That routes through Warning.warn,
|
|
8
|
+
# which a project is free to replace: the `warning` gem with a raising handler
|
|
9
|
+
# turns every Ruby warning into an exception, and both dry-schema and
|
|
10
|
+
# dry-monads configure exactly that in their spec_helper. Under such a project
|
|
11
|
+
# our advisory message would be promoted to a fatal error, so a mutation we
|
|
12
|
+
# meant to classify :unresolved would be reported :error instead -- 239 of
|
|
13
|
+
# dry-monads' 240 mutations, in the run that found this.
|
|
14
|
+
#
|
|
15
|
+
# A message about how evilution is behaving must never be able to fail a
|
|
16
|
+
# user's mutation. Writing straight to the stream keeps it a diagnostic.
|
|
17
|
+
# EV-df7u / GH #1588.
|
|
18
|
+
module Evilution::Diagnostic
|
|
19
|
+
def self.warn(message, stream: $stderr)
|
|
20
|
+
stream.puts(message)
|
|
21
|
+
end
|
|
22
|
+
end
|